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 --- src/QuickMedia.cpp | 48 +++++++++----------------- src/VideoPlayer.cpp | 5 +++ src/plugins/youtube/YoutubeMediaProxy.cpp | 57 ++++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 35 deletions(-) (limited to 'src') 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