Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow http video streaming while downloading #12542

Closed
wants to merge 14 commits into from

Conversation

jagannatharjun
Copy link
Member

@jagannatharjun jagannatharjun commented Apr 18, 2020

It basically provides the user with an HTTP URL, which can be played with any video player that allows users to watch movies and seek while a file is downloading. Internally it uses HTTP byte serving.

with this QBittorrent is almost "complete" for me 😊

@jagannatharjun jagannatharjun marked this pull request as draft April 18, 2020 20:25
@jagannatharjun
Copy link
Member Author

jagannatharjun commented Apr 18, 2020

"core" part is complete i.e streaming works, but require some work to be "mergeable", actually I tried to implement this for fun, but to my surprise actually made it work and it was far easier then I thought XD, but I do realize real work starts now.

about the "interface"
Currently, I made some spaghetti to allow async processing in Connection(base\http\connection.h) and IRequestHandler(base\http\IRequestHandler.h), which I think will not be acceptable. New plan
image
basically IRequestHandler will become an "Empty" interface, and Connection class will use dynamic_cast to determine whether the class is SyncHandler or ASyncHandler and process accordingly

@glassez @Chocobo1 opinions?

PS: I am very new to UML and stuff.

@glassez
Copy link
Member

glassez commented Apr 19, 2020

IMO, it is another step on the way to make qBittorrent "bloatware".
Not to mention its implementation (it looks like there are a lot of cross-dependencies again, etc.)... but I will only review it if this "feature" is approved generally.

@glassez glassez requested a review from Chocobo1 April 19, 2020 06:03
@Chocobo1
Copy link
Member

Chocobo1 commented Apr 19, 2020

While it is a cool feature, unfortunately it doesn't really align with this project vision. It should be a good idea to maintain such a fork (on your side) and users could have more choices.

@jagannatharjun
Copy link
Member Author

IMO, it is another step on the way to make qBittorrent "bloatware".

, unfortunately it doesn't really align with this project vision.

Since QBittorrent isn't good with handling large number of torrents and "real" seeders will use a barebone libtorrent wrappers anyway. I think our target audience consists of casual torrenters. Utorrent and many other clients have this feature and at the end of the day, it's pretty useful for people who just use torrent for movies which is the majority. I personally use this a lot and had to use Web torrent until now and then shift my torrents to qBittorrent for seeding (don't want to use Utorrent). It would be quite useful for me if you guys let me get this in. I can help with maintenance.

It should be a good idea to maintain such a fork (on your side)

I do this for fun and learning and maintaining a fork would be too much for me. Personally I think it would not be successful anyway, not many people run anything from the internet, I would never.

I hope you guys will see it more from the user side.

@sledgehammer999
Copy link
Member

@jagannatharjun I need some clarification since the title is minimal an now description is given in the opening comment. And I am not going to look through all that code.

What does this do? Does it stream local video files to someone connected to the qbt-machine via http?

@FranciscoPombal
Copy link
Member

@jagannatharjun I think qbittorrent-nox has the potential of competing with the more barebones libtorrent wrappers. The WebAPI and WebUI just need a little more work.

Anyway, I must say the purpose of this PR is also not entirely clear to me. What does this accomplish that is different from simply downloading sequentially and opening the file? I will build this and test it during the week to have a closer look, but I'd also appreciate a more detailed explanation.

Thanks for the initiative anyway.

@Chocobo1
Copy link
Member

Chocobo1 commented Apr 20, 2020

Since QBittorrent isn't good with handling large number of torrents and "real" seeders will use a barebone libtorrent wrappers anyway.

Is it affecting GUI or WebUI? I think supporting more workload is always the goal for qbittorrent ;)

Does it stream local video files to someone connected to the qbt-machine via http?

AFAIK it gives the user a URL for a movie file on server, and the user can open it with a media player and play it (and probably seek it).

@jagannatharjun jagannatharjun changed the title Allow http video streaming Allow http video streaming while downloading Apr 20, 2020
@jagannatharjun
Copy link
Member Author

jagannatharjun commented Apr 20, 2020

Is it affecting GUI or WebUI? I think this is always the goal for qbittorrent ;)

I have some "seeders" friends, they tend to use rtorrent or deluge for seedboxes. They imply that qbittorrent is too "heavy" for seedboxes and can't handle 100+ torrent. But that's a different debate. And I still stand by that our main user base is "normal" people, who just download torrents to watch movies and this feature would be very useful for this group of people.

What does this do? Does it stream local video files to someone connected to the qbt-machine via http?

Sorry about that. this lockdown is getting on my head🤦‍♂️
about the feature, as @Chocobo1 said it gives the user an URL to play video files while downloading. The main highlight is seeking and other related user experience improvements. This simply uses Http byte serving for implementation, which is very simple to implement and shouldn't pose any maintenance burden,

@FranciscoPombal
Copy link
Member

I have some "seeders" friends, they tend to use rtorrent or deluge for seedboxes. They imply that qbittorrent is too "heavy" for seedboxes and can't handle 100+ torrent. But that's a different debate. And I still stand by that our main user base is "normal" people, who just download torrents to watch movies.

