aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--include/VideoPlayer.hpp17
-rw-r--r--plugins/Youtube.hpp2
-rw-r--r--src/QuickMedia.cpp84
-rw-r--r--src/VideoPlayer.cpp121
5 files changed, 162 insertions, 67 deletions
diff --git a/README.md b/README.md
index 2b5f40b..40b8493 100644
--- a/README.md
+++ b/README.md
@@ -18,19 +18,16 @@ See project.conf \[dependencies].
`youtube-dl` needs to be installed to play videos from youtube.\
`notify-send` needs to be installed to show notifications (on Linux and other systems that uses d-bus notification system).
# TODO
-Fix x11 freeze that sometimes happens when playing video.\
If a search returns no results, then "No results found for ..." should be shown and navigation should go back to searching with suggestions.\
-Give user the option to start where they left off or from the start.\
+Give user the option to start where they left off or from the start or from the start.\
For manga, view the next chapter when reaching the end of a chapter.\
Search is asynchronous, but download of image also needs to be asynchronous, also add loading animation.\
Retain search text when navigating back.\
Disable ytdl_hook subtitles. If a video has subtitles for many languages, then it will stall video playback for several seconds
until all subtitles have been downloaded and loaded.
-Figure out why memory usage doesn't drop much when killing the video player. Is it a bug in proprietary nvidia drivers on gnu/linux?\
Add grid-view when thumbnails are visible.\
Add scrollbar.\
Add option to scale image to window size.\
-If you search too fast the search suggestion wont show up and when you press enter it will clear and you wont progress.
The search should wait until there are search results before clearing the search field and selecting the search suggestion.\
Full-screening a video doesn't work.\
Somehow deal with youtube banning ip when searching too often. \ No newline at end of file
diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp
index 2266a16..3839530 100644
--- a/include/VideoPlayer.hpp
+++ b/include/VideoPlayer.hpp
@@ -29,6 +29,7 @@ namespace QuickMedia {
UNEXPECTED_WINDOW_ERROR,
FAIL_TO_READ,
READ_TIMEOUT,
+ READ_RESPONSE_ERROR,
READ_INCORRECT_TYPE,
INIT_FAILED
};
@@ -48,9 +49,18 @@ namespace QuickMedia {
// Progress is in range [0..1]
Error get_progress(double *result);
+ Error get_time_remaining(double *result);
+ Error set_paused(bool paused);
+
// Progress is in range [0..1]
Error set_progress(double progress);
+
+ Error is_seekable(bool *result);
+
+ bool is_connected() const { return connected_to_ipc; }
private:
+ Error set_property(const std::string &property_name, const Json::Value &value);
+ Error get_property(const std::string &property_name, Json::Value *result, Json::ValueType result_type);
Error send_command(const char *cmd, size_t size);
Error launch_video_process(const char *path, sf::WindowHandle parent_window);
VideoPlayer::Error read_ipc_func();
@@ -71,5 +81,12 @@ namespace QuickMedia {
unsigned int request_id;
unsigned int expected_request_id;
Json::Value request_response_data;
+
+ enum ResponseDataStatus {
+ NONE,
+ OK,
+ ERROR
+ };
+ ResponseDataStatus response_data_status;
};
}
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index 16f5870..4ca7955 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -9,7 +9,7 @@ namespace QuickMedia {
BodyItems get_related_media(const std::string &url) override;
bool search_suggestions_has_thumbnails() const override { return true; }
bool search_results_has_thumbnails() const override { return false; }
- int get_search_delay() const override { return 500; }
+ int get_search_delay() const override { return 350; }
bool search_suggestion_is_search() const override { return true; }
Page get_page_after_search() const override { return Page::VIDEO_CONTENT; }
private:
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index ba9abf7..e80fde6 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -391,43 +391,13 @@ namespace QuickMedia {
video_player_ui_window->setVerticalSyncEnabled(true);
};
- // This variable is needed because calling play_video is not possible in onPlaybackEndedCallback
- bool play_next_video = false;
bool ui_resize = true;
+ bool seekable = false;
std::unique_ptr<VideoPlayer> video_player;
- auto play_video = [this, &video_player, &play_next_video, &on_window_create, &video_player_ui_window, &ui_resize]() {
+ auto load_video_error_check = [this, &video_player]() {
watched_videos.insert(content_url);
- video_player = std::make_unique<VideoPlayer>([this, &play_next_video, &video_player_ui_window, &ui_resize](const char *event_name) {
- if(strcmp(event_name, "end-file") == 0) {
- video_player_ui_window = nullptr;
- ui_resize = true;
-
- std::string new_video_url;
- BodyItems related_media = current_plugin->get_related_media(content_url);
- // Find video that hasn't been played before in this video session
- for(auto it = related_media.begin(), end = related_media.end(); it != end; ++it) {
- if(watched_videos.find((*it)->url) == watched_videos.end()) {
- new_video_url = (*it)->url;
- break;
- }
- }
-
- // If there are no videos to play, then dont play any...
- if(new_video_url.empty()) {
- show_notification("Video player", "No more related videos to play");
- current_page = Page::SEARCH_SUGGESTION;
- return;
- }
-
- content_url = std::move(new_video_url);
- play_next_video = true;
- // TODO: This doesn't seem to work correctly right now, it causes video to become black when changing video (context reset bug).
- //video_player->load_file(video_url);
- }
- }, on_window_create);
-
VideoPlayer::Error err = video_player->load_video(content_url.c_str(), window.getSystemHandle());
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
@@ -436,7 +406,41 @@ namespace QuickMedia {
current_page = Page::SEARCH_SUGGESTION;
}
};
- play_video();
+
+ video_player = std::make_unique<VideoPlayer>([this, &video_player, &seekable, &load_video_error_check](const char *event_name) {
+ bool end_of_file = false;
+ if(strcmp(event_name, "pause") == 0) {
+ double time_remaining = 0.0;
+ if(video_player->get_time_remaining(&time_remaining) == VideoPlayer::Error::OK && time_remaining < 0.1)
+ end_of_file = true;
+ } else if(strcmp(event_name, "playback-restart") == 0) {
+ video_player->set_paused(false);
+ video_player->is_seekable(&seekable);
+ }
+
+ if(end_of_file) {
+ std::string new_video_url;
+ BodyItems related_media = current_plugin->get_related_media(content_url);
+ // Find video that hasn't been played before in this video session
+ for(auto it = related_media.begin(), end = related_media.end(); it != end; ++it) {
+ if(watched_videos.find((*it)->url) == watched_videos.end()) {
+ new_video_url = (*it)->url;
+ break;
+ }
+ }
+
+ // If there are no videos to play, then dont play any...
+ if(new_video_url.empty()) {
+ show_notification("Video player", "No more related videos to play");
+ current_page = Page::SEARCH_SUGGESTION;
+ return;
+ }
+
+ content_url = std::move(new_video_url);
+ load_video_error_check();
+ }
+ }, on_window_create);
+ load_video_error_check();
auto on_doubleclick = []() {
// TODO: Toggle fullscreen of video here
@@ -444,7 +448,7 @@ namespace QuickMedia {
sf::Clock ui_hide_timer;
bool ui_visible = true;
- const int UI_HIDE_TIMEOUT = 3000;
+ const int UI_HIDE_TIMEOUT = 4500;
sf::Clock time_since_last_left_click;
int left_click_counter;
@@ -461,11 +465,6 @@ namespace QuickMedia {
window.display();
while (current_page == Page::VIDEO_CONTENT) {
- if(play_next_video) {
- play_next_video = false;
- play_video();
- }
-
while (window.pollEvent(event)) {
base_event_handler(event, Page::SEARCH_SUGGESTION);
if(event.type == sf::Event::Resized) {
@@ -517,7 +516,7 @@ namespace QuickMedia {
//window.clear();
//window.display();
- if(get_progress_timer.getElapsedTime().asMilliseconds() >= 500) {
+ if(video_player->is_connected() && get_progress_timer.getElapsedTime().asMilliseconds() >= 500) {
get_progress_timer.restart();
video_player->get_progress(&progress);
}
@@ -549,7 +548,10 @@ namespace QuickMedia {
if(sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
auto mouse_pos = sf::Mouse::getPosition(window);
if(mouse_pos.y >= window_size.y - ui_height) {
- video_player->set_progress((double)mouse_pos.x / (double)window_size.x);
+ if(seekable)
+ video_player->set_progress((double)mouse_pos.x / (double)window_size.x);
+ else
+ fprintf(stderr, "Video is not seekable!\n"); // TODO: Show this to the user
}
}
} else {
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index 96b9ee2..79322b6 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -2,6 +2,7 @@
#include "../include/Program.h"
#include <string>
#include <json/reader.h>
+#include <json/writer.h>
#include <memory>
#include <assert.h>
@@ -30,7 +31,8 @@ namespace QuickMedia {
display(nullptr),
request_id(1),
expected_request_id(0),
- request_response_data(Json::nullValue)
+ request_response_data(Json::nullValue),
+ response_data_status(ResponseDataStatus::NONE)
{
display = XOpenDisplay(NULL);
if (!display)
@@ -62,9 +64,11 @@ namespace QuickMedia {
}
const std::string parent_window_str = std::to_string(parent_window);
- const char *args[] = { "mpv", /*"--keep-open=yes", "--keep-open-pause=no",*/ "--input-ipc-server", ipc_server_path,
+ const char *args[] = { "mpv", "--keep-open=yes", /*"--keep-open-pause=no",*/ "--input-ipc-server", ipc_server_path,
"--no-config", "--no-input-default-bindings", "--input-vo-keyboard=no", "--no-input-cursor",
"--cache-secs=120", "--demuxer-max-bytes=40M", "--demuxer-max-back-bytes=20M",
+ "--no-input-terminal",
+ "--no-osc",
/*"--vo=gpu", "--hwdec=auto",*/
"--wid", parent_window_str.c_str(), "--", path, nullptr };
if(exec_program_async(args, &video_process_id) != 0)
@@ -93,9 +97,17 @@ namespace QuickMedia {
if(video_process_id == -1)
return launch_video_process(path, _parent_window);
- std::string cmd = "loadfile ";
- cmd += path;
- return send_command(cmd.c_str(), cmd.size());
+ Json::Value command_data(Json::arrayValue);
+ command_data.append("loadfile");
+ command_data.append(path);
+ 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());
}
static std::vector<Window> get_child_window(Display *display, Window window) {
@@ -187,9 +199,16 @@ namespace QuickMedia {
if(json_reader->parse(buffer + start, buffer + i, &json_root, &json_errors)) {
const Json::Value &event = json_root["event"];
const Json::Value &request_id_json = json_root["request_id"];
- if(event.isString() && event_callback)
- event_callback(event.asCString());
- else if(expected_request_id != 0 && request_id_json.isNumeric() && request_id_json.asUInt() == expected_request_id) {
+ if(event.isString()) {
+ if(event_callback)
+ event_callback(event.asCString());
+ }
+
+ if(expected_request_id != 0 && request_id_json.isNumeric() && request_id_json.asUInt() == expected_request_id) {
+ if(json_root["error"].isNull())
+ response_data_status = ResponseDataStatus::ERROR;
+ else
+ response_data_status = ResponseDataStatus::OK;
request_response_data = json_root["data"];
}
} else {
@@ -208,15 +227,60 @@ namespace QuickMedia {
}
VideoPlayer::Error VideoPlayer::get_progress(double *result) {
+ Json::Value percent_pos_json;
+ Error err = get_property("percent-pos", &percent_pos_json, Json::realValue);
+ if(err != Error::OK)
+ return err;
+
+ *result = percent_pos_json.asDouble() * 0.01;
+ return err;
+ }
+
+ VideoPlayer::Error VideoPlayer::get_time_remaining(double *result) {
+ Json::Value time_remaining_json;
+ Error err = get_property("time-remaining", &time_remaining_json, Json::realValue);
+ if(err != Error::OK)
+ return err;
+
+ *result = time_remaining_json.asDouble();
+ return err;
+ }
+
+ VideoPlayer::Error VideoPlayer::set_property(const std::string &property_name, const Json::Value &value) {
+ Json::Value command_data(Json::arrayValue);
+ command_data.append("set_property");
+ command_data.append(property_name);
+ command_data.append(value);
+ 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::get_property(const std::string &property_name, Json::Value *result, Json::ValueType result_type) {
unsigned int cmd_request_id = request_id;
++request_id;
// Overflow check. 0 is defined as no request, 1 is the first valid one
if(request_id == 0)
request_id = 1;
- std::string cmd = "{ \"command\": [\"get_property\", \"percent-pos\"], \"request_id\": ";
- cmd += std::to_string(cmd_request_id) + " }\n";
- Error err = send_command(cmd.c_str(), cmd.size());
+ Json::Value command_data(Json::arrayValue);
+ command_data.append("get_property");
+ command_data.append(property_name);
+ Json::Value command(Json::objectValue);
+ command["command"] = command_data;
+ command["request_id"] = cmd_request_id;
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+ const std::string cmd_str = Json::writeString(builder, command) + "\n";
+
+ Error err = send_command(cmd_str.c_str(), cmd_str.size());
if(err != Error::OK)
return err;
@@ -227,35 +291,50 @@ namespace QuickMedia {
if(err != Error::OK)
goto cleanup;
- if(!request_response_data.isNull())
+ if(response_data_status != ResponseDataStatus::NONE)
break;
} while(read_timer.getElapsedTime().asMilliseconds() < READ_TIMEOUT_MS);
- if(request_response_data.isNull()) {
- err = Error::READ_TIMEOUT;
+ if(response_data_status == ResponseDataStatus::OK) {
+ if(request_response_data.type() == result_type)
+ *result = request_response_data;
+ else
+ err = Error::READ_INCORRECT_TYPE;
+ } else if(response_data_status == ResponseDataStatus::ERROR) {
+ err = Error::READ_RESPONSE_ERROR;
goto cleanup;
- }
-
- if(!request_response_data.isDouble()) {
- fprintf(stderr, "Read: expected to receive data of type double for request id %u, was type %s\n", cmd_request_id, "TODO: Fill type here");
- err = Error::READ_INCORRECT_TYPE;
+ } else {
+ err = Error::READ_TIMEOUT;
goto cleanup;
}
- *result = request_response_data.asDouble() * 0.01;
-
cleanup:
expected_request_id = 0;
+ response_data_status = ResponseDataStatus::NONE;
request_response_data = Json::Value(Json::nullValue);
return err;
}
+ VideoPlayer::Error VideoPlayer::set_paused(bool paused) {
+ return set_property("pause", paused);
+ }
+
VideoPlayer::Error VideoPlayer::set_progress(double progress) {
std::string cmd = "{ \"command\": [\"set_property\", \"percent-pos\", ";
cmd += std::to_string(progress * 100.0) + "] }";
return send_command(cmd.c_str(), cmd.size());
}
+ VideoPlayer::Error VideoPlayer::is_seekable(bool *result) {
+ Json::Value seekable_json;
+ Error err = get_property("seekable", &seekable_json, Json::booleanValue);
+ if(err != Error::OK)
+ return err;
+
+ *result = seekable_json.asBool();
+ return err;
+ }
+
VideoPlayer::Error VideoPlayer::send_command(const char *cmd, size_t size) {
if(!connected_to_ipc)
return Error::FAIL_NOT_CONNECTED;