aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-06-11 06:33:35 +0200
committerdec05eba <dec05eba@protonmail.com>2021-06-11 06:34:10 +0200
commit6443ce7df2d690c5a03dc68cb6866f5d7d4e3fba (patch)
treed331799384d53372832d17e277e50da7cc8720f4 /src
parent89383cff1ba5d8a928262fcb4c40382a981c78c8 (diff)
Make get_related_videos async, readd mark-watched
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp175
-rw-r--r--src/plugins/MediaGeneric.cpp2
-rw-r--r--src/plugins/Youtube.cpp135
3 files changed, 199 insertions, 113 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 7479299..4b78117 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -2362,8 +2362,6 @@ namespace QuickMedia {
if(video_page->autoplay_next_item() && play_index + 1 >= 0 && play_index + 1 < (int)next_play_items.size())
related_videos.insert(related_videos.end(), next_play_items.begin() + play_index + 1, next_play_items.end());
- std::string channel_url;
-
sf::WindowHandle video_player_window = None;
auto on_window_create = [this, &video_player_window](sf::WindowHandle _video_player_window) mutable {
video_player_window = _video_player_window;
@@ -2371,11 +2369,18 @@ namespace QuickMedia {
XSync(disp, False);
};
+ std::string channel_url;
+ AsyncTask<BodyItems> video_tasks;
std::function<void(const char*)> video_event_callback;
- auto load_video_error_check = [this, &related_videos, &channel_url, &video_title, &video_player, 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 {
- TaskResult load_result = run_task_with_loading_screen([video_page]() {
- return video_page->load() == PluginResult::OK;
+ auto load_video_error_check = [this, &video_title, &video_tasks, &video_player, &channel_url, 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 {
+ video_player.reset();
+ channel_url.clear();
+ video_loaded = false;
+ video_player_window = None;
+
+ TaskResult load_result = run_task_with_loading_screen([video_page, &channel_url]() {
+ return video_page->load(channel_url) == PluginResult::OK;
});
if(load_result == TaskResult::CANCEL) {
@@ -2471,33 +2476,26 @@ namespace QuickMedia {
}
time_watched_timer.restart();
- video_loaded = false;
- video_player_window = None;
- watched_videos.insert(video_url);
+ watched_videos.insert(video_page->get_url());
video_player = std::make_unique<VideoPlayer>(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);
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
- err_msg += video_url;
+ err_msg += video_page->get_url();
show_notification("QuickMedia", err_msg.c_str(), Urgency::CRITICAL);
current_page = previous_page;
} else {
if(video_page->autoplay_next_item())
return;
- channel_url.clear();
- TaskResult load_related_media_result = run_task_with_loading_screen([video_page, &related_videos, &channel_url]{
- // TODO: Do async
- related_videos = video_page->get_related_media(video_page->get_url(), channel_url);
- return true;
+ std::string url = video_page->get_url();
+ video_tasks = AsyncTask<BodyItems>([video_page, url]() {
+ BodyItems related_videos = video_page->get_related_media(url);
+ video_page->mark_watched();
+ return related_videos;
});
- if(load_related_media_result == TaskResult::CANCEL) {
- current_page = previous_page;
- return;
- }
-
// TODO: Make this also work for other video plugins
if(strcmp(plugin_name, "youtube") != 0 || resume_video)
return;
@@ -2622,62 +2620,89 @@ namespace QuickMedia {
} 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_r && pressing_ctrl) {
- if(!cursor_visible)
- window.setMouseCursorVisible(true);
- cursor_visible = true;
-
- int search_delay = 0;
- auto search_page = video_page->create_search_page(this, search_delay);
- auto comments_page = video_page->create_comments_page(this);
- auto related_videos_page = video_page->create_related_videos_page(this);
- auto channels_page = video_page->create_channels_page(this, channel_url);
- if(search_page || related_videos_page || channels_page) {
+ bool cancelled = false;
+ if(video_tasks.valid()) {
XUnmapWindow(disp, video_player_window);
XSync(disp, False);
- std::vector<Tab> tabs;
- if(search_page) {
- tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", search_delay)});
- }
- if(comments_page) {
- tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr});
- }
- if(related_videos_page) {
- auto related_videos_body = create_body(false, true);
- related_videos_body->items = related_videos;
- tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
- }
- if(channels_page) {
- tabs.push_back(Tab{create_body(false, true), std::move(channels_page), create_search_bar("Search...", 350)});
- }
+ TaskResult task_result = run_task_with_loading_screen([&video_tasks, &related_videos]() {
+ while(true) {
+ if(program_is_dead_in_current_thread())
+ return false;
- bool page_changed = false;
- page_loop(tabs, 1, [this, &video_player, &page_changed](const std::vector<Tab> &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();
+ if(video_tasks.ready()) {
+ related_videos = video_tasks.get();
+ return true;
}
- page_changed = true;
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
- if(!window.isOpen() || current_page == PageType::EXIT)
- return;
+ XMapWindow(disp, video_player_window);
+ XSync(disp, False);
- if(page_changed) {
- current_page = PageType::VIDEO_CONTENT;
- load_video_error_check(true);
- } else {
- XMapWindow(disp, video_player_window);
+ if(task_result == TaskResult::CANCEL || task_result == TaskResult::FALSE)
+ cancelled = true;
+ }
+
+ if(!cancelled) {
+ if(!cursor_visible)
+ window.setMouseCursorVisible(true);
+ cursor_visible = true;
+
+ int search_delay = 0;
+ auto search_page = video_page->create_search_page(this, search_delay);
+ auto comments_page = video_page->create_comments_page(this);
+ auto related_videos_page = video_page->create_related_videos_page(this);
+ auto channels_page = video_page->create_channels_page(this, channel_url);
+ if(search_page || related_videos_page || channels_page) {
+ XUnmapWindow(disp, video_player_window);
XSync(disp, False);
+
+ std::vector<Tab> tabs;
+ if(search_page) {
+ tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", search_delay)});
+ }
+ if(comments_page) {
+ tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr});
+ }
+ if(related_videos_page) {
+ auto related_videos_body = create_body(false, true);
+ related_videos_body->items = related_videos;
+ tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ }
+ if(channels_page) {
+ tabs.push_back(Tab{create_body(false, true), std::move(channels_page), create_search_bar("Search...", 350)});
+ }
+
+ bool page_changed = false;
+ page_loop(tabs, 1, [this, &video_player, &page_changed](const std::vector<Tab> &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();
+ }
+ page_changed = true;
+ }
+ });
+
+ if(!window.isOpen() || current_page == PageType::EXIT)
+ return;
+
+ if(page_changed) {
+ current_page = PageType::VIDEO_CONTENT;
+ load_video_error_check(true);
+ } else {
+ XMapWindow(disp, video_player_window);
+ XSync(disp, False);
+ }
}
}
} else if(pressed_keysym == XK_c && pressing_ctrl) {
@@ -2702,6 +2727,26 @@ namespace QuickMedia {
std::string new_video_url;
std::string new_video_title;
+ if(video_tasks.valid()) {
+ TaskResult task_result = run_task_with_loading_screen([&video_tasks, &related_videos]() {
+ while(true) {
+ if(program_is_dead_in_current_thread())
+ return false;
+
+ if(video_tasks.ready()) {
+ related_videos = video_tasks.get();
+ return true;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ });
+
+ if(task_result == TaskResult::CANCEL || task_result == TaskResult::FALSE) {
+ current_page = previous_page;
+ return;
+ }
+ }
+
// Find video that hasn't been played before in this video session
auto find_next_video = [this, &related_videos, &video_page, &new_video_url, &new_video_title]() {
for(auto it = related_videos.begin(), end = related_videos.end(); it != end; ++it) {
diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp
index c3a8d8e..11b0957 100644
--- a/src/plugins/MediaGeneric.cpp
+++ b/src/plugins/MediaGeneric.cpp
@@ -180,7 +180,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- BodyItems MediaGenericVideoPage::get_related_media(const std::string &url, std::string&) {
+ BodyItems MediaGenericVideoPage::get_related_media(const std::string &url) {
BodyItems result_items;
search_page->get_related_media(url, result_items);
return result_items;
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 9cec69c..04c29e0 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -13,6 +13,7 @@ extern "C" {
#include <json/writer.h>
#include <string.h>
#include <unistd.h>
+#include <fcntl.h>
namespace QuickMedia {
bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) {
@@ -1691,7 +1692,7 @@ namespace QuickMedia {
return "";
}
- BodyItems YoutubeVideoPage::get_related_media(const std::string &url, std::string &channel_url) {
+ BodyItems YoutubeVideoPage::get_related_media(const std::string &url) {
BodyItems result_items;
std::string modified_url = remove_index_from_playlist_url(url);
@@ -1721,56 +1722,12 @@ namespace QuickMedia {
if(!json_item.isObject())
continue;
- const Json::Value &player_response_json = json_item["playerResponse"];
- if(channel_url.empty()) {
- if(player_response_json.isObject()) {
- const Json::Value &video_details_json = player_response_json["videoDetails"];
- if(video_details_json.isObject()) {
- const Json::Value &channel_id_json = video_details_json["channelId"];
- if(channel_id_json.isString())
- channel_url = "https://www.youtube.com/channel/" + channel_id_json.asString();
- }
- }
- }
-
if(xsrf_token.empty()) {
const Json::Value &xsrf_token_json = json_item["xsrf_token"];
if(xsrf_token_json.isString())
xsrf_token = xsrf_token_json.asString();
}
- if(player_response_json.isObject()) {
- const Json::Value &playback_tracing_json = player_response_json["playbackTracking"];
- if(playback_tracing_json.isObject()) {
- if(playback_url.empty()) {
- const Json::Value &video_stats_playback_url_json = playback_tracing_json["videostatsPlaybackUrl"];
- if(video_stats_playback_url_json.isObject()) {
- const Json::Value &base_url_json = video_stats_playback_url_json["baseUrl"];
- if(base_url_json.isString())
- playback_url = base_url_json.asString();
- }
- }
-
- if(watchtime_url.empty()) {
- const Json::Value &video_stats_watchtime_url_json = playback_tracing_json["videostatsWatchtimeUrl"];
- if(video_stats_watchtime_url_json.isObject()) {
- const Json::Value &base_url_json = video_stats_watchtime_url_json["baseUrl"];
- if(base_url_json.isString())
- watchtime_url = base_url_json.asString();
- }
- }
-
- if(tracking_url.empty()) {
- const Json::Value &p_tracking_url_json = playback_tracing_json["ptrackingUrl"];
- if(p_tracking_url_json.isObject()) {
- const Json::Value &base_url_json = p_tracking_url_json["baseUrl"];
- if(base_url_json.isString())
- tracking_url = base_url_json.asString();
- }
- }
- }
- }
-
const Json::Value &response_json = json_item["response"];
if(!response_json.isObject())
continue;
@@ -1900,7 +1857,7 @@ namespace QuickMedia {
return audio_formats.front().base.url;
}
- PluginResult YoutubeVideoPage::load() {
+ PluginResult YoutubeVideoPage::load(std::string &channel_url) {
video_formats.clear();
audio_formats.clear();
@@ -1919,7 +1876,7 @@ namespace QuickMedia {
additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
std::string response;
- DownloadResult download_result = download_to_string("https://www.youtube.com/get_video_info?html5=1&video_id=" + video_id + "&eurl=https://www.youtube.googleapis.com/v/" + video_id, response, std::move(additional_args), true); // TODO: true?
+ DownloadResult download_result = download_to_string("https://www.youtube.com/get_video_info?html5=1&video_id=" + video_id + "&eurl=https://www.youtube.googleapis.com/v/" + video_id, response, std::move(additional_args), true);
if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
std::string player_response_param = url_extract_param(response, "player_response");
@@ -1946,6 +1903,43 @@ namespace QuickMedia {
if(video_formats.empty() && audio_formats.empty())
return PluginResult::ERR;
+ const Json::Value &video_details_json = json_root["videoDetails"];
+ if(video_details_json.isObject()) {
+ const Json::Value &channel_id_json = video_details_json["channelId"];
+ if(channel_id_json.isString())
+ channel_url = "https://www.youtube.com/channel/" + channel_id_json.asString();
+ }
+
+ const Json::Value &playback_tracing_json = json_root["playbackTracking"];
+ if(playback_tracing_json.isObject()) {
+ if(playback_url.empty()) {
+ const Json::Value &video_stats_playback_url_json = playback_tracing_json["videostatsPlaybackUrl"];
+ if(video_stats_playback_url_json.isObject()) {
+ const Json::Value &base_url_json = video_stats_playback_url_json["baseUrl"];
+ if(base_url_json.isString())
+ playback_url = base_url_json.asString();
+ }
+ }
+
+ if(watchtime_url.empty()) {
+ const Json::Value &video_stats_watchtime_url_json = playback_tracing_json["videostatsWatchtimeUrl"];
+ if(video_stats_watchtime_url_json.isObject()) {
+ const Json::Value &base_url_json = video_stats_watchtime_url_json["baseUrl"];
+ if(base_url_json.isString())
+ watchtime_url = base_url_json.asString();
+ }
+ }
+
+ if(tracking_url.empty()) {
+ const Json::Value &p_tracking_url_json = playback_tracing_json["ptrackingUrl"];
+ if(p_tracking_url_json.isObject()) {
+ const Json::Value &base_url_json = p_tracking_url_json["baseUrl"];
+ if(base_url_json.isString())
+ tracking_url = base_url_json.asString();
+ }
+ }
+ }
+
std::sort(video_formats.begin(), video_formats.end(), [](const YoutubeVideoFormat &format1, const YoutubeVideoFormat &format2) {
return format1.base.bitrate > format2.base.bitrate;
});
@@ -1957,6 +1951,53 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ static bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) {
+ int fd = open("/dev/urandom", O_RDONLY);
+ if(fd == -1) {
+ perror("/dev/urandom");
+ return false;
+ }
+
+ if(read(fd, buffer, buffer_size) < buffer_size) {
+ fprintf(stderr, "Failed to read %d bytes from /dev/urandom\n", buffer_size);
+ close(fd);
+ return false;
+ }
+
+ for(int i = 0; i < buffer_size; ++i) {
+ unsigned char c = *(unsigned char*)&buffer[i];
+ buffer[i] = alphabet[c % alphabet_size];
+ }
+ close(fd);
+ return true;
+ }
+
+ void YoutubeVideoPage::mark_watched() {
+ if(playback_url.empty()) {
+ fprintf(stderr, "Failed to mark video as watched because playback_url is empty\n");
+ return;
+ }
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "x-youtube-client-name: 1" },
+ { "-H", "x-youtube-client-version: 2.20200626.03.00" }
+ };
+
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+
+ std::string cpn;
+ cpn.resize(16);
+ generate_random_characters(cpn.data(), cpn.size(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", 64);
+
+ std::string response;
+ DownloadResult download_result = download_to_string(playback_url + "&ver=2&cpn=" + cpn, response, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK) {
+ fprintf(stderr, "Failed to mark video as watched because http request failed\n");
+ return;
+ }
+ }
+
static bool parse_cipher_format(const Json::Value &format, YoutubeFormat &youtube_format) {
std::map<std::string, std::string> cipher_params;
const Json::Value &cipher_json = format["cipher"];