People using qbittorrent in a headless setting got the impression that it does not handle 100+ torrents well because they are most likely using the webUI, and for a long time the webUI was badly misusing the webAPI in a way that severely impacted performance. This was relatively recently fixed in #11635. Before that, just clicking around the WebUI would send more and more requests before the current one was complete, essentially DOS'ing the instance. Now, even on slow machines the experience is good with up to ~1000 torrents. After that, the steady performance degradation starts to become noticeable. A faster machine may still handle several thousands very well, but qBittorrent is definitely not capable yet of handling several thousands or 10k+ torrents in general with the current webAPI implementation. But the "10k" problem is another matter entirely.

about the feature, as @Chocobo1 said it gives the user an URL to play video files while downloading. The main highlight is seeking and other related user experience improvements. This simply uses Http byte serving for implementation, which is very simple to implement and shouldn't pose any maintenance burden,

👍

@sledgehammer999
Copy link
Member

about the feature, as @Chocobo1 said it gives the user an URL to play video files while downloading.

In that case I have to agree with @glassez and @Chocobo1. This is out of scope for a bittorrent client. Or at least for qBittorrent.
If a user runs a server, then he surely has other means of accessing the storage (eg ssh/ftp).

It would be another story, if this was an optimization to serially download torrent pieces to facilitate "streaming" by simultaneously downloading a torrent and watching it.

I am sorry that you did all this work, but we can't accept it.

@jagannatharjun
Copy link
Member Author

jagannatharjun commented Apr 20, 2020

I respect your opinion, but most of the users don't run on servers but locally, not all features are useful for everyone but if it is useful for most of them, it should be considered.

If I'm correct Utorrent has this since like 2011

@sledgehammer999
Copy link
Member

I respect your opinion, but most of the users don't run on servers but locally

But if they run it locally then why can't simply access the storage themselves?

@jagannatharjun
Copy link
Member Author

But if they run it locally then why can't simply access the storage themselves?

The main idea here is to provide seeking.

@sledgehammer999
Copy link
Member

The main idea here is to provide seeking.

Can you expand on that? Do you mean seeking a file from a multifile torrent or seeking inside a file?

@jagannatharjun
Copy link
Member Author

Can you expand on that? Do you mean seeking a file from a multifile torrent or seeking inside a file?

Seeking in video files while playing, This enables QBittorrent to download that part of the file which is currently playing in the video player.

@sledgehammer999
Copy link
Member

Seeking in video files while playing, This enables QBittorrent to download that part of the file which is currently playing in the video player.

So somewhere in your code you implement this:

It would be another story, if this was an optimization to serially download torrent pieces to facilitate "streaming" by simultaneously downloading a torrent and watching it.

Right?
If so, what happens in the above case if the user simply opens said file in their media player? Won't they be able to play it? Only that they won't be able to seek right?

@jagannatharjun
Copy link
Member Author

jagannatharjun commented Apr 20, 2020

So somewhere in your code you implement this:

In Http byte streaming, one can ask for the part of a file from the server through simple Http request, this is how this PR facilitates the streaming of file, The media player (client) provides us the range, and we provide the file back in the specified range by the following process :

  1. map the range into the piece using torrent_info::map_file
  2. if we don't have that piece, set the deadline_time of that piece so as to prioritize this specific piece and check back after that deadline,
    if still don't have that piece repeat until we have that piece
  3. send read_piece request to libtorrent
  4. send back provided data from libtorrent through the http connection to the client (media player) who made the request

basically this PR adds the ability to selectively and promptly download the currently needed chunk of the file by the media player.

If so, what happens in the above case if the user simply opens said file in their media player? Won't they be able to play it? Only that they won't be able to seek right?

yes, they won't be able to seek if they were to directly open from disk, they should open it from the provided URL.

@jagannatharjun
Copy link
Member Author

Whenever a user requests to "stream a file", we provide an Http URL, if that URL is opened with media player, we follow http byte serving protocol and serve the file in this manner

  • map the range into the piece using torrent_info::map_file
  • if we don't have that piece, set the deadline_time of that piece so as to prioritize this specific piece and check back after that deadline,
    if still don't have that piece repeat until we have that piece
  • send read_piece request to libtorrent
  • send back provided data from libtorrent through the http connection to the client (media player) who made the request

@FranciscoPombal
Copy link
Member

@jagannatharjun thanks for the clarifications. Are the seeked pieces still saved on disk, same as any other piece? If so, this seems like it could be a straight improvement over simply downloading sequentially - the files get saved all the same, but with this method the user has the freedom to seek around while playing, outside of strict sequential order.

@jagannatharjun
Copy link
Member Author

Are the seeked pieces still saved on disk, same as any other piece?

yes, they are still saved, there is no difference from that end.

@sledgehammer999
Copy link
Member

After the explanation/clarification given in the previous comments I can see that this feature might be useful to some users. Now I am not entirely against it.

