aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-03-08 16:39:55 +0100
committerdec05eba <dec05eba@protonmail.com>2022-03-08 16:39:55 +0100
commita26d0fcc0a30a28ce0e458ea275fc0787c693bc6 (patch)
treeffe2212b81d9fdc93b49383c102e7c0ece0d225a /src
parent21c50903a68c253fa5fcb9ed5ac8ba5abb1142b9 (diff)
Save youtube watch progress and resume next time the video is played
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp30
-rw-r--r--src/VideoPlayer.cpp19
-rw-r--r--src/plugins/Lbry.cpp5
-rw-r--r--src/plugins/LocalAnime.cpp2
-rw-r--r--src/plugins/MediaGeneric.cpp3
-rw-r--r--src/plugins/Peertube.cpp6
-rw-r--r--src/plugins/Soundcloud.cpp3
-rw-r--r--src/plugins/Youtube.cpp54
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);
+ }
}