From a0c0f0bd551155270b11ac23c7f1c526a20a44b9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 5 Aug 2021 10:22:41 +0200 Subject: Youtube: attempt to auto-detect throttling and restart download --- TODO | 1 - plugins/youtube/YoutubeMediaProxy.hpp | 13 ++++++- src/QuickMedia.cpp | 48 +++++++++----------------- src/VideoPlayer.cpp | 5 +++ src/plugins/youtube/YoutubeMediaProxy.cpp | 57 ++++++++++++++++++++++++++++--- 5 files changed, 87 insertions(+), 37 deletions(-) diff --git a/TODO b/TODO index 82467dd..ef7080e 100644 --- a/TODO +++ b/TODO @@ -164,7 +164,6 @@ Load the next page in chapter list when reaching the bottom (when going to previ Loading image background should be rounded. //Workaround mpv issue where video is frozen after seeking (with and without cache enabled, but more often with cache enabled). This happens because of audio. Reloading audio fixes this but audio will then be gone. Better deal with reading from file errors. This could happen when reading a file while its being modified. See read_file_as_json. -Somehow fix youtube throttling speed limit to as low as 20-80kb which is fixed with a refresh. This should be detected automatically somehow. Allow ctrl+r for video when the video is loading. Youtube download gets stuck sometimes because of audio. Find a workaround for this. Dynamically change youtube video quality by modifying the itags (and other params?) if download is buffering or if the video is lagging. diff --git a/plugins/youtube/YoutubeMediaProxy.hpp b/plugins/youtube/YoutubeMediaProxy.hpp index 28aa6fe..d50d8df 100644 --- a/plugins/youtube/YoutubeMediaProxy.hpp +++ b/plugins/youtube/YoutubeMediaProxy.hpp @@ -2,10 +2,13 @@ #include "../../include/Program.hpp" #include +#include // TODO: Sync sequence for video and audio (for live stream). namespace QuickMedia { + using ThrottleHandler = std::function; + class YoutubeMediaProxy { public: enum Error { @@ -29,7 +32,7 @@ namespace QuickMedia { class YoutubeStaticMediaProxy : public YoutubeMediaProxy { public: - YoutubeStaticMediaProxy() = default; + YoutubeStaticMediaProxy(ThrottleHandler throttle_handler = nullptr); YoutubeStaticMediaProxy(YoutubeStaticMediaProxy&) = delete; YoutubeStaticMediaProxy&operator=(YoutubeStaticMediaProxy&) = delete; ~YoutubeStaticMediaProxy(); @@ -65,11 +68,19 @@ namespace QuickMedia { bool download_header_remaining_sent = false; int download_header_written_offset = 0; int download_header_offset_to_end_of_header = 0; + time_t download_start_time = 0; + time_t throttle_start_time = 0; + int64_t total_downloaded_bytes = 0; + bool download_started = false; + bool throttle_started = false; + bool throttle_callback_called = false; + ThrottleHandler throttle_handler = nullptr; bool client_request_finished = false; std::string client_request_buffer; char download_read_buffer[16384]; }; + // TODO: Add throttle detection for live streams class YoutubeLiveStreamMediaProxy : public YoutubeMediaProxy { public: YoutubeLiveStreamMediaProxy() = default; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 2db5537..bb92484 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2550,9 +2550,6 @@ namespace QuickMedia { PageType previous_page = pop_page_stack(); bool video_loaded = false; - bool in_seeking = false; - 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 std::string youtube_video_id_dummy; const bool is_youtube = youtube_url_extract_id(video_page->get_url(), youtube_video_id_dummy); @@ -2569,7 +2566,7 @@ namespace QuickMedia { } sf::WindowHandle video_player_window = None; - auto on_window_create = [this, &video_player_window, &video_loaded, &in_seeking](sf::WindowHandle _video_player_window) mutable { + auto on_window_create = [this, &video_player_window, &video_loaded](sf::WindowHandle _video_player_window) mutable { video_player_window = _video_player_window; XSelectInput(disp, video_player_window, KeyPressMask | PointerMotionMask); XSync(disp, False); @@ -2578,10 +2575,8 @@ namespace QuickMedia { // TODO: This is an issue just because of ubuntu shit that uses old mpv that doesn't support ipc over file descriptors. double time_in_file = 0.0; video_player->get_time_in_file(&time_in_file); - if(time_in_file > 0.00001) { + if(time_in_file > 0.00001) video_loaded = true; - in_seeking = false; - } }; std::unique_ptr youtube_video_media_proxy; @@ -2602,11 +2597,15 @@ namespace QuickMedia { std::string prev_start_time; std::vector media_chapters; - 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 { + bool throttled = false; + auto throttle_handler = [&throttled] { + throttled = true; + }; + + auto load_video_error_check = [this, &throttle_handler, &throttled, &youtube_downloader_task, &youtube_video_media_proxy, &youtube_audio_media_proxy, &youtube_video_content_length, &youtube_audio_content_length, &prev_start_time, &media_chapters, &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; - in_seeking = false; video_player_window = None; bool is_audio_only = no_video; @@ -2713,6 +2712,7 @@ namespace QuickMedia { youtube_downloader_task.cancel(); youtube_video_media_proxy.reset(); youtube_audio_media_proxy.reset(); + throttled = false; struct MediaProxyMetadata { std::unique_ptr *media_proxy; @@ -2729,7 +2729,7 @@ namespace QuickMedia { if(media_proxies[i].url->empty() || youtube_url_is_live_stream(*media_proxies[i].url)) continue; - *media_proxies[i].media_proxy = std::make_unique(); + *media_proxies[i].media_proxy = std::make_unique(throttle_handler); 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; @@ -2823,27 +2823,17 @@ namespace QuickMedia { } }; - 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(); - } - + video_event_callback = [&video_loaded](const char *event_name) mutable { if(strcmp(event_name, "pause") == 0) { //double time_remaining = 9999.0; //if(video_player->get_time_remaining(&time_remaining) == VideoPlayer::Error::OK && time_remaining <= 1.0) // end_of_file = true; } else if(strcmp(event_name, "playback-restart") == 0) { //video_player->set_paused(false); - in_seeking = false; } else if(strcmp(event_name, "file-loaded") == 0) { video_loaded = true; - in_seeking = false; } else if(strcmp(event_name, "video-reconfig") == 0 || strcmp(event_name, "audio-reconfig") == 0) { - if(!video_loaded) { - video_loaded = true; - } - in_seeking = false; + video_loaded = true; } //fprintf(stderr, "event name: %s\n", event_name); @@ -2918,7 +2908,6 @@ namespace QuickMedia { } else if(pressed_keysym == XK_s && pressing_ctrl) { video_page_download_video(video_page->get_url(), video_player_window); } else if(pressed_keysym == XK_F5) { - in_seeking = false; double resume_start_time = 0.0; video_player->get_time_in_file(&resume_start_time); load_video_error_check(std::to_string((int)resume_start_time)); @@ -3017,15 +3006,12 @@ namespace QuickMedia { cursor_visible = true; } - // TODO: Remove the need for this. This is needed right now because mpv sometimes gets stuck when playing youtube videos after seeking too much - if(is_youtube && video_player && in_seeking && seeking_start_timer.getElapsedTime().asSeconds() >= seeking_restart_timeout_sec) { - in_seeking = false; + if(is_youtube && video_player && throttled) { + throttled = false; + fprintf(stderr, "Throttled media download detected, reconnecting...\n"); double resume_start_time = 0.0; - if(video_player->get_time_in_file(&resume_start_time) == VideoPlayer::Error::OK) { - fprintf(stderr, "Video seems to be stuck after seeking, reloading...\n"); - // TODO: Set the second argument to false if the video url is no longer valid (or always?) - load_video_error_check(std::to_string((int)resume_start_time), true); - } + video_player->get_time_in_file(&resume_start_time); + load_video_error_check(std::to_string((int)resume_start_time)); } VideoPlayer::Error update_err = video_player ? video_player->update() : VideoPlayer::Error::OK; diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 7f01912..3e22b8b 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -133,6 +133,7 @@ namespace QuickMedia { wid_arg += parent_window_str; std::string input_conf = "--input-conf=" + resource_root + "input.conf"; + std::string cache_dir = "--cache-dir=" + std::move(get_cache_dir().join("media").data); // TODO: Resume playback if the last video played matches the first video played next time QuickMedia is launched args.insert(args.end(), { @@ -149,6 +150,10 @@ namespace QuickMedia { "--force-seekable=yes", "--image-display-duration=5", "--cache-pause=yes", + "--cache=yes", + "--cache-on-disk=yes", + "--cache-secs=86400", // 24 hours + cache_dir.c_str(), input_conf.c_str(), wid_arg.c_str() }); diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp index 6642b86..ed38981 100644 --- a/src/plugins/youtube/YoutubeMediaProxy.cpp +++ b/src/plugins/youtube/YoutubeMediaProxy.cpp @@ -19,6 +19,8 @@ namespace QuickMedia { static const int MAX_BUFFER_SIZE = 65536; static const int RANGE = 524287; + static const int64_t THROTTLED_DOWNLOAD_LIMIT_KB = 80; // TODO: What about people with really slow internet? What if the video player cache is not working and download is stuck, leading to false download speed calculation? + static const int64_t THROTTLED_DURATION_SECS = 3; static const char download_error_response_msg[] = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Length: 0\r\n\r\n"; @@ -73,6 +75,10 @@ namespace QuickMedia { return true; } + YoutubeStaticMediaProxy::YoutubeStaticMediaProxy(ThrottleHandler throttle_handler) : throttle_handler(std::move(throttle_handler)) { + + } + YoutubeStaticMediaProxy::~YoutubeStaticMediaProxy() { stop(); } @@ -193,6 +199,8 @@ namespace QuickMedia { void YoutubeStaticMediaProxy::on_client_disconnect() { client_request_buffer.clear(); client_request_finished = false; + download_started = false; + throttle_started = false; if(client_fd != -1) { close(client_fd); @@ -248,6 +256,8 @@ namespace QuickMedia { if(header_end != std::string::npos) { client_request_buffer.erase(header_end + 4); client_request_finished = true; + download_started = false; + throttle_started = false; int new_start_range = header_extract_start_range(client_request_buffer); //fprintf(stderr, "got new range from client: %d\n", new_start_range); @@ -284,7 +294,7 @@ namespace QuickMedia { if(client_disconnected) { wait_program(downloader_read_program.pid); } else { - if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0) + if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0 && program_status == 0) return Error::OK; } downloader_read_program.pid = -1; @@ -444,6 +454,45 @@ namespace QuickMedia { 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; + throttle_started = false; + + struct timespec tp; + clock_gettime(CLOCK_BOOTTIME, &tp); + download_start_time = tp.tv_sec; + } + total_downloaded_bytes += downloader_num_read_bytes; + } + } + + if(download_started) { + struct timespec tp; + clock_gettime(CLOCK_BOOTTIME, &tp); + + int64_t time_elapsed = tp.tv_sec - download_start_time; + int64_t download_speed_kb_sec = 0; + if(time_elapsed > 0) + download_speed_kb_sec = (total_downloaded_bytes / time_elapsed) / 1024; + + if(download_speed_kb_sec < THROTTLED_DOWNLOAD_LIMIT_KB) { + if(throttle_started) { + if(tp.tv_sec - throttle_start_time >= THROTTLED_DURATION_SECS && !throttle_callback_called) { + total_downloaded_bytes = 0; + download_started = false; + throttle_started = false; + throttle_callback_called = true; + if(throttle_handler) + throttle_handler(); + } + } else { + throttle_started = true; + throttle_start_time = tp.tv_sec; + } + } else { + throttle_started = false; } } @@ -587,7 +636,7 @@ namespace QuickMedia { YoutubeMediaProxy::Error YoutubeLiveStreamMediaProxy::update_download_program_status() { int program_status = 0; - if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0) + if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0 && program_status == 0) return Error::OK; downloader_read_program.pid = -1; @@ -688,13 +737,13 @@ namespace QuickMedia { std::string sequence_num = header_extract_value(download_header, "x-sequence-num"); fprintf(stderr, "server sequence num: |%s|\n", sequence_num.c_str()); if(sequence_num.empty()) - fprintf(stderr, "YoutubeLiveStreamMediaProxy::handle_download: missing sequence num from server\n"); + fprintf(stderr, "YoutubeLiveStreamMediaProxy::update: missing sequence num from server\n"); else livestream_sequence_num = strtoll(sequence_num.c_str(), nullptr, 10); } } else { if(download_header.size() > MAX_BUFFER_SIZE) { - fprintf(stderr, "YoutubeLiveStreamMediaProxy::handle_download: buffer is full (malicious server?)\n"); + fprintf(stderr, "YoutubeLiveStreamMediaProxy::update: buffer is full (malicious server?)\n"); if(downloader_read_program.pid != -1) { kill(downloader_read_program.pid, SIGTERM); wait_program(downloader_read_program.pid); -- cgit v1.2.3