aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/Downloader.hpp29
-rw-r--r--include/Storage.hpp4
-rw-r--r--plugins/youtube/YoutubeMediaProxy.hpp75
-rw-r--r--src/Downloader.cpp279
-rw-r--r--src/QuickMedia.cpp81
-rw-r--r--src/Storage.cpp4
-rw-r--r--src/plugins/FileManager.cpp2
-rw-r--r--src/plugins/youtube/YoutubeMediaProxy.cpp623
-rw-r--r--video_player/src/main.cpp253
9 files changed, 488 insertions, 862 deletions
diff --git a/include/Downloader.hpp b/include/Downloader.hpp
index cc15e5d..cb70f2e 100644
--- a/include/Downloader.hpp
+++ b/include/Downloader.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include "../plugins/youtube/YoutubeMediaProxy.hpp"
#include "Path.hpp"
#include "AsyncTask.hpp"
#include <string>
@@ -46,7 +45,7 @@ namespace QuickMedia {
AsyncTask<bool> header_reader;
std::string header;
int64_t content_length = -1;
- size_t downloaded_since_last_check = 0;
+ int64_t downloaded_since_last_check = 0;
float progress = 0.0f;
std::mutex content_length_mutex;
std::string progress_text;
@@ -80,9 +79,26 @@ namespace QuickMedia {
int64_t content_length;
};
+ struct YoutubeReadProgram {
+ ReadProgram read_program;
+ int64_t offset = 0;
+ int64_t content_length = -1;
+ int64_t bytes_downloaded = 0;
+ int64_t downloaded_since_last_check = 0;
+ char *url = nullptr;
+ std::string output_filepath;
+ std::string output_filepath_tmp;
+ double progress = 0.0;
+ std::string progress_text;
+ std::string download_speed_text;
+ bool finished = false;
+ };
+
class YoutubeDownloader : public Downloader {
public:
YoutubeDownloader(const MediaMetadata &video_metadata, const MediaMetadata &audio_metadata, const std::string &output_filepath);
+ ~YoutubeDownloader();
+
bool start() override;
bool stop(bool download_completed) override;
DownloadUpdateStatus update() override;
@@ -90,15 +106,14 @@ namespace QuickMedia {
std::string get_progress_text() override;
std::string get_download_speed_text() override;
private:
- CurlDownloader* get_min_progress_downloader();
+ YoutubeReadProgram* get_min_progress_downloader();
+ DownloadUpdateStatus update(size_t program_index);
private:
MediaMetadata video_metadata;
MediaMetadata audio_metadata;
std::string video_output_filepath;
std::string audio_output_filepath;
- std::unique_ptr<CurlDownloader> downloaders[2];
- AsyncTask<void> downloader_task;
- std::unique_ptr<YoutubeMediaProxy> youtube_video_media_proxy;
- std::unique_ptr<YoutubeMediaProxy> youtube_audio_media_proxy;
+ YoutubeReadProgram *program[2];
+ AsyncTask<bool> downloader_tasks[2];
};
} \ No newline at end of file
diff --git a/include/Storage.hpp b/include/Storage.hpp
index c187261..19dd345 100644
--- a/include/Storage.hpp
+++ b/include/Storage.hpp
@@ -30,7 +30,7 @@ namespace QuickMedia {
int create_directory_recursive(const Path &path);
FileType get_file_type(const Path &path);
int file_get_content(const Path &path, std::string &result);
- int file_get_size(const Path &path, size_t *size);
+ int file_get_size(const Path &path, int64_t *size);
bool file_get_last_modified_time_seconds(const char *path, time_t *result);
int file_overwrite(const Path &path, const std::string &data);
int file_overwrite_atomic(const Path &path, const std::string &data);
@@ -46,5 +46,5 @@ namespace QuickMedia {
bool is_program_executable_by_name(const char *name);
- std::string file_size_to_human_readable_string(size_t bytes);
+ std::string file_size_to_human_readable_string(int64_t bytes);
} \ No newline at end of file
diff --git a/plugins/youtube/YoutubeMediaProxy.hpp b/plugins/youtube/YoutubeMediaProxy.hpp
deleted file mode 100644
index 0fb2d6a..0000000
--- a/plugins/youtube/YoutubeMediaProxy.hpp
+++ /dev/null
@@ -1,75 +0,0 @@
-#pragma once
-
-#include "../../include/Program.hpp"
-#include <string>
-#include <functional>
-
-// TODO: Sync sequence for video and audio (for live stream).
-
-namespace QuickMedia {
- class YoutubeMediaProxy {
- public:
- enum Error {
- OK,
- ERROR
- };
-
- virtual ~YoutubeMediaProxy() = default;
-
- virtual bool start(const std::string &youtube_media_url, int64_t content_length) = 0;
- // This should be the last call. Do not call |start| after this. TODO: Allow restarting with |start| after |stop| is called.
- virtual void stop() = 0;
- virtual Error update() = 0;
- virtual bool get_address(std::string &address) = 0;
-
- bool start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, int64_t end_range, bool include_header);
- };
-
- class YoutubeStaticMediaProxy : public YoutubeMediaProxy {
- public:
- YoutubeStaticMediaProxy();
- YoutubeStaticMediaProxy(YoutubeStaticMediaProxy&) = delete;
- YoutubeStaticMediaProxy&operator=(YoutubeStaticMediaProxy&) = delete;
- ~YoutubeStaticMediaProxy();
-
- bool start(const std::string &youtube_media_url, int64_t content_length) override;
- void stop() override;
- Error update() override;
- bool get_address(std::string &address) override;
- private:
- void on_client_disconnect();
- Error read_client_data();
- void clear_download_state();
- // If |new_range_start| is not -1 then the start range is set to that
- Error update_download_program_status(bool client_disconnected, int64_t new_range_start = -1, bool restart_download = false);
- Error continue_send(const char *buffer_start, size_t total_bytes_to_write, int &buffer_offset);
- Error handle_download();
- // Returns the client fd or -1
- int accept_client();
- private:
- int socket_fd = -1;
- int port = 0;
- int client_fd = -1;
- std::string youtube_media_url;
- int64_t content_length = 0;
- ReadProgram downloader_read_program;
- int download_read_buffer_offset = 0;
- ssize_t downloader_num_read_bytes = 0;
- int64_t download_range_start = 0;
- int64_t current_download_range = 0;
- int64_t next_range_length = 0;
- std::string download_header;
- bool end_of_file = false;
- bool download_header_finished = false;
- bool download_header_sent = false;
- bool download_header_remaining_sent = false;
- int download_header_written_offset = 0;
- int download_header_offset_to_end_of_header = 0;
- int64_t download_start_time = 0;
- int64_t total_downloaded_bytes = 0;
- bool download_started = false;
- bool client_request_finished = false;
- std::string client_request_buffer;
- char download_read_buffer[16384];
- };
-} \ No newline at end of file
diff --git a/src/Downloader.cpp b/src/Downloader.cpp
index 89d83cb..24f670d 100644
--- a/src/Downloader.cpp
+++ b/src/Downloader.cpp
@@ -4,6 +4,8 @@
#include "../include/Notification.hpp"
#include <unistd.h>
#include <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
namespace QuickMedia {
static bool youtube_url_is_live_stream(const std::string &url) {
@@ -112,9 +114,9 @@ namespace QuickMedia {
}
std::lock_guard<std::mutex> lock(content_length_mutex);
- size_t output_file_size = 0;
+ int64_t output_file_size = 0;
file_get_size(output_filepath_tmp, &output_file_size);
- size_t downloaded_size = std::min((int64_t)output_file_size, content_length);
+ int64_t downloaded_size = std::min((int64_t)output_file_size, content_length);
if(content_length == -1) {
progress_text = std::to_string(output_file_size / 1024) + "/Unknown";
@@ -127,7 +129,7 @@ namespace QuickMedia {
}
// TODO: Take into consideration time overflow?
- size_t downloaded_diff = std::max(0lu, downloaded_size - downloaded_since_last_check);
+ int64_t downloaded_diff = std::max((int64_t)0, downloaded_size - downloaded_since_last_check);
download_speed_text = file_size_to_human_readable_string(downloaded_diff) + "/s";
downloaded_since_last_check = downloaded_size;
@@ -279,79 +281,178 @@ namespace QuickMedia {
return download_speed_text;
}
+ static int64_t seek_fn(YoutubeReadProgram *program, int64_t offset) {
+ if(program->read_program.pid != -1) {
+ kill(program->read_program.pid, SIGTERM);
+ int status;
+ waitpid(program->read_program.pid, &status, 0);
+ program->read_program.pid = -1;
+ }
+
+ if(program->read_program.read_fd != -1) {
+ close(program->read_program.read_fd);
+ program->read_program.read_fd = -1;
+ }
+
+ program->offset = offset;
+
+ char range[64];
+ snprintf(range, sizeof(range), "%lld-%lld", (long long)program->offset, (long long)program->offset + 5242870LL);
+ const char *args[] = { "curl",
+ "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
+ "--no-buffer",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-g", "-s", "-L", "-f",
+ "-r", range, "--", program->url, nullptr };
+ int res = exec_program_pipe(args, &program->read_program);
+ if(res != 0)
+ return -1;
+
+ int flags = fcntl(program->read_program.read_fd, F_GETFL, 0);
+ if(flags != -1)
+ fcntl(program->read_program.read_fd, F_SETFL, flags | O_NONBLOCK);
+
+ return offset;
+ }
+
+ static int64_t read_fn(YoutubeReadProgram *program, char *buf, uint64_t nbytes) {
+ ssize_t bytes_read = read(program->read_program.read_fd, buf, nbytes);
+ if(bytes_read > 0) {
+ program->offset += bytes_read;
+ program->bytes_downloaded += bytes_read;
+ } else if(bytes_read == 0) {
+ // End of current range, progress to next range
+ bytes_read = seek_fn(program, program->offset);
+ if(bytes_read >= 0) {
+ bytes_read = read(program->read_program.read_fd, buf, nbytes);
+ if(bytes_read > 0) {
+ program->offset += bytes_read;
+ program->bytes_downloaded += bytes_read;
+ }
+ }
+ } else if(bytes_read < 0) {
+ if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
+ return 0;
+ }
+ return bytes_read;
+ }
+
+ static void close_fn(YoutubeReadProgram *program) {
+ if(program->read_program.pid != -1) {
+ kill(program->read_program.pid, SIGTERM);
+ int status;
+ waitpid(program->read_program.pid, &status, 0);
+ program->read_program.pid = -1;
+ }
+
+ if(program->read_program.read_fd != -1) {
+ close(program->read_program.read_fd);
+ program->read_program.read_fd = -1;
+ }
+
+ if(program->url) {
+ free(program->url);
+ program->url = nullptr;
+ }
+ }
+
+ static YoutubeReadProgram* open_fn(const char *uri) {
+ YoutubeReadProgram *program = new YoutubeReadProgram();
+ program->read_program.read_fd = -1;
+ program->read_program.pid = -1;
+ program->url = strdup(uri);
+ program->offset = 0;
+ if(seek_fn(program, program->offset) < 0) {
+ delete program;
+ return nullptr;
+ }
+ return program;
+ }
+
YoutubeDownloader::YoutubeDownloader(const MediaMetadata &video_metadata, const MediaMetadata &audio_metadata, const std::string &output_filepath) :
Downloader("", output_filepath),
video_metadata(video_metadata),
audio_metadata(audio_metadata)
{
for(int i = 0; i < 2; ++i)
- downloaders[i] = nullptr;
+ program[i] = nullptr;
+ }
+
+ YoutubeDownloader::~YoutubeDownloader() {
+ for(int i = 0; i < 2; ++i) {
+ if(program[i]) {
+ close_fn(program[i]);
+ delete program[i];
+ program[i] = nullptr;
+ }
+ }
}
bool YoutubeDownloader::start() {
- struct MediaProxyMetadata {
- std::unique_ptr<YoutubeMediaProxy> *media_proxy;
+ struct MediaMetadataInfo {
MediaMetadata *media_metadata;
- std::unique_ptr<CurlDownloader> *downloader;
std::string *output_filepath;
bool tmp_audio_file;
};
const bool has_video = !video_metadata.url.empty();
- MediaProxyMetadata media_proxies[2] = {
- { &youtube_video_media_proxy, &video_metadata, &downloaders[0], &video_output_filepath, false },
- { &youtube_audio_media_proxy, &audio_metadata, &downloaders[1], &audio_output_filepath, has_video }
+ MediaMetadataInfo media_info[2] = {
+ { &video_metadata, &video_output_filepath, false },
+ { &audio_metadata, &audio_output_filepath, has_video }
};
- int num_proxied_media = 0;
+ int num_streams = 0;
for(int i = 0; i < 2; ++i) {
- media_proxies[i].output_filepath->clear();
- if(media_proxies[i].media_metadata->url.empty() || youtube_url_is_live_stream(media_proxies[i].media_metadata->url))
+ media_info[i].output_filepath->clear();
+ if(media_info[i].media_metadata->url.empty() || youtube_url_is_live_stream(media_info[i].media_metadata->url))
continue;
- *media_proxies[i].media_proxy = std::make_unique<YoutubeStaticMediaProxy>();
- if(!(*media_proxies[i].media_proxy)->start(media_proxies[i].media_metadata->url, media_proxies[i].media_metadata->content_length)) {
- fprintf(stderr, "Failed to load start youtube media proxy\n");
- return false;
- }
+ if(media_info[i].tmp_audio_file)
+ *media_info[i].output_filepath = output_filepath + ".audio";
+ else
+ *media_info[i].output_filepath = output_filepath;
- std::string media_proxy_addr;
- if(!(*media_proxies[i].media_proxy)->get_address(media_proxy_addr)) {
- fprintf(stderr, "Failed to load start youtube media proxy\n");
+ program[i] = open_fn(media_info[i].media_metadata->url.c_str());
+ if(!program[i]) {
+ fprintf(stderr, "Error: open failed for %s\n", media_info[i].media_metadata->url.c_str());
return false;
}
+ program[i]->content_length = media_info[i].media_metadata->content_length;
+ program[i]->output_filepath = *media_info[i].output_filepath;
+ program[i]->output_filepath_tmp = program[i]->output_filepath + ".tmp";
- if(media_proxies[i].tmp_audio_file)
- *media_proxies[i].output_filepath = output_filepath + ".audio";
- else
- *media_proxies[i].output_filepath = output_filepath;
+ program[i]->progress_text = "0 bytes/Unknown";
+ program[i]->download_speed_text = "Unknown/s";
- remove(media_proxies[i].output_filepath->c_str());
- fprintf(stderr, "Downloading %s to %s\n", media_proxy_addr.c_str(), media_proxies[i].output_filepath->c_str());
- *media_proxies[i].downloader = std::make_unique<CurlDownloader>(media_proxy_addr, *media_proxies[i].output_filepath, media_proxies[i].media_metadata->content_length);
- *media_proxies[i].output_filepath = (*media_proxies[i].downloader)->get_output_filepath();
- if(!(*media_proxies[i].downloader)->start())
- return false;
- ++num_proxied_media;
- }
+ remove(program[i]->output_filepath.c_str());
+ remove(program[i]->output_filepath_tmp.c_str());
- if(num_proxied_media == 0) {
- return false;
- }
+ downloader_tasks[i] = AsyncTask<bool>([this, i]() {
+ FILE *f = fopen(program[i]->output_filepath_tmp.c_str(), "wb");
+ if(!f)
+ return false;
+
+ char buffer[8192];
+ while(!program_is_dead_in_current_thread()) {
+ if(program[i]->offset >= program[i]->content_length)
+ break;
- downloader_task = AsyncTask<void>([this]() {
- // TODO: Poll instead of sleep
- while(!program_is_dead_in_current_thread()) {
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->update();
+ int64_t bytes_read = read_fn(program[i], buffer, sizeof(buffer));
+ if(bytes_read > 0)
+ fwrite(buffer, 1, bytes_read, f);
+ }
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->update();
+ fclose(f);
+ close_fn(program[i]);
+ return true;
+ });
- usleep(1000);
- }
- });
+ ++num_streams;
+ }
+
+ if(num_streams == 0)
+ return false;
return true;
}
@@ -362,27 +463,22 @@ namespace QuickMedia {
}
bool YoutubeDownloader::stop(bool download_completed) {
+ bool success = true;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
- downloaders[i]->stop(download_completed);
- downloaders[i].reset();
+ if(program[i]) {
+ downloader_tasks[i].cancel();
+ if(downloader_tasks[i].ready() && !downloader_tasks[i].get())
+ success = false;
+ if(rename_atomic(program[i]->output_filepath_tmp.c_str(), program[i]->output_filepath.c_str()) != 0)
+ success = false;
+ close_fn(program[i]);
}
}
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->stop();
-
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->stop();
-
- downloader_task.cancel();
- youtube_video_media_proxy.reset();
- youtube_audio_media_proxy.reset();
-
- if(!download_completed)
+ if(!download_completed || !success)
return false;
- bool success = true;
+ success = true;
if(!video_metadata.url.empty() && !audio_metadata.url.empty()) {
std::string tmp_file = video_output_filepath + ".tmp.mkv";
if(ffmpeg_merge_audio_and_video(video_output_filepath, audio_output_filepath, tmp_file)) {
@@ -406,9 +502,9 @@ namespace QuickMedia {
int num_downloaders = 0;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
+ if(program[i]) {
++num_downloaders;
- DownloadUpdateStatus update_status = downloaders[i]->update();
+ DownloadUpdateStatus update_status = update(i);
switch(update_status) {
case DownloadUpdateStatus::DOWNLOADING:
++num_downloading;
@@ -432,33 +528,68 @@ namespace QuickMedia {
}
float YoutubeDownloader::get_progress() {
- return get_min_progress_downloader()->get_progress();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->progress;
}
std::string YoutubeDownloader::get_progress_text() {
- return get_min_progress_downloader()->get_progress_text();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->progress_text;
}
std::string YoutubeDownloader::get_download_speed_text() {
- return get_min_progress_downloader()->get_download_speed_text();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->download_speed_text;
}
- CurlDownloader* YoutubeDownloader::get_min_progress_downloader() {
+ YoutubeReadProgram* YoutubeDownloader::get_min_progress_downloader() {
int min_index = -1;
float progress = 999999.0f;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
- float item_progress = downloaders[i]->get_progress();
- if(item_progress < progress) {
+ if(program[i]) {
+ if(program[i]->progress < progress) {
min_index = i;
- progress = item_progress;
+ progress = program[i]->progress;
}
}
}
- //assert(min_index != -1);
- // Shouldn't happen
if(min_index == -1)
min_index = 0;
- return downloaders[min_index].get();
+ return program[min_index];
+ }
+
+ DownloadUpdateStatus YoutubeDownloader::update(size_t program_index) {
+ bool finished = false;
+ YoutubeReadProgram *youtube_program = program[program_index];
+ auto &downloader_task = downloader_tasks[program_index];
+
+ if(youtube_program->finished)
+ return DownloadUpdateStatus::FINISHED;
+
+ if(downloader_task.ready()) {
+ if(downloader_task.get()) {
+ finished = true;
+ youtube_program->finished = true;
+ } else {
+ return DownloadUpdateStatus::ERROR;
+ }
+ }
+
+ int64_t output_file_size = 0;
+ file_get_size(youtube_program->output_filepath_tmp, &output_file_size);
+ int64_t downloaded_size = std::min((int64_t)output_file_size, youtube_program->content_length);
+
+ int64_t percentage = 0;
+ if(output_file_size > 0)
+ percentage = (double)downloaded_size / (double)youtube_program->content_length * 100.0;
+ youtube_program->progress = (double)percentage / 100.0;
+ youtube_program->progress_text = file_size_to_human_readable_string(downloaded_size) + "/" + file_size_to_human_readable_string(youtube_program->content_length) + " (" + std::to_string(percentage) + "%)";
+
+ // TODO: Take into consideration time overflow?
+ int64_t downloaded_diff = std::max((int64_t)0, downloaded_size - youtube_program->downloaded_since_last_check);
+ youtube_program->download_speed_text = file_size_to_human_readable_string(downloaded_diff) + "/s";
+ youtube_program->downloaded_since_last_check = downloaded_size;
+
+ return finished ? DownloadUpdateStatus::FINISHED : DownloadUpdateStatus::DOWNLOADING;
}
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 49bfd36..4242fa1 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -40,7 +40,6 @@
#include "../include/Storage.hpp"
#include "../include/AsyncImageLoader.hpp"
#include <mglpp/system/FloatRect.hpp>
-#include "../plugins/youtube/YoutubeMediaProxy.hpp"
#include "../include/gui/Button.hpp"
#include "../external/hash-library/sha256.h"
@@ -3090,9 +3089,6 @@ namespace QuickMedia {
update_window_focus = true;
};
- std::unique_ptr<YoutubeMediaProxy> youtube_video_media_proxy;
- std::unique_ptr<YoutubeMediaProxy> youtube_audio_media_proxy;
- AsyncTask<void> youtube_downloader_task;
int64_t youtube_video_content_length = 0;
int64_t youtube_audio_content_length = 0;
@@ -3211,65 +3207,10 @@ namespace QuickMedia {
std::string v = video_url;
std::string a = audio_url;
if(is_youtube) {
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->stop();
-
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->stop();
-
- youtube_downloader_task.cancel();
- youtube_video_media_proxy.reset();
- youtube_audio_media_proxy.reset();
-
- struct MediaProxyMetadata {
- std::unique_ptr<YoutubeMediaProxy> *media_proxy;
- std::string *url;
- int64_t content_length;
- };
-
- MediaProxyMetadata media_proxies[2] = {
- { &youtube_video_media_proxy, &v, youtube_video_content_length },
- { &youtube_audio_media_proxy, &a, youtube_audio_content_length }
- };
- int num_proxied_media = 0;
- for(int i = 0; i < 2; ++i) {
- if(media_proxies[i].url->empty() || youtube_url_is_live_stream(*media_proxies[i].url))
- continue;
-
- *media_proxies[i].media_proxy = std::make_unique<YoutubeStaticMediaProxy>();
- if(!(*media_proxies[i].media_proxy)->start(*media_proxies[i].url, media_proxies[i].content_length)) {
- show_notification("QuickMedia", "Failed to load start youtube media proxy", Urgency::CRITICAL);
- current_page = previous_page;
- go_to_previous_page = true;
- return;
- }
-
- std::string media_proxy_addr;
- if(!(*media_proxies[i].media_proxy)->get_address(media_proxy_addr)) {
- show_notification("QuickMedia", "Failed to load start youtube media proxy", Urgency::CRITICAL);
- current_page = previous_page;
- go_to_previous_page = true;
- return;
- }
-
- *media_proxies[i].url = std::move(media_proxy_addr);
- ++num_proxied_media;
- }
-
- if(num_proxied_media > 0) {
- youtube_downloader_task = AsyncTask<void>([&youtube_video_media_proxy, &youtube_audio_media_proxy]() {
- // TODO: Poll instead of sleep
- while(!program_is_dead_in_current_thread()) {
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->update();
-
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->update();
-
- usleep(1000);
- }
- });
- }
+ if(!v.empty() && !youtube_url_is_live_stream(v))
+ v = "qm-yt://" + v;
+ if(!a.empty() && !youtube_url_is_live_stream(a))
+ a = "qm-yt://" + a;
}
VideoPlayer::StartupArgs startup_args;
@@ -3499,16 +3440,6 @@ namespace QuickMedia {
video_player->get_time_in_file(&resume_start_time);
video_player.reset();
page_changed = true;
-
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->stop();
-
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->stop();
-
- youtube_downloader_task.cancel();
- youtube_video_media_proxy.reset();
- youtube_audio_media_proxy.reset();
}
});
@@ -3866,7 +3797,7 @@ namespace QuickMedia {
return true;
}
} else {
- size_t file_size = 0;
+ int64_t file_size = 0;
if(download_to_file(url, image_filepath_tmp.data, extra_args, true, cloudflare_bypass) != DownloadResult::OK || (is_manganelo && file_get_size(image_filepath_tmp, &file_size) == 0 && file_size < 255)) {
if(!image_download_cancel) show_notification("QuickMedia", "Failed to download image: " + url, Urgency::CRITICAL);
return true;
@@ -7763,7 +7694,7 @@ namespace QuickMedia {
}
const float progress_diff = progress - ui_progress;
- const float progress_move = frame_timer.get_elapsed_time_seconds() * 500.0f * std::abs(progress_diff);
+ const float progress_move = frame_timer.get_elapsed_time_seconds() * 2500.0f * std::abs(progress_diff);
if(std::abs(progress_diff) < progress_move) {
ui_progress = progress;
} else {
diff --git a/src/Storage.cpp b/src/Storage.cpp
index effa70b..7554b73 100644
--- a/src/Storage.cpp
+++ b/src/Storage.cpp
@@ -190,7 +190,7 @@ namespace QuickMedia {
return 0;
}
- int file_get_size(const Path &path, size_t *size) {
+ int file_get_size(const Path &path, int64_t *size) {
struct stat file_stat;
memset(&file_stat, 0, sizeof(file_stat));
int ret;
@@ -399,7 +399,7 @@ namespace QuickMedia {
return false;
}
- std::string file_size_to_human_readable_string(size_t bytes) {
+ std::string file_size_to_human_readable_string(int64_t bytes) {
double kb = (double)bytes / 1024.0;
double mb = (double)bytes / 1024.0 / 1024.0;
double gb = (double)bytes / 1024.0 / 1024.0 / 1024.0;
diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp
index eb4a3ef..5fd6670 100644
--- a/src/plugins/FileManager.cpp
+++ b/src/plugins/FileManager.cpp
@@ -128,7 +128,7 @@ namespace QuickMedia {
strftime(time_str, sizeof(time_str) - 1, "%Y %b %d, %a %H:%M", &last_modified_tm);
std::error_code file_size_err;
- size_t file_size = p.first.file_size(file_size_err);
+ int64_t file_size = p.first.file_size(file_size_err);
if(file_size_err)
file_size = 0;
diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp
deleted file mode 100644
index e51e3d9..0000000
--- a/src/plugins/youtube/YoutubeMediaProxy.cpp
+++ /dev/null
@@ -1,623 +0,0 @@
-#include "../../../plugins/youtube/YoutubeMediaProxy.hpp"
-#include "../../../include/NetUtils.hpp"
-#include "../../../include/Utils.hpp"
-
-#include <vector>
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <signal.h>
-#include <inttypes.h>
-#include <assert.h>
-
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <fcntl.h>
-
-// TODO: What if the client sends a new header without reconnecting? is that even allowed by http standard?
-// TODO: Detect when download has finished (and close connection).
-
-static ssize_t read_eintr(int fd, void *buffer, size_t size) {
- while(true) {
- ssize_t bytes_read = read(fd, buffer, size);
- if(bytes_read == -1) {
- if(errno != EINTR)
- return -1;
- } else {
- return bytes_read;
- }
- }
-}
-
-static ssize_t write_all(int fd, const void *buffer, size_t size) {
- ssize_t bytes_written = 0;
- while((size_t)bytes_written < size) {
- ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written);
- if(written == -1) {
- if(errno != EINTR)
- return -1;
- } else {
- bytes_written += written;
- }
- }
- return bytes_written;
-}
-
-static ssize_t write_all_blocking(int fd, const void *buffer, size_t size) {
- ssize_t bytes_written = 0;
- while((size_t)bytes_written < size) {
- ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written);
- if(written == -1) {
- if(errno != EINTR && errno != EWOULDBLOCK)
- return -1;
- } else {
- bytes_written += written;
- }
- }
- return bytes_written;
-}
-
-namespace QuickMedia {
- static const int MAX_BUFFER_SIZE = 65536;
- static const int64_t RANGE = 5242870;
- static const char download_error_response_msg[] =
- "HTTP/1.1 500 Internal Server Error\r\n"
- "Content-Length: 0\r\n\r\n";
- static const char download_finished_response_msg[] =
- "HTTP/1.1 200 OK\r\n"
- "Content-Length: 0\r\n\r\n";
-
- static bool set_non_blocking(int fd) {
- const int flags = fcntl(fd, F_GETFL, 0);
- if(fd == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
- return false;
- return true;
- }
-
- // TODO: Restrict range end to remote file size (content-length which we have saved).
- // TODO: Check if custom youtube redirect code is needed
- bool YoutubeMediaProxy::start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, int64_t end_range, bool include_header) {
- std::string r = std::to_string(range_start) + "-" + std::to_string(end_range);
-
- std::string url = media_url;;
- std::vector<const char*> args = { "curl",
- "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
- "--no-buffer",
- "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
- "-g", "-s", "-L", "-f", "-r", r.c_str() };
-
- if(include_header)
- args.push_back("-i");
-
- //fprintf(stderr, "url: %s\n", url.c_str());
-
- args.insert(args.end(), { "--", url.c_str(), nullptr });
-
- if(exec_program_pipe(args.data(), &read_program) != 0)
- return false;
-
- if(!set_non_blocking(read_program.read_fd)) {
- perror("start_download: failed to set curl pipe non blocking mode");
- close(read_program.read_fd);
- kill(read_program.pid, SIGTERM);
- read_program.read_fd = -1;
- read_program.pid = -1;
- return false;
- }
-
- return true;
- }
-
- YoutubeStaticMediaProxy::YoutubeStaticMediaProxy() {
-
- }
-
- YoutubeStaticMediaProxy::~YoutubeStaticMediaProxy() {
- stop();
- }
-
- bool YoutubeStaticMediaProxy::start(const std::string &youtube_media_url, int64_t content_length) {
- if(socket_fd != -1)
- return false;
-
- socket_fd = socket(AF_INET, SOCK_STREAM, 0);
- if(socket_fd == -1) {
- perror("YoutubeStaticMediaProxy::start: socket failed");
- return false;
- }
-
- socklen_t response_sock_addr_len;
- struct sockaddr_in server_addr;
- memset(&server_addr, 0, sizeof(server_addr));
- server_addr.sin_family = AF_INET;
- server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- server_addr.sin_port = htons(0);
-
- next_range_length = RANGE;
-
- if(bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
- perror("YoutubeStaticMediaProxy::start: bind failed");
- goto err;
- }
-
- if(listen(socket_fd, 2) == -1) {
- perror("YoutubeStaticMediaProxy::start: listen failed");
- goto err;
- }
-
- if(!set_non_blocking(socket_fd)) {
- perror("YoutubeStaticMediaProxy::start: failed to set socket non blocking mode");
- goto err;
- }
-
- struct sockaddr_in response_sock_addr;
- response_sock_addr_len = sizeof(response_sock_addr);
- if(getsockname(socket_fd, (struct sockaddr*)&response_sock_addr, &response_sock_addr_len) == -1) {
- perror("YoutubeStaticMediaProxy::start: getsockname failed");
- goto err;
- }
-
- port = ntohs(response_sock_addr.sin_port);
- this->youtube_media_url = youtube_media_url;
- this->content_length = content_length;
- return true;
-
- err:
- if(downloader_read_program.read_fd != -1)
- close(downloader_read_program.read_fd);
- if(downloader_read_program.pid != -1)
- kill(downloader_read_program.pid, SIGTERM);
- close(socket_fd);
- socket_fd = -1;
- return false;
- }
-
- void YoutubeStaticMediaProxy::stop() {
- if(downloader_read_program.read_fd != -1) {
- close(downloader_read_program.read_fd);
- downloader_read_program.read_fd = -1;
- }
-
- if(downloader_read_program.pid != -1) {
- kill(downloader_read_program.pid, SIGTERM);
- wait_program(downloader_read_program.pid);
- downloader_read_program.pid = -1;
- }
-
- if(client_fd != -1) {
- close(client_fd);
- client_fd = -1;
- }
-
- if(socket_fd != -1) {
- close(socket_fd);
- socket_fd = -1;
- }
-
- clear_download_state();
- client_request_buffer.clear();
- client_request_finished = false;
- }
-
- YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::update() {
- if(socket_fd == -1)
- return Error::OK;
-
- if(client_fd == -1) {
- client_fd = accept_client();
- if(client_fd == -1)
- return Error::OK;
- } else {
- const int new_client_fd = accept_client();
- if(new_client_fd != -1) {
- on_client_disconnect();
- client_fd = new_client_fd;
- }
- }
-
- Error err = read_client_data();
- if(err != Error::OK || !client_request_finished)
- return err;
- if(downloader_read_program.pid == -1)
- return Error::ERROR;
- return handle_download();
- }
-
- bool YoutubeStaticMediaProxy::get_address(std::string &address) {
- if(socket_fd == -1)
- return false;
-
- address = "http://127.0.0.1:" + std::to_string(port);
- return true;
- }
-
- void YoutubeStaticMediaProxy::on_client_disconnect() {
- client_request_buffer.clear();
- client_request_finished = false;
- download_started = false;
-
- if(client_fd != -1) {
- close(client_fd);
- client_fd = -1;
- }
-
- if(downloader_read_program.pid != -1)
- kill(downloader_read_program.pid, SIGTERM);
-
- update_download_program_status(true);
- }
-
- // Returns 0 if start range is not found
- static int64_t header_extract_start_range(const std::string &header) {
- std::string range = header_extract_value(header, "range");
- if(range.empty())
- return 0;
-
- int64_t start_range = 0;
- if(sscanf(range.c_str(), " bytes=%" PRId64, &start_range) != 1)
- return 0;
-
- return start_range;
- }
-
- static int64_t header_extract_content_length(const std::string &header) {
- std::string content_length_str = header_extract_value(header, "content-length");
- if(content_length_str.empty())
- return 0;
-
- errno = 0;
- char *endptr;
- int64_t content_length = strtoll(content_length_str.c_str(), &endptr, 10);
- if(endptr == content_length_str.c_str() || errno != 0)
- return 0;
-
- return content_length;
- }
-
- // TODO: What about hls (live streams)? need to test with that. There may not be a need to use YoutubeMediaProxy for that case (in that case document it).
- YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::read_client_data() {
- if(client_request_finished)
- return Error::OK;
-
- char read_buffer[4096];
- const ssize_t num_bytes_read = read_eintr(client_fd, read_buffer, sizeof(read_buffer));
- if(num_bytes_read == -1) {
- const int err = errno;
- if(err == EAGAIN || err == EWOULDBLOCK) {
- return Error::OK;
- } else if(err == EPIPE || err == ECONNRESET) {
- //fprintf(stderr, "YoutubeStaticMediaProxy::read_client_data: client disconnected\n");
- on_client_disconnect();
- return Error::ERROR;
- } else {
- perror("YoutubeStaticMediaProxy::read_client_data: read failed");
- return Error::ERROR;
- }
- } else if(num_bytes_read == 0) {
- //fprintf(stderr, "YoutubeStaticMediaProxy::read_client_data: client disconnected\n");
- on_client_disconnect();
- return Error::ERROR;
- }
-
- client_request_buffer.append(read_buffer, num_bytes_read);
- const size_t header_end = client_request_buffer.find("\r\n\r\n");
- if(header_end != std::string::npos) {
- client_request_buffer.erase(header_end + 4);
- client_request_finished = true;
- download_started = false;
- download_read_buffer_offset = 0;
-
- const int64_t new_start_range = header_extract_start_range(client_request_buffer);
- if(downloader_read_program.pid != -1) {
- kill(downloader_read_program.pid, SIGTERM);
- wait_program(downloader_read_program.pid);
- downloader_read_program.pid = -1;
- }
- clear_download_state();
- return update_download_program_status(false, new_start_range, true);
- } else {
- if(client_request_buffer.size() > MAX_BUFFER_SIZE) {
- client_request_finished = true;
- fprintf(stderr, "YoutubeStaticMediaProxy::read_client_data: buffer is full (malicious client?)\n");
- return Error::ERROR;
- }
- }
- return Error::OK;
- }
-
- void YoutubeStaticMediaProxy::clear_download_state() {
- download_header.clear();
- download_header_finished = false;
- download_header_sent = false;
- download_header_remaining_sent = false;
- download_header_written_offset = 0;
- download_read_buffer_offset = 0;
- next_range_length = RANGE;
- end_of_file = false;
- }
-
- YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::update_download_program_status(bool client_disconnected, int64_t new_range_start, bool restart_download) {
- int program_status = 0;
- if(downloader_read_program.pid != -1) {
- if(client_disconnected) {
- wait_program(downloader_read_program.pid);
- } else {
- if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0 && program_status == 0)
- return Error::OK;
- }
- downloader_read_program.pid = -1;
- }
-
- if(downloader_read_program.read_fd != -1) {
- close(downloader_read_program.read_fd);
- downloader_read_program.read_fd = -1;
- }
-
- if(program_status != 0) {
- //fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: download failed, exit status: %d\n", program_status);
- if(client_fd != -1) {
- write_all_blocking(client_fd, download_finished_response_msg, sizeof(download_finished_response_msg) - 1);
- close(client_fd);
- client_fd = -1;
- client_request_buffer.clear();
- client_request_finished = false;
- }
- return Error::ERROR;
- }
-
- if(client_disconnected) {
- current_download_range = 0;
- } else {
- current_download_range += next_range_length;
- }
-
- if(new_range_start != -1) {
- download_range_start = new_range_start;
- current_download_range = download_range_start;
- }
-
- if(new_range_start == -1) {
- download_header_finished = true;
- download_header_sent = true;
- download_header_remaining_sent = true;
- } else {
- clear_download_state();
- }
-
- if(client_disconnected) {
- clear_download_state();
- return Error::ERROR;
- }
-
- if(!restart_download)
- return Error::OK;
-
- if(new_range_start == -1) {
- download_header_finished = true;
- download_header_sent = true;
- download_header_remaining_sent = true;
- }
-
- int64_t end_range = current_download_range + RANGE;
- if(end_range > content_length) {
- end_range = content_length;
- end_of_file = true;
- }
-
- const bool start_download_success = start_download(youtube_media_url, downloader_read_program, current_download_range, end_range, new_range_start != -1);
- if(!start_download_success) {
- fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: failed to start download\n");
- if(client_fd != -1) {
- write_all_blocking(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1);
- close(client_fd);
- client_fd = -1;
- client_request_buffer.clear();
- client_request_finished = false;
- }
-
- clear_download_state();
- return Error::ERROR;
- }
-
- return Error::OK;
- }
-
- static void header_replace_content_length(std::string &header, size_t header_size, int64_t new_content_length) {
- if(new_content_length < 0)
- new_content_length = 0;
-
- const char *content_length_p = strcasestr(header.c_str(), "content-length:");
- if(!content_length_p)
- return;
-
- const size_t content_length_start = (content_length_p + 15) - header.c_str();
- if(content_length_start >= header_size)
- return;
- const size_t line_end = header.find("\r\n", content_length_start);
- header.replace(content_length_start, line_end - content_length_start, std::to_string(new_content_length));
- }
-
- YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::continue_send(const char *buffer_start, size_t total_bytes_to_write, int &buffer_offset) {
- int num_bytes_to_write = total_bytes_to_write - buffer_offset;
- //assert(num_bytes_to_write >= 0);
- if(num_bytes_to_write < 0) num_bytes_to_write = 0;
- if(num_bytes_to_write == 0) {
- buffer_offset = 0;
- return Error::OK;
- }
-
- const ssize_t num_bytes_written = write_all(client_fd, buffer_start + buffer_offset, num_bytes_to_write);
- if(num_bytes_written == -1) {
- const int err = errno;
- if(err == EAGAIN || err == EWOULDBLOCK) {
- return Error::OK;
- } else if(err == EPIPE || err == ECONNRESET) {
- //fprintf(stderr, "YoutubeStaticMediaProxy::continue_send: client disconnected\n");
- on_client_disconnect();
- return Error::ERROR;
- } else {
- perror("YoutubeStaticMediaProxy::continue_send: write failed");
- return Error::ERROR;
- }
- } else if(num_bytes_written == 0) {
- //fprintf(stderr, "YoutubeStaticMediaProxy::continue_send: client disconnected\n");
- on_client_disconnect();
- return Error::ERROR;
- } else if(num_bytes_written != num_bytes_to_write) {
- buffer_offset += num_bytes_written;
- } else {
- buffer_offset = 0;
- }
- return Error::OK;
- }
-
- static bool http_is_redirect(const char *header, size_t size) {
- const void *end_of_first_line_p = memmem(header, size, "\r\n", 2);
- if(!end_of_first_line_p)
- return false;
- return memmem(header, (const char*)end_of_first_line_p - header, " 30", 3) != nullptr;
- }
-
- static size_t find_start_of_first_non_redirect_header(const char *headers, size_t size, size_t &header_end) {
- const char *start = headers;
- while(size > 0) {
- const void *end_of_header = memmem(headers, size, "\r\n\r\n", 4);
- if(!end_of_header)
- return std::string::npos;
-
- const size_t offset_to_end_of_headers = ((const char*)end_of_header + 4) - headers;
- if(!http_is_redirect(headers, offset_to_end_of_headers)) {
- header_end = (headers - start) + offset_to_end_of_headers;
- return headers - start;
- }
-
- headers += offset_to_end_of_headers;
- size -= offset_to_end_of_headers;
- }
- return std::string::npos;
- }
-
- YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::handle_download() {
- // TODO: Maybe read even if write is being slow and failing?
- if(download_read_buffer_offset == 0) {
- downloader_num_read_bytes = read_eintr(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer));
- if(downloader_num_read_bytes == -1) {
- const int err = errno;
- if(err == EAGAIN || err == EWOULDBLOCK) {
- return Error::OK;
- } else {
- perror("YoutubeStaticMediaProxy::handle_download: curl read failed");
- return Error::ERROR;
- }
- } else if(downloader_num_read_bytes == 0) {
- if(end_of_file) {
- if(client_fd != -1) {
- write_all_blocking(client_fd, download_finished_response_msg, sizeof(download_finished_response_msg) - 1);
- close(client_fd);
- client_fd = -1;
- client_request_buffer.clear();
- client_request_finished = false;
- }
- return Error::OK;
- }
-
- Error err = update_download_program_status(false, -1, true);
- if(err != Error::OK)
- return err;
- } else {
- if(!download_started) {
- total_downloaded_bytes = 0;
- download_started = true;
- download_start_time = get_boottime_milliseconds();
- }
- total_downloaded_bytes += downloader_num_read_bytes;
- }
- }
-
- // TODO: Remove this code and instead create the header ourselves and send it to the client. Then pipe the curl output directly to the client input.
- if(!download_header_finished) {
- download_header.append(download_read_buffer, downloader_num_read_bytes);
- size_t header_end = std::string::npos;
- const size_t offset_to_start_of_header = find_start_of_first_non_redirect_header(download_header.c_str(), download_header.size(), header_end);
- if(header_end != std::string::npos) {
- download_header.erase(0, offset_to_start_of_header);
- header_end -= offset_to_start_of_header;
-
- download_header_finished = true;
- download_header_sent = false;
- download_header_remaining_sent = false;
- download_header_written_offset = 0;
- download_header_offset_to_end_of_header = header_end;
- download_read_buffer_offset = -1;
-
- next_range_length = header_extract_content_length(download_header.substr(0, header_end));
- header_replace_content_length(download_header, header_end, content_length - download_range_start);
- } else {
- if(download_header.size() > MAX_BUFFER_SIZE) {
- fprintf(stderr, "YoutubeStaticMediaProxy::handle_download: buffer is full (malicious server?)\n");
- if(downloader_read_program.pid != -1) {
- kill(downloader_read_program.pid, SIGTERM);
- wait_program(downloader_read_program.pid);
- downloader_read_program.pid = -1;
- }
- clear_download_state();
- update_download_program_status(false, 0, false);
- return Error::ERROR;
- }
- }
- }
-
- if(download_header_finished && !download_header_sent) {
- Error err = continue_send(download_header.data(), download_header_offset_to_end_of_header, download_header_written_offset);
- if(err != Error::OK)
- return err;
-
- if(download_header_written_offset == 0) {
- download_header_sent = true;
- download_header_written_offset = download_header_offset_to_end_of_header;
- }
- }
-
- if(download_header_finished && !download_header_remaining_sent) {
- Error err = continue_send(download_header.data(), download_header.size(), download_header_written_offset);
- if(err != Error::OK)
- return err;
-
- if(download_header_written_offset == 0) {
- download_header_remaining_sent = true;
- download_read_buffer_offset = 0;
- return Error::OK;
- }
- }
-
- if(download_header_remaining_sent)
- return continue_send(download_read_buffer, downloader_num_read_bytes, download_read_buffer_offset);
-
- return Error::OK;
- }
-
- int YoutubeStaticMediaProxy::accept_client() {
- struct sockaddr_in client_addr;
- socklen_t client_addr_len = sizeof(client_addr);
- int new_client_fd = accept(socket_fd, (struct sockaddr*)&client_addr, &client_addr_len);
- if(new_client_fd == -1) {
- const int err = errno;
- if(err == EAGAIN || err == EWOULDBLOCK) {
- return -1;
- } else {
- perror("YoutubeStaticMediaProxy::accept_client accept failed");
- return -1;
- }
- }
-
- if(!set_non_blocking(new_client_fd)) {
- perror("YoutubeStaticMediaProxy::accept_client: failed to set client socket non blocking mode");
- close(new_client_fd);
- return -1;
- }
-
- //fprintf(stderr, "YoutubeStaticMediaProxy::accept_client: client connected!\n");
- return new_client_fd;
- }
-}
diff --git a/video_player/src/main.cpp b/video_player/src/main.cpp
index 91f2d4b..42a9b4f 100644
--- a/video_player/src/main.cpp
+++ b/video_player/src/main.cpp
@@ -11,8 +11,13 @@
#include <mutex>
#include <optional>
#include <set>
+#include <algorithm>
#include <mpv/client.h>
+#include <mpv/stream_cb.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <sys/wait.h>
#include <json/json.h>
#define COMMAND_BUFFER_MAX_SIZE 2048
@@ -313,6 +318,247 @@ static void mpv_set_before_init_options(mpv_handle *mpv_ctx, const Args &args) {
}
}
+#define READ_END 0
+#define WRITE_END 1
+
+struct ReadProgram {
+ pid_t pid = -1;
+ int read_fd = -1;
+ int64_t offset = 0;
+ int64_t content_length = -1;
+ char *url = nullptr;
+};
+
+static int exec_program_pipe(const char **args, ReadProgram *read_program) {
+ read_program->pid = -1;
+ read_program->read_fd = -1;
+
+ /* 1 arguments */
+ if(args[0] == NULL)
+ return -1;
+
+ int fd[2];
+ if(pipe(fd) == -1) {
+ perror("Failed to open pipe");
+ return -2;
+ }
+
+ pid_t pid = vfork();
+ if(pid == -1) {
+ perror("Failed to vfork");
+ close(fd[READ_END]);
+ close(fd[WRITE_END]);
+ return -3;
+ } else if(pid == 0) { /* child */
+ dup2(fd[WRITE_END], STDOUT_FILENO);
+ close(fd[READ_END]);
+ close(fd[WRITE_END]);
+
+ execvp(args[0], (char* const*)args);
+ perror("execvp");
+ _exit(127);
+ } else { /* parent */
+ close(fd[WRITE_END]);
+ read_program->pid = pid;
+ read_program->read_fd = fd[READ_END];
+ return 0;
+ }
+}
+
+static char to_upper(char c) {
+ if(c >= 'a' && c <= 'z')
+ return c - 32;
+ else
+ return c;
+}
+
+size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len) {
+ if(substr_len == 0)
+ return 0;
+
+ auto it = std::search(str.begin() + start_index, str.end(), substr, substr + substr_len,
+ [](char c1, char c2) {
+ return to_upper(c1) == to_upper(c2);
+ });
+
+ if(it == str.end())
+ return std::string::npos;
+
+ return it - str.begin();
+}
+
+static int64_t size_fn(void *cookie) {
+ ReadProgram *program = (ReadProgram*)cookie;
+ if(program->content_length != -1)
+ return program->content_length;
+
+ const char *args[] = { "curl",
+ "-I",
+ "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
+ "--no-buffer",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-g", "-s", "-L", "-f",
+ "--", program->url, nullptr };
+
+ ReadProgram header_program;
+ int res = exec_program_pipe(args, &header_program);
+ if(res != 0)
+ return MPV_ERROR_UNSUPPORTED;
+
+ std::string buffer;
+ buffer.resize(8192);
+ size_t read_offset = 0;
+ for(;;) {
+ ssize_t bytes_read = read(header_program.read_fd, &buffer[read_offset], buffer.size() - read_offset);
+ if(bytes_read == 0) {
+ buffer.resize(read_offset);
+ break;
+ } else if(bytes_read == -1) {
+ res = MPV_ERROR_UNSUPPORTED;
+ break;
+ }
+
+ read_offset += bytes_read;
+ if(read_offset >= buffer.size()) {
+ fprintf(stderr, "Error: youtube header too large\n");
+ res = MPV_ERROR_UNSUPPORTED;
+ break;
+ }
+ }
+
+ if(res != 0)
+ kill(header_program.pid, SIGTERM);
+
+ int status = 0;
+ if(waitpid(header_program.pid, &status, 0) == -1) {
+ perror("waitpid failed");
+ res = MPV_ERROR_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ if(!WIFEXITED(status)) {
+ res = MPV_ERROR_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ if(WEXITSTATUS(status) != 0)
+ res = MPV_ERROR_UNSUPPORTED;
+
+ cleanup:
+ close(header_program.read_fd);
+
+ if(res == 0) {
+ size_t content_length_index = str_find_case_insensitive(buffer, 0, "content-length:", 15);
+ if(content_length_index == std::string::npos)
+ return res;
+
+ content_length_index += 15;
+ size_t content_length_end = buffer.find('\r', content_length_index);
+ if(content_length_end == std::string::npos)
+ return res;
+
+ buffer[content_length_end] = '\0';
+ errno = 0;
+ char *endptr;
+ int64_t content_length = strtoll(&buffer[content_length_index], &endptr, 10);
+ if(endptr == &buffer[content_length_index] || errno != 0)
+ return res;
+
+ res = content_length;
+ }
+
+ program->content_length = res;
+ return res;
+}
+
+static int64_t seek_fn(void *cookie, int64_t offset) {
+ ReadProgram *program = (ReadProgram*)cookie;
+
+ if(program->pid != -1) {
+ kill(program->pid, SIGTERM);
+ int status;
+ waitpid(program->pid, &status, 0);
+ program->pid = -1;
+ }
+
+ if(program->read_fd != -1) {
+ close(program->read_fd);
+ program->read_fd = -1;
+ }
+
+ program->offset = offset;
+
+ char range[64];
+ snprintf(range, sizeof(range), "%lld-%lld", (long long)program->offset, (long long)program->offset + 5242870LL);
+ const char *args[] = { "curl",
+ "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
+ "--no-buffer",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-g", "-s", "-L", "-f",
+ "-r", range, "--", program->url, nullptr };
+ int res = exec_program_pipe(args, program);
+ if(res != 0)
+ return MPV_ERROR_GENERIC;
+
+ return offset;
+}
+
+static int64_t read_fn(void *cookie, char *buf, uint64_t nbytes) {
+ ReadProgram *program = (ReadProgram*)cookie;
+ ssize_t bytes_read = read(program->read_fd, buf, nbytes);
+ if(bytes_read > 0) {
+ program->offset += bytes_read;
+ } else if(bytes_read == 0) {
+ // End of current range, progress to next range
+ bytes_read = seek_fn(program, program->offset);
+ if(bytes_read >= 0) {
+ bytes_read = read(program->read_fd, buf, nbytes);
+ if(bytes_read > 0)
+ program->offset += bytes_read;
+ }
+ }
+ return bytes_read;
+}
+
+static void close_fn(void *cookie) {
+ ReadProgram *program = (ReadProgram*)cookie;
+
+ if(program->pid != -1) {
+ kill(program->pid, SIGTERM);
+ int status;
+ waitpid(program->pid, &status, 0);
+ program->pid = -1;
+ }
+
+ if(program->read_fd != -1) {
+ close(program->read_fd);
+ program->read_fd = -1;
+ }
+
+ if(program->url) {
+ free(program->url);
+ program->url = nullptr;
+ }
+
+ delete program;
+}
+
+static int open_fn(void*, char *uri, mpv_stream_cb_info *info) {
+ ReadProgram *read_program = new ReadProgram();
+ read_program->read_fd = -1;
+ read_program->pid = -1;
+ read_program->url = strdup((const char*)uri + 8);
+ read_program->offset = 0;
+ int64_t res = seek_fn(read_program, read_program->offset);
+
+ info->cookie = read_program;
+ info->size_fn = size_fn;
+ info->read_fn = read_fn;
+ info->seek_fn = seek_fn;
+ info->close_fn = close_fn;
+ return res >= 0 ? 0 : MPV_ERROR_LOADING_FAILED;
+}
+
int main(int argc, char **argv) {
// This is needed for mpv_create or it will fail
setlocale(LC_ALL, "C");
@@ -328,12 +574,13 @@ int main(int argc, char **argv) {
mpv_set_before_init_options(mpv_ctx, args);
check_error(mpv_initialize(mpv_ctx), "mpv_initialize");
- const char *cmd[] = { "loadfile", args.file_to_play, NULL };
- check_error(mpv_command(mpv_ctx, cmd), "loadfile");
-
+ check_error(mpv_stream_cb_add_ro(mpv_ctx, "qm-yt", nullptr, open_fn), "mpv_stream_cb_add_ro");
check_error(mpv_observe_property(mpv_ctx, 0, "idle-active", MPV_FORMAT_FLAG), "observe idle-active");
check_error(mpv_observe_property(mpv_ctx, 0, "fullscreen", MPV_FORMAT_FLAG), "observe fullscreen");
+ const char *cmd[] = { "loadfile", args.file_to_play, NULL };
+ check_error(mpv_command(mpv_ctx, cmd), "loadfile");
+
char command_buffer[COMMAND_BUFFER_MAX_SIZE];
size_t command_buffer_size = 0;
std::mutex command_mutex;