From 38202de4f953fca28aa884246ced0aadf0d25a4d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 25 Jun 2021 12:44:53 +0200 Subject: Add a http server proxy for better youtube downloading (bypassing rate limit cased by http range header). Fix youtube live streams --- src/QuickMedia.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 9 deletions(-) (limited to 'src/QuickMedia.cpp') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 7305788..e6e4719 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -29,6 +29,7 @@ #include "../include/Utils.hpp" #include "../include/Tabs.hpp" #include "../include/Theme.hpp" +#include "../plugins/youtube/YoutubeMediaProxy.hpp" #include "../include/gui/Button.hpp" #include "../external/hash-library/sha256.h" @@ -1049,6 +1050,13 @@ namespace QuickMedia { return PluginResult::OK; } + static void check_youtube_dl_installed(const std::string &plugin_name) { + if(!is_program_executable_by_name("youtube-dl")) { + show_notification("QuickMedia", "youtube-dl needs to be installed to play " + plugin_name + " videos", Urgency::CRITICAL); + abort(); + } + } + void Program::load_plugin_by_name(std::vector &tabs, int &start_tab_index, FileManagerMimeType fm_mime_type, FileSelectionHandler file_selection_handler) { if(!plugin_name || plugin_name[0] == '\0') return; @@ -1208,18 +1216,22 @@ namespace QuickMedia { video_content_page(nullptr, youtube_video_page.get(), "", false, nullptr, body_items, 0); } } else if(strcmp(plugin_name, "pornhub") == 0) { + check_youtube_dl_installed(plugin_name); auto search_page = std::make_unique(this, "https://www.pornhub.com/", sf::Vector2i(320/1.5f, 180/1.5f)); add_pornhub_handlers(search_page.get()); tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "spankbang") == 0) { + check_youtube_dl_installed(plugin_name); auto search_page = std::make_unique(this, "https://spankbang.com/", sf::Vector2i(500/2.5f, 281/2.5f)); add_spankbang_handlers(search_page.get()); tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "xvideos") == 0) { + check_youtube_dl_installed(plugin_name); auto search_page = std::make_unique(this, "https://www.xvideos.com/", sf::Vector2i(352/1.5f, 198/1.5f)); add_xvideos_handlers(search_page.get()); tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "xhamster") == 0) { + check_youtube_dl_installed(plugin_name); auto search_page = std::make_unique(this, "https://xhamster.com/", sf::Vector2i(240, 135)); add_xhamster_handlers(search_page.get()); tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)}); @@ -2469,6 +2481,11 @@ namespace QuickMedia { return true; } + // TODO: Test with video that has hlsManifestUrl + static bool youtube_url_is_live_stream(const std::string &url) { + return url.find("yt_live_broadcast") != std::string::npos; + } + #define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask)) void Program::video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, Body *parent_body, BodyItems &next_play_items, int play_index, int *parent_body_page, const std::string &parent_page_search) { @@ -2496,6 +2513,12 @@ namespace QuickMedia { XSync(disp, False); }; + std::unique_ptr youtube_video_media_proxy; + std::unique_ptr youtube_audio_media_proxy; + AsyncTask youtube_downloader_task; + int youtube_video_content_length = 0; + int youtube_audio_content_length = 0; + std::string channel_url; AsyncTask video_tasks; std::function video_event_callback; @@ -2514,7 +2537,7 @@ namespace QuickMedia { std::string prev_start_time; std::vector media_chapters; - auto load_video_error_check = [this, &prev_start_time, &media_chapters, &in_seeking, &video_url, &audio_url, &has_embedded_audio, &video_title, &video_tasks, &channel_url, previous_page, &go_to_previous_page, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix, download_if_streaming_fails](std::string start_time = "", bool reuse_media_source = false) mutable { + auto load_video_error_check = [this, &youtube_downloader_task, &youtube_video_media_proxy, &youtube_audio_media_proxy, &youtube_video_content_length, &youtube_audio_content_length, &prev_start_time, &media_chapters, &in_seeking, &video_url, &audio_url, &has_embedded_audio, &video_title, &video_tasks, &channel_url, previous_page, &go_to_previous_page, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix, download_if_streaming_fails](std::string start_time = "", bool reuse_media_source = false) mutable { video_player.reset(); channel_url.clear(); video_loaded = false; @@ -2530,7 +2553,7 @@ namespace QuickMedia { audio_url.clear(); has_embedded_audio = true; - TaskResult load_result = run_task_with_loading_screen([this, video_page, &new_title, &channel_url, &media_chapters, largest_monitor_height, &has_embedded_audio, &video_url, &audio_url, &is_audio_only, &previous_page, is_youtube, download_if_streaming_fails]() { + TaskResult load_result = run_task_with_loading_screen([this, video_page, &youtube_video_content_length, &youtube_audio_content_length, &new_title, &channel_url, &media_chapters, largest_monitor_height, &has_embedded_audio, &video_url, &audio_url, &is_audio_only, &previous_page, is_youtube, download_if_streaming_fails]() { if(video_page->load(new_title, channel_url, media_chapters) != PluginResult::OK) return false; @@ -2555,6 +2578,37 @@ namespace QuickMedia { return false; } + if(is_youtube) { + // TODO: Do these requests in parallel + std::pair media_url_content_lengths[2] = { + std::make_pair(&video_url, &youtube_video_content_length), + std::make_pair(&audio_url, &youtube_audio_content_length), + }; + for(int i = 0; i < 2; ++i) { + if(media_url_content_lengths[i].first->empty() || youtube_url_is_live_stream(*media_url_content_lengths[i].first)) { + *media_url_content_lengths[i].second = 0; + continue; + } + + std::string headers; + if(download_head_to_string(*media_url_content_lengths[i].first, headers) != DownloadResult::OK) + return false; + + std::string content_length = header_extract_value(headers, "content-length"); + if(content_length.empty()) + return false; + + errno = 0; + char *endptr; + const long content_length_tmp = strtol(content_length.c_str(), &endptr, 10); + if(endptr != content_length.c_str() && errno == 0) { + *media_url_content_lengths[i].second = content_length_tmp; + } else { + return false; + } + } + } + return true; }); @@ -2579,9 +2633,82 @@ namespace QuickMedia { prev_start_time = start_time; watched_videos.insert(video_page->get_url()); + // TODO: Sync sequences + //audio_url.clear(); + //video_url.clear(); + //is_audio_only = true; + + 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(); + + if(youtube_downloader_task.valid()) + youtube_downloader_task.cancel(); + + youtube_video_media_proxy.reset(); + youtube_audio_media_proxy.reset(); + + struct MediaProxyMetadata { + std::unique_ptr *media_proxy; + std::string *url; + int 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(); + 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([&youtube_video_media_proxy, &youtube_audio_media_proxy]() { + sf::Clock timer; + const double sleep_time_millisec = 1; + 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(); + + const int sleep_time = sleep_time_millisec - timer.restart().asMilliseconds(); + if(sleep_time > 0) + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); + } + }); + } + } video_player = std::make_unique(is_audio_only, use_system_mpv_config, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, largest_monitor_height, plugin_name); - VideoPlayer::Error err = video_player->load_video(video_url.c_str(), audio_url.c_str(), window.getSystemHandle(), is_youtube, video_title, start_time, media_chapters); + VideoPlayer::Error err = video_player->load_video(v.c_str(), a.c_str(), window.getSystemHandle(), is_youtube, video_title, start_time, media_chapters); if(err != VideoPlayer::Error::OK) { std::string err_msg = "Failed to play url: "; err_msg += video_page->get_url(); @@ -2787,11 +2914,23 @@ namespace QuickMedia { bool page_changed = false; double resume_start_time = 0.0; - page_loop(tabs, 1, [this, &page_changed, &resume_start_time](const std::vector &new_tabs) { + page_loop(tabs, 1, [this, &page_changed, &resume_start_time, &youtube_video_media_proxy, &youtube_audio_media_proxy, &youtube_downloader_task](const std::vector &new_tabs) { if(!page_changed && new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { 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(); + + if(youtube_downloader_task.valid()) + youtube_downloader_task.cancel(); + + youtube_video_media_proxy.reset(); + youtube_audio_media_proxy.reset(); } }); @@ -6339,6 +6478,7 @@ namespace QuickMedia { if(exec_program_pipe(args, &read_program) != 0) return false; + // TODO: Remove this async task and make the fd non blocking instead header_reader = AsyncTask([this]{ char tmp_buf[1024]; while(true) { @@ -6368,10 +6508,10 @@ namespace QuickMedia { } void stop(bool download_completed) override { - if(read_program.pid != -1) - close(read_program.pid); if(read_program.read_fd != -1) - kill(read_program.read_fd, SIGTERM); + close(read_program.read_fd); + if(read_program.pid != -1) + kill(read_program.pid, SIGTERM); if(!download_completed) remove(output_filepath_tmp.data.c_str()); //header_reader.cancel(); @@ -6476,6 +6616,7 @@ namespace QuickMedia { return false; } + // TODO: Remove this async task and make the fd non blocking instead youtube_dl_output_reader = AsyncTask([this]{ char line[128]; char progress_c[10]; @@ -6524,8 +6665,8 @@ namespace QuickMedia { void stop(bool) override { if(read_program_file) fclose(read_program_file); - if(read_program.read_fd != -1) - kill(read_program.read_fd, SIGTERM); + if(read_program.pid != -1) + kill(read_program.pid, SIGTERM); // TODO: Remove the temporary files created by youtube-dl (if !download_completed) //header_reader.cancel(); } -- cgit v1.2.3