Does anyone else feel like this is ok to add?

@jagannatharjun
Copy link
Member Author

Current Implementation works but doesn't follow RFC fully. Had to reimplement a large part of it and I don't think I can use Http::Server and Http::Connection as they are and refactoring them is just too much so have to provide separate implementation but I don't that will add too much code. A lot of "logic" based code can be just reused from Http::* like RequestParser etc.

@glassez @Chocobo1 any chance of getting this in or should I close this PR 😊?

@glassez
Copy link
Member

glassez commented Apr 28, 2020

I don't think I can use Http::Server and Http::Connection as they are and refactoring them is just too much

It's a generally bad idea to make something universal if it ends up being equally bad for everyone. If it can't be equally good, it's better to have different implementations for different tasks.
You can try to extract some lower level things and share them if possible (according to the sentence above).

@jagannatharjun
Copy link
Member Author

jagannatharjun commented Apr 28, 2020

It's a generally bad idea to make something universal if it ends up being equally bad for everyone. If it can't be equally good, it's better to have different implementations for different tasks.
You can try to extract some lower level things and share them if possible (according to the sentence above).

The problem is that all clients actually request whole file in a single request, so then I have to store the connection for a very long time send response in chunks, this is bit difficult with the current setup of Http::Server and Http::Connection. moreover, I have to do this in asynchronous manner

Also with this response, I assume you approve this feature 😊, will continue working on it.

@glassez
Copy link
Member

glassez commented Apr 28, 2020

Also with this response, I assume you approve this feature

...if it "looks nice" in the end (not as something "alien", breaking code design and unmaintainable).

@jagannatharjun
Copy link
Member Author

with the latest push, This PR is complete specification wise and "works". So if anyone wants to try out they can do it by downloading files from Github action or compiling themselves. PS: I may not able to complete it for review till next year.

Copy link
Member

@glassez glassez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to start dealing with this PR, but there are so many Coding Style violations here that I'm starting to get bogged down in it... Could you get it ready for review first?

, m_torrentHandle(torrentHandle)
{
const BitTorrent::TorrentInfo info = m_torrentHandle->info();
m_name = info.name() + '/' + info.fileName(fileIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not m_name = info.fileName(fileIndex);? Or even m_name = torrentHandle->fileName(fileIndex);

@@ -328,6 +328,7 @@ void SearchWidget::on_searchButton_clicked()
QString tabName = pattern;
tabName.replace(QRegularExpression("&{1}"), "&&");
m_ui->tabWidget->addTab(newTab, tabName);
//m_ui->tabWidget->setTabsClosable(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like unrelated changes...

@@ -64,7 +64,8 @@ void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o

QStyleOptionProgressBar newopt;
newopt.initFrom(&m_dummyProgressBar);
newopt.rect = option.rect;
static_cast<QStyleOption &>(newopt) = static_cast<const QStyleOption &>(option);
newopt.palette = m_dummyProgressBar.palette();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like unrelated changes...

using QTcpServer::QTcpServer;

void start();
private:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add empty line above.

#include <QTcpSocket>
#include <QElapsedTimer>

#include "../indexrange.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use base/indexrange.h and so on.

#include "../indexrange.h"
#include "../http/types.h"

class HttpSocket : public QObject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to put this class into separate files.


StreamFile *StreamingManager::findFile(int fileIndex, BitTorrent::TorrentHandle *torrentHandle) const
{
const auto fileIter =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong coding style.
Please use the following:

const auto fileIter = std::find_if(m_files.begin(), m_files.end()
    , [fileIndex, torrentHandle](const StreamFile *file)
{
    return file->isSameFile(fileIndex, torrentHandle);
});

m_files.remove(m_files.indexOf(file));
});
}
QProcess::startDetached(vlcPath(), {url(file)});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what is it for?

connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved, file, [this, torrentHandle, file](BitTorrent::TorrentHandle *handle)
{
if (handle == torrentHandle)
m_files.remove(m_files.indexOf(file));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it a memory leak?

@@ -47,6 +47,13 @@ namespace BitTorrent
class InfoHash;
class TrackerEntry;

struct PieceFileInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this structure describe? Index of what? Start of what? Length of what?

@jagannatharjun
Copy link
Member Author

I tried to start dealing with this PR, but there are so many Coding Style violations here that I'm starting to get bogged down in it... Could you get it ready for review first?

Thanks for taking a look, but this is not ready for review, I and my friends are actually using this in our builds for the last couple of months and I know of a couple of edge cases I have to deal with. I actually submit this work as my minor project this semester :), I will probably open a new PR when it's complete. I should be able to complete this by mid-January. Until then I'm leaving this open just in case if anyone wants to try it out.

@github-actions
Copy link

This PR is stale because it has been 60 days with no activity. This PR will be automatically closed within 7 days if there is no further activity.

@github-actions github-actions bot added the Stale label Aug 12, 2021
@github-actions
Copy link

This PR was closed because it has been stalled for some time with no activity.

@github-actions github-actions bot closed this Aug 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants