From d37b3a7aac87e5f60c49202c824d985e53b7b544 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 14 Jun 2021 07:22:05 +0200 Subject: Rework around mpv issue: reload video if frozen after seek. Add f5 to reload video, readd video cache --- src/QuickMedia.cpp | 289 +++++++++++++++++++++++++++++----------------------- src/VideoPlayer.cpp | 49 ++------- 2 files changed, 173 insertions(+), 165 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c81d59d..c6a1d2f 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2376,6 +2376,76 @@ namespace QuickMedia { return url.substr(timestamp_start, timestamp_end - timestamp_start); } + bool Program::video_download_if_non_streamable(std::string &video_url, std::string &audio_url, bool &is_audio_only, bool &has_embedded_audio, PageType previous_page) { + Path video_cache_dir = get_cache_dir().join("media"); + Path video_path = video_cache_dir; + SHA256 sha256; + sha256.add(video_url.data(), video_url.size()); + video_path.join(sha256.getHash()); + + if(get_file_type(video_path) == FileType::REGULAR) { + fprintf(stderr, "%s is found in cache. Playing from cache...\n", video_url.c_str()); + video_url = std::move(video_path.data); + audio_url.clear(); + if(no_video) { + is_audio_only = true; + has_embedded_audio = false; + } else { + is_audio_only = false; + has_embedded_audio = true; + } + } else { + TaskResult video_is_not_streamble_result = run_task_with_loading_screen([video_url]() { + return video_url_is_non_streamable_mp4(video_url.c_str()); + }); + + if(video_is_not_streamble_result == TaskResult::TRUE) { + fprintf(stderr, "%s is detected to be a non-streamable mp4 file, downloading it before playing it...\n", video_url.c_str()); + if(create_directory_recursive(video_cache_dir) != 0) { + show_notification("QuickMedia", "Failed to create video cache directory", Urgency::CRITICAL); + current_page = previous_page; + go_to_previous_page = true; + return false; + } + + TaskResult download_file_result = run_task_with_loading_screen([&video_path, video_url]() { + return download_to_file(video_url, video_path.data, {}, true) == DownloadResult::OK; + }); + switch(download_file_result) { + case TaskResult::TRUE: { + video_url = std::move(video_path.data); + audio_url.clear(); + if(no_video) { + is_audio_only = true; + has_embedded_audio = false; + } else { + is_audio_only = false; + has_embedded_audio = true; + } + break; + } + case TaskResult::FALSE: { + show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL); + current_page = previous_page; + go_to_previous_page = true; + return false; + } + case TaskResult::CANCEL: { + current_page = previous_page; + go_to_previous_page = true; + return false; + } + } + } else if(video_is_not_streamble_result == TaskResult::CANCEL) { + current_page = previous_page; + go_to_previous_page = true; + return false; + } + } + + return true; + } + #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, BodyItems &next_play_items, int play_index, int *parent_body_page, const std::string &parent_page_search) { @@ -2406,127 +2476,77 @@ namespace QuickMedia { std::function video_event_callback; bool go_to_previous_page = false; - auto load_video_error_check = [this, &video_title, &video_tasks, &channel_url, previous_page, &go_to_previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix, download_if_streaming_fails](bool resume_video) mutable { + std::string video_url; + std::string audio_url; + + bool in_seeking = false; + sf::Clock seeking_start_timer; + const float seeking_restart_timeout_sec = 4.0f; // TODO: Test if this timeout is good on slow hardware such as pinephone and slow internet + + auto load_video_error_check = [this, &in_seeking, &video_url, &audio_url, &video_title, &video_tasks, &channel_url, previous_page, &go_to_previous_page, &time_watched_timer, &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; - - std::string new_title; - TaskResult load_result = run_task_with_loading_screen([video_page, &new_title, &channel_url]() { - return video_page->load(new_title, channel_url) == PluginResult::OK; - }); - - 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) { - show_notification("QuickMedia", "Failed to load media", Urgency::CRITICAL); - current_page = previous_page; - go_to_previous_page = true; - return; - } bool is_audio_only = no_video; bool has_embedded_audio = true; const int largest_monitor_height = get_largest_monitor_height(disp); - std::string video_url = video_page->get_video_url(largest_monitor_height, has_embedded_audio); - std::string audio_url; - 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(); - } - - bool video_url_is_local = false; - if(!is_youtube && download_if_streaming_fails) { - Path video_cache_dir = get_cache_dir().join("media"); - Path video_path = video_cache_dir; - SHA256 sha256; - sha256.add(video_url.data(), video_url.size()); - video_path.join(sha256.getHash()); - if(get_file_type(video_path) == FileType::REGULAR) { - fprintf(stderr, "%s is found in cache. Playing from cache...\n", video_url.c_str()); - video_url = std::move(video_path.data); - video_url_is_local = true; - audio_url.clear(); - if(no_video) { + + if(!reuse_media_source) { + std::string new_title; + TaskResult load_result = run_task_with_loading_screen([video_page, &new_title, &channel_url]() { + return video_page->load(new_title, channel_url) == PluginResult::OK; + }); + + 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) { + show_notification("QuickMedia", "Failed to load media", Urgency::CRITICAL); + current_page = previous_page; + go_to_previous_page = true; + return; + } + + video_url.clear(); + audio_url.clear(); + 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 { - is_audio_only = false; - has_embedded_audio = true; } - } else { - TaskResult video_is_not_streamble_result = run_task_with_loading_screen([video_url]() { - return video_url_is_non_streamable_mp4(video_url.c_str()); - }); - if(video_is_not_streamble_result == TaskResult::TRUE) { - fprintf(stderr, "%s is detected to be a non-streamable mp4 file, downloading it before playing it...\n", video_url.c_str()); - if(create_directory_recursive(video_cache_dir) != 0) { - show_notification("QuickMedia", "Failed to create video cache directory", Urgency::CRITICAL); - current_page = previous_page; - go_to_previous_page = true; - return; - } + } else if(!has_embedded_audio) { + audio_url = video_page->get_audio_url(); + } - TaskResult download_file_result = run_task_with_loading_screen([&video_path, video_url]() { - return download_to_file(video_url, video_path.data, {}, true) == DownloadResult::OK; - }); - switch(download_file_result) { - case TaskResult::TRUE: { - video_url = std::move(video_path.data); - video_url_is_local = true; - audio_url.clear(); - if(no_video) { - is_audio_only = true; - has_embedded_audio = false; - } else { - is_audio_only = false; - has_embedded_audio = true; - } - break; - } - case TaskResult::FALSE: { - show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL); - current_page = previous_page; - go_to_previous_page = true; - return; - } - case TaskResult::CANCEL: { - current_page = previous_page; - go_to_previous_page = true; - return; - } - } - } else if(video_is_not_streamble_result == TaskResult::CANCEL) { - current_page = previous_page; - go_to_previous_page = true; + 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; - } } } - std::string start_time; - if(is_youtube) + const bool is_resume_go_back = !start_time.empty(); + if(is_youtube && start_time.empty()) start_time = youtube_url_extract_timestamp(video_page->get_url()); time_watched_timer.restart(); watched_videos.insert(video_page->get_url()); - video_player = std::make_unique(is_audio_only, use_system_mpv_config, resume_video, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, largest_monitor_height); - VideoPlayer::Error err = video_player->load_video(video_url.c_str(), audio_url.c_str(), window.getSystemHandle(), plugin_name, video_title, start_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); + VideoPlayer::Error err = video_player->load_video(video_url.c_str(), audio_url.c_str(), window.getSystemHandle(), is_youtube, video_title, start_time); if(err != VideoPlayer::Error::OK) { std::string err_msg = "Failed to play url: "; err_msg += video_page->get_url(); @@ -2537,15 +2557,17 @@ namespace QuickMedia { if(video_page->autoplay_next_item()) return; - std::string url = video_page->get_url(); - video_tasks = AsyncTask([video_page, url]() { - BodyItems related_videos = video_page->get_related_media(url); - video_page->mark_watched(); - return related_videos; - }); + if(!is_resume_go_back) { + std::string url = video_page->get_url(); + video_tasks = AsyncTask([video_page, url]() { + BodyItems related_videos = video_page->get_related_media(url); + video_page->mark_watched(); + return related_videos; + }); + } // TODO: Make this also work for other video plugins - if(strcmp(plugin_name, "youtube") != 0 || resume_video) + if(strcmp(plugin_name, "youtube") != 0 || is_resume_go_back) return; std::string video_id; @@ -2580,29 +2602,35 @@ namespace QuickMedia { } }; - video_event_callback = [this, &time_watched_timer, &video_loaded](const char *event_name) mutable { + video_event_callback = [&time_watched_timer, &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(); + } + 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) { time_watched_timer.restart(); - if(!video_loaded) - video_player->set_property("no-resume-playback", true); 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; time_watched_timer.restart(); } + in_seeking = false; } //fprintf(stderr, "event name: %s\n", event_name); }; - load_video_error_check(false); + load_video_error_check(); sf::Event event; @@ -2647,11 +2675,13 @@ namespace QuickMedia { window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE); } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) { save_video_url_to_clipboard(); + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::F5) { + load_video_error_check(); } } handle_window_close(); - if(video_player_window && XCheckTypedWindowEvent(disp, video_player_window, KeyPress, &xev)/* && xev.xkey.subwindow == video_player_window*/) { + if(video_player && video_player_window && XCheckTypedWindowEvent(disp, video_player_window, KeyPress, &xev)/* && xev.xkey.subwindow == video_player_window*/) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" KeySym pressed_keysym = XKeycodeToKeysym(disp, xev.xkey.keycode, 0); @@ -2669,6 +2699,11 @@ namespace QuickMedia { window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE); } else if(pressed_keysym == XK_s && pressing_ctrl) { video_page_download_video(video_page->get_url(), !is_matrix || is_youtube, 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)); } else if(pressed_keysym == XK_r && pressing_ctrl) { bool cancelled = false; if(video_tasks.valid()) { @@ -2726,19 +2761,11 @@ namespace QuickMedia { } bool page_changed = false; - page_loop(tabs, 1, [this, &page_changed](const std::vector &new_tabs) { + double resume_start_time = 0.0; + page_loop(tabs, 1, [this, &page_changed, &resume_start_time](const std::vector &new_tabs) { if(!page_changed && new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { - window.setMouseCursorVisible(true); - if(video_player) { - video_player->quit_and_save_watch_later(); - while(true) { - VideoPlayer::Error update_err = video_player->update(); - if(update_err != VideoPlayer::Error::OK || !window.isOpen() || current_page == PageType::EXIT) - break; - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } - video_player.reset(); - } + video_player->get_time_in_file(&resume_start_time); + video_player.reset(); page_changed = true; } }); @@ -2750,7 +2777,7 @@ namespace QuickMedia { if(!video_player) { current_page = PageType::VIDEO_CONTENT; - load_video_error_check(true); + load_video_error_check(resume_start_time > 0.1 ? std::to_string((int)resume_start_time) : ""); } else { XMapWindow(disp, video_player_window); XSync(disp, False); @@ -2770,6 +2797,16 @@ 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; + double resume_start_time = 0.0; + if(video_player->get_time_in_file(&resume_start_time) == VideoPlayer::Error::OK) { + // 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); + } + } + VideoPlayer::Error update_err = video_player ? video_player->update() : VideoPlayer::Error::OK; if(update_err == VideoPlayer::Error::FAIL_TO_CONNECT_TIMEOUT) { show_notification("QuickMedia", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL); @@ -2863,7 +2900,7 @@ namespace QuickMedia { break; } - load_video_error_check(false); + load_video_error_check(); } else if(update_err != VideoPlayer::Error::OK) { show_notification("QuickMedia", "Failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL); current_page = previous_page; diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 0df4108..fe4643e 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -20,11 +20,10 @@ const int MAX_RETRIES_CONNECT = 1000; const int READ_TIMEOUT_MS = 200; namespace QuickMedia { - VideoPlayer::VideoPlayer(bool no_video, bool use_system_mpv_config, bool resume_playback, bool keep_open, EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback, const std::string &resource_root, int monitor_height) : + VideoPlayer::VideoPlayer(bool no_video, bool use_system_mpv_config, bool keep_open, EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback, const std::string &resource_root, int monitor_height) : exit_status(0), no_video(no_video), use_system_mpv_config(use_system_mpv_config), - resume_playback(resume_playback), keep_open(keep_open), video_process_id(-1), connected_to_ipc(false), @@ -67,7 +66,7 @@ namespace QuickMedia { XCloseDisplay(display); } - VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, const char *audio_path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &title, const std::string &start_time) { + VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, const char *audio_path, sf::WindowHandle _parent_window, bool is_youtube, const std::string &title, const std::string &start_time) { parent_window = _parent_window; if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) { @@ -77,10 +76,6 @@ namespace QuickMedia { return Error::FAIL_TO_CREATE_SOCKET; } - Path cookies_filepath; - if(get_cookies_filepath(cookies_filepath, plugin_name) != 0) - fprintf(stderr, "Failed to create %s cookies filepath\n", plugin_name.c_str()); - const std::string parent_window_str = std::to_string(parent_window); std::vector args; @@ -91,19 +86,12 @@ namespace QuickMedia { std::string input_conf = "--input-conf=" + resource_root + "input.conf"; - Path mpv_watch_later_dir = get_storage_dir().join("mpv").join("watch_later"); - if(create_directory_recursive(mpv_watch_later_dir) != 0) - fprintf(stderr, "Failed to create %s watch later directory\n", mpv_watch_later_dir.data.c_str()); - std::string watch_later_dir = "--watch-later-directory=" + mpv_watch_later_dir.data; - std::string ytdl_format; if(no_video) ytdl_format = "--ytdl-format=bestaudio/best"; else ytdl_format = "--ytdl-format=bestvideo[height<=?" + std::to_string(monitor_height) + "]+bestaudio/best"; - std::string cookies_file_arg = "--cookies-file=" + cookies_filepath.data; - // TODO: Resume playback if the last video played matches the first video played next time QuickMedia is launched args.insert(args.end(), { "mpv", @@ -112,13 +100,11 @@ namespace QuickMedia { "--no-terminal", "--save-position-on-quit=yes", "--profile=pseudo-gui", // For gui when playing audio, requires a version of mpv that isn't ancient - watch_later_dir.c_str(), ytdl_format.c_str(), + "--no-resume-playback", // TODO: Disable hr seek on low power devices? "--hr-seek=yes", - "--cache=no", - "--cookies", - cookies_file_arg.c_str(), + //"--cache=no", input_conf.c_str(), wid_arg.c_str() }); @@ -131,10 +117,8 @@ namespace QuickMedia { if(keep_open) args.push_back("--keep-open=yes"); - if(resume_playback) - args.push_back("--resume-playback"); - else - args.push_back("--no-resume-playback"); + if(is_youtube) + args.push_back("--no-ytdl"); if(!use_system_mpv_config) { args.insert(args.end(), { @@ -161,7 +145,7 @@ namespace QuickMedia { } std::string start_time_arg; - if(!resume_playback && !start_time.empty()) { + if(!start_time.empty()) { start_time_arg = "--start=" + start_time; args.push_back(start_time_arg.c_str()); } @@ -184,16 +168,16 @@ namespace QuickMedia { return Error::OK; } - VideoPlayer::Error VideoPlayer::load_video(const char *path, const char *audio_path, sf::WindowHandle _parent_window, const std::string &plugin_name, const std::string &title, const std::string &start_time) { + VideoPlayer::Error VideoPlayer::load_video(const char *path, const char *audio_path, sf::WindowHandle _parent_window, bool is_youtube, const std::string &title, const std::string &start_time) { // This check is to make sure we dont change window that the video belongs to. This is not a usecase we will have so // no need to support it for now at least. assert(parent_window == 0 || parent_window == _parent_window); assert(path); fprintf(stderr, "Playing video: %s, audio: %s\n", path ? path : "", audio_path ? audio_path : ""); if(video_process_id == -1) - return launch_video_process(path, audio_path, _parent_window, plugin_name, title, start_time); + return launch_video_process(path, audio_path, _parent_window, is_youtube, title, start_time); - // TODO: When these are used, add audio_path, title and start_time. Also handle plugin_name + // TODO: When these are used, add audio_path, title and start_time. Also handle is_youtube Json::Value command_data(Json::arrayValue); command_data.append("loadfile"); command_data.append(path); @@ -399,19 +383,6 @@ namespace QuickMedia { return err; } - VideoPlayer::Error VideoPlayer::quit_and_save_watch_later() { - Json::Value command_data(Json::arrayValue); - command_data.append("quit-watch-later"); - Json::Value command(Json::objectValue); - command["command"] = command_data; - - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; - const std::string cmd_str = Json::writeString(builder, command) + "\n"; - return send_command(cmd_str.c_str(), cmd_str.size()); - } - VideoPlayer::Error VideoPlayer::send_command(const char *cmd, size_t size) { if(!connected_to_ipc) return Error::FAIL_NOT_CONNECTED; -- cgit v1.2.3