From dfa4e24f72996d507e710fc6839367536237c501 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 25 Jun 2021 22:52:47 +0200 Subject: Rework youtube redirect code --- include/Program.hpp | 3 +- plugins/Youtube.hpp | 4 +- src/Program.cpp | 18 +++++-- src/QuickMedia.cpp | 136 ++++++++++++++++++++---------------------------- src/plugins/Youtube.cpp | 64 ++++++++++++++++++----- 5 files changed, 127 insertions(+), 98 deletions(-) diff --git a/include/Program.hpp b/include/Program.hpp index 4007a24..92defa1 100644 --- a/include/Program.hpp +++ b/include/Program.hpp @@ -40,7 +40,8 @@ int wait_program_non_blocking(pid_t process_id, int *status); int exec_program_async(const char **args, pid_t *result_process_id); void program_clear_current_thread(); -void program_kill_in_thread(const std::thread::id &thread_id); +void program_kill_in_thread(std::thread::id thread_id); +bool program_is_dead_in_thread(std::thread::id thread_id); bool program_is_dead_in_current_thread(); #endif /* QUICKMEDIA_PROGRAM_HPP */ diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index 5428de0..1b17c7b 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -24,7 +24,9 @@ namespace QuickMedia { }; bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id); - void youtube_custom_redirect(std::string &video_url, std::string &audio_url); + // |video_url| or |audio_url| will be empty if there is an error and false will be returned. + // If false is returned from |active_handler|, then this function is cancelled. + bool youtube_custom_redirect(std::string &video_url, std::string &audio_url, int &video_content_length, int &audio_content_length, std::function active_handler); class YoutubeSearchPage : public LazyFetchPage { public: diff --git a/src/Program.cpp b/src/Program.cpp index 57d7c61..c0e0eca 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -57,9 +57,11 @@ public: void kill_in_thread(const std::thread::id &thread_id) { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(thread_id); - if(it != thread_current_program.end() && it->second.read_program.pid != -1 && it->second.read_program.read_fd != -1) { - close(it->second.read_program.read_fd); - kill(it->second.read_program.pid, SIGTERM); + if(it != thread_current_program.end()) { + if(it->second.read_program.read_fd != -1) + close(it->second.read_program.read_fd); + if(it->second.read_program.pid != -1) + kill(it->second.read_program.pid, SIGTERM); it->second.killed = true; } } @@ -282,10 +284,18 @@ void program_clear_current_thread() { current_thread_program.clear(); } -void program_kill_in_thread(const std::thread::id &thread_id) { +void program_kill_in_thread(std::thread::id thread_id) { current_thread_program.kill_in_thread(thread_id); } +bool program_is_dead_in_thread(std::thread::id thread_id) { + std::lock_guard lock(thread_current_program_mutex); + auto it = thread_current_program.find(thread_id); + if(it != thread_current_program.end()) + return it->second.killed; + return false; +} + bool program_is_dead_in_current_thread() { return current_thread_program.is_killed(); } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index e6e4719..97900d3 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2532,8 +2532,6 @@ namespace QuickMedia { sf::Clock seeking_start_timer; const float seeking_restart_timeout_sec = 10.0f; // TODO: Test if this timeout is good on slow hardware such as pinephone and slow internet - const int num_load_tries_max = 3; - int load_try = 0; std::string prev_start_time; std::vector media_chapters; @@ -2553,73 +2551,70 @@ namespace QuickMedia { audio_url.clear(); has_embedded_audio = true; - 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; - - if(!no_video) - video_url = video_page->get_video_url(largest_monitor_height, has_embedded_audio); - - if(video_url.empty() || no_video) { - video_url = video_page->get_audio_url(); - if(video_url.empty()) { - video_url = video_page->get_url(); - has_embedded_audio = true; - } else { - is_audio_only = true; - has_embedded_audio = false; - } - } else if(!has_embedded_audio) { - audio_url = video_page->get_audio_url(); - } - - if(!is_youtube && download_if_streaming_fails) { - if(!video_download_if_non_streamable(video_url, audio_url, is_audio_only, has_embedded_audio, previous_page)) + const int num_retries = is_youtube ? 3 : 1; + bool load_successful = false; + for(int i = 0; i < num_retries; ++i) { + bool cancelled = false; + TaskResult load_result = run_task_with_loading_screen([this, video_page, &cancelled, &new_title, &channel_url, &media_chapters, &youtube_video_content_length, &youtube_audio_content_length, 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; - } - 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; - } + if(!no_video) + video_url = video_page->get_video_url(largest_monitor_height, has_embedded_audio); - std::string headers; - if(download_head_to_string(*media_url_content_lengths[i].first, headers) != DownloadResult::OK) - return false; + if(video_url.empty() || no_video) { + video_url = video_page->get_audio_url(); + if(video_url.empty()) { + video_url = video_page->get_url(); + has_embedded_audio = true; + } else { + is_audio_only = true; + has_embedded_audio = false; + } + } else if(!has_embedded_audio) { + audio_url = video_page->get_audio_url(); + } - std::string content_length = header_extract_value(headers, "content-length"); - if(content_length.empty()) + if(!is_youtube && download_if_streaming_fails) { + if(!video_download_if_non_streamable(video_url, audio_url, is_audio_only, has_embedded_audio, previous_page)) 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 { + if(is_youtube && !youtube_url_is_live_stream(video_url) && !youtube_url_is_live_stream(audio_url)) { + youtube_video_content_length = 0; + youtube_audio_content_length = 0; + std::string new_video_url = video_url; + std::string new_audio_url = audio_url; + auto current_thread_id = std::this_thread::get_id(); + if(!youtube_custom_redirect(new_video_url, new_audio_url, youtube_video_content_length, youtube_audio_content_length, [current_thread_id]{ return !program_is_dead_in_thread(current_thread_id); })) { + if(program_is_dead_in_current_thread()) + cancelled = true; return false; } + + video_url = std::move(new_video_url); + audio_url = std::move(new_audio_url); } - } - return true; - }); + return true; + }); - if(!new_title.empty()) - video_title = std::move(new_title); + if(!new_title.empty()) + video_title = std::move(new_title); - if(load_result == TaskResult::CANCEL) { - current_page = previous_page; - go_to_previous_page = true; - return; - } else if(load_result == TaskResult::FALSE) { + if(load_result == TaskResult::CANCEL || cancelled) { + current_page = previous_page; + go_to_previous_page = true; + return; + } else if(load_result == TaskResult::FALSE) { + continue; + } + + load_successful = true; + break; + } + + if(!load_successful) { show_notification("QuickMedia", "Failed to load media", Urgency::CRITICAL); current_page = previous_page; go_to_previous_page = true; @@ -2762,7 +2757,7 @@ namespace QuickMedia { } }; - video_event_callback = [&load_try, &video_loaded, &in_seeking, &seeking_start_timer](const char *event_name) mutable { + video_event_callback = [&video_loaded, &in_seeking, &seeking_start_timer](const char *event_name) mutable { if(strcmp(event_name, "seek") == 0) { in_seeking = true; seeking_start_timer.restart(); @@ -2775,17 +2770,14 @@ namespace QuickMedia { } else if(strcmp(event_name, "playback-restart") == 0) { //video_player->set_paused(false); in_seeking = false; - load_try = 0; } else if(strcmp(event_name, "file-loaded") == 0) { video_loaded = true; in_seeking = false; - load_try = 0; } else if(strcmp(event_name, "video-reconfig") == 0 || strcmp(event_name, "audio-reconfig") == 0) { if(!video_loaded) { video_loaded = true; } in_seeking = false; - load_try = 0; } //fprintf(stderr, "event name: %s\n", event_name); @@ -3064,24 +3056,10 @@ namespace QuickMedia { load_video_error_check(); } else if(update_err != VideoPlayer::Error::OK) { - ++load_try; - if(load_try == 1 && num_load_tries_max > 1 && is_youtube) { - fprintf(stderr, "Failed to play the media, retrying (try %d out of %d)\n", 1 + load_try, num_load_tries_max); - std::string prev_video_url = video_url; - std::string prev_audio_url = audio_url; - youtube_custom_redirect(video_url, audio_url); - load_video_error_check(prev_start_time, video_url != prev_video_url || audio_url != prev_audio_url); - } else { - if(load_try < num_load_tries_max) { - fprintf(stderr, "Failed to play the media, retrying (try %d out of %d)\n", 1 + load_try, num_load_tries_max); - load_video_error_check(prev_start_time); - } else { - show_notification("QuickMedia", "Failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL); - current_page = previous_page; - go_to_previous_page = true; - break; - } - } + show_notification("QuickMedia", "Failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL); + current_page = previous_page; + go_to_previous_page = true; + break; } AsyncImageLoader::get_instance().update(); diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 05fe5c5..f20f38e 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -220,7 +220,7 @@ R"END( // Sometimes youtube returns a redirect url (not in the header but in the body...). // TODO: Find why this happens and if there is a way bypass it. - static std::string get_playback_url_recursive(std::string playback_url) { + static std::string get_playback_url_recursive(std::string playback_url, int &content_length) { std::vector additional_args = get_cookies(); additional_args.push_back({ "-r", "0-4096" }); @@ -232,16 +232,27 @@ R"END( std::string content_type = header_extract_value(response_headers, "content-type"); if(content_type.empty()) { - fprintf(stderr, "Failed to find content-type in youtube video header. Trying to play the video anyways\n"); - return playback_url; + fprintf(stderr, "Failed to find content-type in youtube video header\n"); + return ""; } - if(string_starts_with(content_type, "video") || string_starts_with(content_type, "audio")) + if(string_starts_with(content_type, "video") || string_starts_with(content_type, "audio")) { + std::string content_length_str = header_extract_value(response_headers, "content-length"); + if(content_length_str.empty()) + return ""; + + errno = 0; + char *endptr; + content_length = strtol(content_length_str.c_str(), &endptr, 10); + if(endptr == content_length_str.c_str() || errno != 0) + return ""; + return playback_url; + } if(response_body.empty()) { - fprintf(stderr, "Failed to redirect youtube video. Trying to play the video anyways\n"); - return playback_url; + fprintf(stderr, "Failed to redirect youtube video\n"); + return ""; } playback_url = std::move(response_body); @@ -250,19 +261,46 @@ R"END( return playback_url; } - void youtube_custom_redirect(std::string &video_url, std::string &audio_url) { + bool youtube_custom_redirect(std::string &video_url, std::string &audio_url, int &video_content_length, int &audio_content_length, std::function active_handler) { // TODO: Do this without threads + int num_total_tasks = 0; AsyncTask tasks[2]; - if(!video_url.empty()) - tasks[0] = AsyncTask([video_url]() { return get_playback_url_recursive(std::move(video_url)); }); - if(!audio_url.empty()) - tasks[1] = AsyncTask([audio_url]() { return get_playback_url_recursive(std::move(audio_url)); }); + if(!video_url.empty()) { + tasks[0] = AsyncTask([video_url, &video_content_length]() { return get_playback_url_recursive(std::move(video_url), video_content_length); }); + ++num_total_tasks; + } + + if(!audio_url.empty()) { + tasks[1] = AsyncTask([audio_url, &audio_content_length]() { return get_playback_url_recursive(std::move(audio_url), audio_content_length); }); + ++num_total_tasks; + } + + if(num_total_tasks == 0) + return false; + + int num_finished_tasks = 0; std::string *strings[2] = { &video_url, &audio_url }; - for(int i = 0; i < 2; ++i) { - if(tasks[i].valid()) + while(true) { + for(int i = 0; i < 2; ++i) { + if(!tasks[i].ready()) + continue; + *strings[i] = tasks[i].get(); + if(strings[i]->empty()) + return false; + + ++num_finished_tasks; + if(num_finished_tasks == num_total_tasks) + return true; + } + + if(!active_handler()) + return false; + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } + return true; } // This is a common setup of text in the youtube json -- cgit v1.2.3