diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-03-08 16:39:55 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2022-03-08 16:39:55 +0100 |
commit | a26d0fcc0a30a28ce0e458ea275fc0787c693bc6 (patch) | |
tree | ffe2212b81d9fdc93b49383c102e7c0ece0d225a /src | |
parent | 21c50903a68c253fa5fcb9ed5ac8ba5abb1142b9 (diff) |
Save youtube watch progress and resume next time the video is played
Diffstat (limited to 'src')
-rw-r--r-- | src/QuickMedia.cpp | 30 | ||||
-rw-r--r-- | src/VideoPlayer.cpp | 19 | ||||
-rw-r--r-- | src/plugins/Lbry.cpp | 5 | ||||
-rw-r--r-- | src/plugins/LocalAnime.cpp | 2 | ||||
-rw-r--r-- | src/plugins/MediaGeneric.cpp | 3 | ||||
-rw-r--r-- | src/plugins/Peertube.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Soundcloud.cpp | 3 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 54 |
8 files changed, 93 insertions, 29 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index b9dd0f0..0da90f9 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3034,7 +3034,9 @@ namespace QuickMedia { bool video_loaded = false; double video_time_pos = 0.0; // Time in media in seconds. Updates every 5 seconds and when starting to watch the video and when seeking. + double video_duration = 0.0; // Time in seconds. 0 if unknown bool update_time_pos = false; + bool update_duration = false; mgl::Clock video_time_pos_clock; std::string youtube_video_id_dummy; @@ -3066,8 +3068,7 @@ namespace QuickMedia { if(!subtitle_data.url.empty()) video_player->add_subtitle(subtitle_data.url, subtitle_data.title, "eng"); - if(video_page->is_local()) - update_time_pos = true; + update_time_pos = true; }; std::unique_ptr<YoutubeMediaProxy> youtube_video_media_proxy; @@ -3109,7 +3110,8 @@ namespace QuickMedia { for(int i = 0; i < num_retries; ++i) { bool cancelled = false; TaskResult load_result = run_task_with_loading_screen([&]() { - if(video_page->load(new_title, channel_url, media_chapters, err_str) != PluginResult::OK) + video_duration = 0.0; + if(video_page->load(new_title, channel_url, video_duration, media_chapters, err_str) != PluginResult::OK) return false; std::string ext; @@ -3328,18 +3330,17 @@ namespace QuickMedia { } else if(strcmp(event_name, "playback-restart") == 0) { //video_player->set_paused(false); } else if(strcmp(event_name, "start-file") == 0) { + update_duration = true; added_recommendations = false; time_watched_timer.restart(); video_loaded = true; - if(video_page->is_local()) - update_time_pos = true; + update_time_pos = true; } else if(strcmp(event_name, "file-loaded") == 0) { video_loaded = true; } else if(strcmp(event_name, "video-reconfig") == 0 || strcmp(event_name, "audio-reconfig") == 0) { video_loaded = true; } else if(strcmp(event_name, "seek") == 0) { - if(video_page->is_local()) - update_time_pos = true; + update_time_pos = true; } else if(strcmp(event_name, "fullscreen") == 0 && args.size() == 1) { window_set_fullscreen(disp, window.get_system_handle(), args[0] == "yes" ? WindowFullscreenState::SET : WindowFullscreenState::UNSET); } @@ -3465,6 +3466,8 @@ namespace QuickMedia { XSync(disp, False); show_notification("QuickMedia", "Failed to get related pages", Urgency::CRITICAL); } else if(related_pages_result == TaskResult::TRUE && !related_pages.empty()) { + video_page->set_watch_progress(video_time_pos, video_duration); + bool page_changed = false; double resume_start_time = 0.0; page_loop(related_pages, video_page->get_related_pages_first_tab(), [&](const std::vector<Tab> &new_tabs) { @@ -3654,6 +3657,13 @@ namespace QuickMedia { update_time_pos = false; video_player->get_time_in_file(&video_time_pos); } + + if(update_duration) { + update_duration = false; + double file_duration = 0.0; + video_player->get_duration_in_file(&file_duration); + video_duration = std::max(video_duration, file_duration); + } } if(video_player_window) { @@ -3679,8 +3689,7 @@ namespace QuickMedia { window_size.x = window_size_u.x; window_size.y = window_size_u.y; - if(video_page->is_local()) - video_page->set_watch_progress(video_time_pos); + video_page->set_watch_progress(video_time_pos, video_duration); } void Program::select_episode(BodyItem *item, bool start_from_beginning) { @@ -7525,7 +7534,8 @@ namespace QuickMedia { std::string channel_url; std::vector<MediaChapter> chapters; filename.clear(); - if(youtube_video_page->load(filename, channel_url, chapters, err_str) != PluginResult::OK) + double duration; + if(youtube_video_page->load(filename, channel_url, duration, chapters, err_str) != PluginResult::OK) return false; std::string ext; diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 3d3fcc7..5e34c8e 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -28,12 +28,12 @@ static ssize_t read_eintr(int fd, void *buffer, size_t size) { } } -static ssize_t write_all(int fd, const void *buffer, size_t size) { +static ssize_t write_all_blocking(int fd, const void *buffer, size_t size) { ssize_t bytes_written = 0; while((size_t)bytes_written < size) { ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written); if(written == -1) { - if(errno != EINTR) + if(errno != EINTR && errno != EWOULDBLOCK) return -1; } else { bytes_written += written; @@ -451,6 +451,19 @@ namespace QuickMedia { return err; } + VideoPlayer::Error VideoPlayer::get_duration_in_file(double *result) { + Json::Value json_root(Json::objectValue); + json_root["command"] = "duration"; + + Json::Value duration_json; + Error err = send_command(json_root, &duration_json, Json::ValueType::realValue); + if(err != Error::OK) + return err; + + *result = duration_json.asDouble(); + return err; + } + VideoPlayer::Error VideoPlayer::add_subtitle(const std::string &url, const std::string &title, const std::string &lang) { Json::Value data_json(Json::objectValue); data_json["file"] = url; @@ -496,7 +509,7 @@ namespace QuickMedia { builder["indentation"] = ""; const std::string cmd_str = Json::writeString(builder, json_root) + "\n"; - if(write_all(ipc_socket, cmd_str.data(), cmd_str.size()) == -1) { + if(write_all_blocking(ipc_socket, cmd_str.data(), cmd_str.size()) == -1) { fprintf(stderr, "Failed to send to ipc socket, error: %s, command: %.*s\n", strerror(errno), (int)cmd_str.size(), cmd_str.c_str()); return Error::FAIL_TO_SEND; } diff --git a/src/plugins/Lbry.cpp b/src/plugins/Lbry.cpp index 73c37ba..feac2c5 100644 --- a/src/plugins/Lbry.cpp +++ b/src/plugins/Lbry.cpp @@ -328,7 +328,7 @@ namespace QuickMedia { } static PluginResult video_get_stream_url(Page *page, const std::string &video_url, std::string &streaming_url, std::string &err_str) { - std::string url = "https://api.na-backend.odysee.com/api/v1/proxy?m=resolve"; + std::string url = "https://api.na-backend.odysee.com/api/v1/proxy?m=get"; Json::Value request_params_json(Json::objectValue); request_params_json["save_file"] = false; @@ -391,9 +391,10 @@ namespace QuickMedia { return ""; } - PluginResult LbryVideoPage::load(std::string &title, std::string&, std::vector<MediaChapter>&, std::string &err_str) { + PluginResult LbryVideoPage::load(std::string &title, std::string&, double &duration, std::vector<MediaChapter>&, std::string &err_str) { streaming_url.clear(); title = this->title; + duration = 0.0; return video_get_stream_url(this, url, streaming_url, err_str); } }
\ No newline at end of file diff --git a/src/plugins/LocalAnime.cpp b/src/plugins/LocalAnime.cpp index 0de33b4..1bc8ca8 100644 --- a/src/plugins/LocalAnime.cpp +++ b/src/plugins/LocalAnime.cpp @@ -490,7 +490,7 @@ namespace QuickMedia { } } - void LocalAnimeVideoPage::set_watch_progress(int64_t time_pos_sec) { + void LocalAnimeVideoPage::set_watch_progress(int64_t time_pos_sec, int64_t) { std::string filename_relative_to_anime_dir = anime_path_to_item_name(url); FileAnalyzer file_analyzer; diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp index 5ee5fbc..c1044b0 100644 --- a/src/plugins/MediaGeneric.cpp +++ b/src/plugins/MediaGeneric.cpp @@ -224,8 +224,9 @@ namespace QuickMedia { return video_url; } - PluginResult MediaGenericVideoPage::load(std::string&, std::string&, std::vector<MediaChapter>&, std::string &err_msg) { + PluginResult MediaGenericVideoPage::load(std::string&, std::string&, double &duration, std::vector<MediaChapter>&, std::string &err_msg) { video_url.clear(); + duration = 0.0; if(!search_page->video_custom_handler) { video_url = url; return PluginResult::OK; diff --git a/src/plugins/Peertube.cpp b/src/plugins/Peertube.cpp index a5c5865..a9620f2 100644 --- a/src/plugins/Peertube.cpp +++ b/src/plugins/Peertube.cpp @@ -370,7 +370,7 @@ namespace QuickMedia { } // TODO: Media chapters - PluginResult PeertubeVideoPage::load(std::string &title, std::string &channel_url, std::vector<MediaChapter>&, std::string &err_str) { + PluginResult PeertubeVideoPage::load(std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter>&, std::string &err_str) { Json::Value json_root; std::string err_msg; DownloadResult download_result = download_json(json_root, server + "/api/v1/videos/" + url, {}, true, &err_msg); @@ -386,6 +386,10 @@ namespace QuickMedia { if(name_json.isString()) title = name_json.asString(); + const Json::Value &duration_json = json_root["duration"]; + if(duration_json.isInt64()) + duration = duration_json.asInt64(); + const Json::Value &channel_json = json_root["channel"]; if(channel_json.isObject()) { const Json::Value &channel_url_json = channel_json["url"]; diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp index 9da65ee..0412aa6 100644 --- a/src/plugins/Soundcloud.cpp +++ b/src/plugins/Soundcloud.cpp @@ -468,8 +468,9 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult SoundcloudAudioPage::load(std::string &title, std::string&, std::vector<MediaChapter>&, std::string&) { + PluginResult SoundcloudAudioPage::load(std::string &title, std::string&, double &duration, std::vector<MediaChapter>&, std::string&) { title = this->title; + duration = 0.0; return PluginResult::OK; } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index d80d86c..2d92cd8 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -7,6 +7,7 @@ #include "../../include/VideoPlayer.hpp" #include "../../include/Utils.hpp" #include "../../include/Theme.hpp" +#include "../../plugins/WatchProgress.hpp" #include <optional> #include <json/reader.h> extern "C" { @@ -1858,6 +1859,30 @@ namespace QuickMedia { VideoPage::set_url(std::move(new_url)); } + std::string YoutubeVideoPage::get_url_timestamp() { + if(!timestamp.empty()) + return timestamp; + + std::string video_id; + if(!youtube_url_extract_id(url, video_id)) { + fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str()); + return ""; + } + + std::unordered_map<std::string, WatchProgress> watch_progress = get_watch_progress_for_plugin("youtube"); + auto it = watch_progress.find(video_id); + if(it == watch_progress.end()) + return ""; + + // If we are very close to the end then start from the beginning. + // This is the same behavior as mpv. + // This is better because we dont want the video player to stop immediately after we start playing and we dont get any chance to seek. + if(it->second.time_pos_sec + 10.0 >= it->second.duration_sec) + return ""; + else + return std::to_string(it->second.time_pos_sec); + } + BodyItems YoutubeVideoPage::get_related_media(const std::string &url) { comments_continuation_token.clear(); BodyItems result_items; @@ -2213,6 +2238,7 @@ namespace QuickMedia { video_details.author.clear(); video_details.views.clear(); video_details.description.clear(); + video_details.duration = 0.0; } PluginResult YoutubeVideoPage::parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) { @@ -2274,11 +2300,18 @@ namespace QuickMedia { const Json::Value &author_json = video_details_json["author"]; const Json::Value &view_count_json = video_details_json["viewCount"]; const Json::Value &short_description_json = video_details_json["shortDescription"]; + const Json::Value &length_seconds_json = video_details_json["lengthSeconds"]; if(title_json.isString()) video_details.title = title_json.asString(); if(author_json.isString()) video_details.author = author_json.asString(); if(view_count_json.isString()) video_details.views = view_count_json.asString(); if(short_description_json.isString()) video_details.description = short_description_json.asString(); + if(length_seconds_json.isString()) { + const char *length_seconds_str = length_seconds_json.asCString(); + int duration = 0; + to_num(length_seconds_str, strlen(length_seconds_str), duration); + video_details.duration = duration; + } title = video_details.title; if(!video_details.description.empty()) @@ -2333,7 +2366,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) { + PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) { std::string video_id; if(!youtube_url_extract_id(url, video_id)) { fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str()); @@ -2408,6 +2441,7 @@ R"END( PluginResult result = parse_video_response(json_root, title, channel_url, chapters, err_str); if(result == PluginResult::OK) { err_str.clear(); + duration = video_details.duration; return PluginResult::OK; } } @@ -2483,15 +2517,6 @@ R"END( if(!format.isObject()) continue; - if(is_adaptive) { - // TODO: Fix. Some streams use &sq=num instead of index - const Json::Value &index_range_json = format["indexRange"]; - if(index_range_json.isNull()) { - fprintf(stderr, "Ignoring adaptive stream without indexRange\n"); - continue; - } - } - // TODO: Support HDR? const Json::Value &quality_label_json = format["qualityLabel"]; if(quality_label_json.isString() && strstr(quality_label_json.asCString(), "HDR")) continue; @@ -2574,4 +2599,13 @@ R"END( const Json::Value &adaptive_formats_json = streaming_data_json["adaptiveFormats"]; parse_format(adaptive_formats_json, true); } + + void YoutubeVideoPage::set_watch_progress(int64_t time_pos_sec, int64_t duration_sec) { + std::string video_id; + if(!youtube_url_extract_id(url, video_id)) { + show_notification("QuickMedia", "Failed to extract youtube id from " + url); + return; + } + set_watch_progress_for_plugin("youtube", video_id, time_pos_sec, duration_sec, video_id); + } } |