diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | include/Body.hpp | 4 | ||||
-rw-r--r-- | include/QuickMedia.hpp | 4 | ||||
-rw-r--r-- | include/VideoPlayer.hpp | 31 | ||||
-rw-r--r-- | plugins/Manganelo.hpp | 4 | ||||
-rw-r--r-- | plugins/Plugin.hpp | 6 | ||||
-rw-r--r-- | plugins/Youtube.hpp | 6 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 158 | ||||
-rw-r--r-- | src/SearchBar.cpp | 1 | ||||
-rw-r--r-- | src/VideoPlayer.cpp | 188 | ||||
-rw-r--r-- | src/plugins/Manganelo.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Plugin.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 14 |
13 files changed, 302 insertions, 131 deletions
@@ -21,7 +21,7 @@ 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.\ For manga, view the next chapter when reaching the end of a chapter.\ -Make network requests asynchronous to not freeze gui when navigating. Also have loading animation.\ +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. @@ -31,5 +31,4 @@ 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.\ -Currently the video player doesn't have any UI and the only input that works is `ESC` (exit video) and `space` (toggle pause). -Also full-screening a video doesn't work.
\ No newline at end of file +Full-screening a video doesn't work.
\ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index 5d16898..a2db62a 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -20,6 +20,8 @@ namespace QuickMedia { bool visible; }; + using BodyItems = std::vector<std::unique_ptr<BodyItem>>; + class Body { public: Body(sf::Font &font); @@ -46,7 +48,7 @@ namespace QuickMedia { sf::Text title_text; sf::Text progress_text; int selected_item; - std::vector<std::unique_ptr<BodyItem>> items; + BodyItems items; std::thread thumbnail_load_thread; bool draw_thumbnails; private: diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 7534439..90ceddd 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Body.hpp" #include "SearchBar.hpp" #include "Page.hpp" #include "Storage.hpp" @@ -9,9 +10,9 @@ #include <SFML/Graphics/RenderWindow.hpp> #include <json/value.h> #include <unordered_set> +#include <future> namespace QuickMedia { - class Body; class Plugin; class Program { @@ -43,5 +44,6 @@ namespace QuickMedia { Path content_storage_file; Json::Value content_storage_json; std::unordered_set<std::string> watched_videos; + std::future<BodyItems> search_suggestion_future; }; }
\ No newline at end of file diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index 9d30dc5..2266a16 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -4,12 +4,14 @@ #include <SFML/System/Clock.hpp> #include <stdio.h> #include <functional> -#include <thread> +#include <json/value.h> #include <sys/un.h> +#include <X11/Xlib.h> namespace QuickMedia { using EventCallbackFunc = std::function<void(const char *event_name)>; + using VideoPlayerWindowCreateCallback = std::function<void(sf::WindowHandle window)>; // Currently this video player launches mpv and embeds it into the QuickMedia window class VideoPlayer { @@ -22,11 +24,17 @@ namespace QuickMedia { FAIL_TO_CONNECT_TIMEOUT, FAIL_NOT_CONNECTED, FAIL_TO_SEND, + FAIL_TO_FIND_WINDOW, + FAIL_TO_FIND_WINDOW_TIMEOUT, + UNEXPECTED_WINDOW_ERROR, + FAIL_TO_READ, + READ_TIMEOUT, + READ_INCORRECT_TYPE, INIT_FAILED }; // @event_callback is called from another thread - VideoPlayer(EventCallbackFunc event_callback); + VideoPlayer(EventCallbackFunc event_callback, VideoPlayerWindowCreateCallback window_create_callback); ~VideoPlayer(); VideoPlayer(const VideoPlayer&) = delete; VideoPlayer& operator=(const VideoPlayer&) = delete; @@ -37,20 +45,31 @@ namespace QuickMedia { Error update(); Error toggle_pause(); + + // Progress is in range [0..1] + Error get_progress(double *result); + // Progress is in range [0..1] + Error set_progress(double progress); private: Error send_command(const char *cmd, size_t size); Error launch_video_process(const char *path, sf::WindowHandle parent_window); - void read_ipc_func(); + VideoPlayer::Error read_ipc_func(); private: pid_t video_process_id; int ipc_socket; bool connected_to_ipc; - sf::Clock ipc_connect_retry_timer; + sf::Clock retry_timer; int connect_tries; + int find_window_tries; struct sockaddr_un ipc_addr; char ipc_server_path[L_tmpnam]; EventCallbackFunc event_callback; - std::thread event_read_thread; - bool alive; + VideoPlayerWindowCreateCallback window_create_callback; + sf::WindowHandle window_handle; + sf::WindowHandle parent_window; + Display *display; + unsigned int request_id; + unsigned int expected_request_id; + Json::Value request_response_data; }; } diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp index 5720199..ab3bb3f 100644 --- a/plugins/Manganelo.hpp +++ b/plugins/Manganelo.hpp @@ -5,8 +5,8 @@ namespace QuickMedia { class Manganelo : public Plugin { public: - SearchResult search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items) override; - SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) override; + SearchResult search(const std::string &url, BodyItems &result_items) override; + SuggestionResult update_search_suggestions(const std::string &text, BodyItems &result_items) override; ImageResult get_image_by_index(const std::string &url, int index, std::string &image_data); ImageResult get_number_of_images(const std::string &url, int &num_images); bool search_suggestions_has_thumbnails() const override { return true; } diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp index 54d49e5..a999d36 100644 --- a/plugins/Plugin.hpp +++ b/plugins/Plugin.hpp @@ -44,9 +44,9 @@ namespace QuickMedia { public: virtual ~Plugin() = default; - virtual SearchResult search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items); - virtual SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items); - virtual std::vector<std::unique_ptr<BodyItem>> get_related_media(const std::string &url); + virtual SearchResult search(const std::string &text, BodyItems &result_items); + virtual SuggestionResult update_search_suggestions(const std::string &text, BodyItems &result_items); + virtual BodyItems get_related_media(const std::string &url); virtual bool search_suggestions_has_thumbnails() const = 0; virtual bool search_results_has_thumbnails() const = 0; virtual int get_search_delay() const = 0; diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index 64a0d6b..f459630 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -5,11 +5,11 @@ namespace QuickMedia { class Youtube : public Plugin { public: - SuggestionResult update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) override; - std::vector<std::unique_ptr<BodyItem>> get_related_media(const std::string &url) override; + SuggestionResult update_search_suggestions(const std::string &text, BodyItems &result_items) override; + 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 250; } + int get_search_delay() const override { return 150; } bool search_suggestion_is_search() const override { return true; } Page get_page_after_search() const override { return Page::VIDEO_CONTENT; } }; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index cb58a1d..32e0b2d 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -16,7 +16,6 @@ #include <cmath> #include <string.h> #include <X11/Xlib.h> -#include <X11/Xatom.h> #include <signal.h> const sf::Color front_color(43, 45, 47); @@ -70,15 +69,6 @@ namespace QuickMedia { return search_result; } - static void update_search_suggestions(const sf::String &text, Body *body, Plugin *plugin) { - body->clear_items(); - if(text.isEmpty()) - return; - - SuggestionResult suggestion_result = plugin->update_search_suggestions(text, body->items); - body->clamp_selection(); - } - static void usage() { fprintf(stderr, "usage: QuickMedia <plugin>\n"); fprintf(stderr, "OPTIONS:\n"); @@ -216,8 +206,10 @@ namespace QuickMedia { } void Program::search_suggestion_page() { - search_bar->onTextUpdateCallback = [this](const std::string &text) { - update_search_suggestions(text, body, current_plugin); + std::string update_search_text; + bool search_running = false; + search_bar->onTextUpdateCallback = [&update_search_text](const std::string &text) { + update_search_text = text; }; search_bar->onTextSubmitCallback = [this](const std::string &text) { @@ -273,6 +265,23 @@ namespace QuickMedia { search_bar->update(); + if(!update_search_text.empty() && !search_running) { + search_suggestion_future = std::async(std::launch::async, [this, update_search_text]() { + BodyItems result; + SuggestionResult suggestion_result = current_plugin->update_search_suggestions(update_search_text, result); + return result; + }); + update_search_text.clear(); + search_running = true; + } + + if(search_running && search_suggestion_future.valid() && search_suggestion_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + body->clear_items(); + body->items = search_suggestion_future.get(); + body->clamp_selection(); + search_running = false; + } + window.clear(back_color); body->draw(window, body_pos, body_size); search_bar->draw(window); @@ -353,27 +362,39 @@ namespace QuickMedia { throw std::runtime_error("Failed to open display to X11 server"); XDisplayScope display_scope(disp); - #if 0 - sf::RenderWindow video_window(sf::VideoMode(300, 300), "QuickMedia Video Player"); - video_window.setVerticalSyncEnabled(true); - XReparentWindow(disp, video_window.getSystemHandle(), window.getSystemHandle(), 0, 0); - XMapWindow(disp, video_window.getSystemHandle()); - XSync(disp, False); - video_window.setSize(sf::Vector2u(400, 300)); - #endif + std::unique_ptr<sf::RenderWindow> video_player_ui_window; + auto on_window_create = [disp, &video_player_ui_window](sf::WindowHandle video_player_window) { + int screen = DefaultScreen(disp); + Window ui_window = XCreateWindow(disp, RootWindow(disp, screen), + 0, 0, 1, 1, 0, + DefaultDepth(disp, screen), + InputOutput, + DefaultVisual(disp, screen), + 0, NULL); + + XReparentWindow(disp, ui_window, video_player_window, 0, 0); + XMapWindow(disp, ui_window); + XFlush(disp); + + video_player_ui_window = std::make_unique<sf::RenderWindow>(ui_window); + }; // This variable is needed because calling play_video is not possible in onPlaybackEndedCallback bool play_next_video = false; + bool ui_resize = true; std::unique_ptr<VideoPlayer> video_player; - auto play_video = [this, &video_player, &play_next_video]() { + auto play_video = [this, &video_player, &play_next_video, &on_window_create, &video_player_ui_window, &ui_resize]() { printf("Playing video: %s\n", content_url.c_str()); watched_videos.insert(content_url); - video_player = std::make_unique<VideoPlayer>([this, &play_next_video](const char *event_name) { + 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; - std::vector<std::unique_ptr<BodyItem>> related_media = current_plugin->get_related_media(content_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()) { @@ -394,7 +415,7 @@ namespace QuickMedia { // 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) { @@ -406,11 +427,18 @@ namespace QuickMedia { }; play_video(); + auto on_doubleclick = []() { + // TODO: Toggle fullscreen of video here + }; + sf::Clock time_since_last_left_click; int left_click_counter; sf::Event event; - sf::RectangleShape rect(sf::Vector2f(500, 500)); + sf::RectangleShape rect; + rect.setFillColor(sf::Color::Red); + sf::Clock get_progress_timer; + double progress = 0.0; while (current_page == Page::VIDEO_CONTENT) { if(play_next_video) { @@ -421,38 +449,16 @@ namespace QuickMedia { while (window.pollEvent(event)) { base_event_handler(event, Page::SEARCH_SUGGESTION); if(event.type == sf::Event::Resized) { - //video_window.setSize(sf::Vector2u(event.size.width, event.size.height)); - } else if(event.key.code == sf::Keyboard::Space) { + if(video_player_ui_window) + ui_resize = true; + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space) { if(video_player->toggle_pause() != VideoPlayer::Error::OK) { fprintf(stderr, "Failed to toggle pause!\n"); } - } - } - - #if 0 - while(video_window.pollEvent(event)) { - if (event.type == sf::Event::Closed) { - current_page = Page::EXIT; - } else if(event.type == sf::Event::Resized) { - sf::FloatRect visible_area(0, 0, event.size.width, event.size.height); - video_window.setView(sf::View(visible_area)); - } else if(event.type == sf::Event::KeyPressed) { - if(event.key.code == sf::Keyboard::Escape) { - current_page = Page::SEARCH_SUGGESTION; - return; - } - - if(event.key.code == sf::Keyboard::Space) { - if(video_player.toggle_pause() != VideoPlayer::Error::OK) { - fprintf(stderr, "Failed to toggle pause!\n"); - } - } - } - - if(event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) { + } else if(event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) { if(time_since_last_left_click.restart().asMilliseconds() <= DOUBLE_CLICK_TIME) { if(++left_click_counter == 2) { - //on_doubleclick(); + on_doubleclick(); left_click_counter = 0; } } else { @@ -460,21 +466,57 @@ namespace QuickMedia { } } } - #endif + + if(video_player_ui_window) { + while(video_player_ui_window->pollEvent(event)) { + if(event.type == sf::Event::Resized) { + sf::FloatRect visible_area(0, 0, event.size.width, event.size.height); + video_player_ui_window->setView(sf::View(visible_area)); + } + } + } VideoPlayer::Error update_err = video_player->update(); if(update_err == VideoPlayer::Error::FAIL_TO_CONNECT_TIMEOUT) { show_notification("Video player", "Failed to connect to mpv ipc after 5 seconds", Urgency::CRITICAL); current_page = Page::SEARCH_SUGGESTION; return; + } else if(update_err != VideoPlayer::Error::OK) { + show_notification("Video player", "Unexpected error while updating", Urgency::CRITICAL); + current_page = Page::SEARCH_SUGGESTION; + return; } - window.clear(); - window.display(); - // TODO: Show loading video animation - //video_window.clear(sf::Color::Red); - //video_window.display(); + //window.clear(); + //window.display(); + + if(get_progress_timer.getElapsedTime().asMilliseconds() >= 500) { + get_progress_timer.restart(); + video_player->get_progress(&progress); + } + + if(video_player_ui_window) { + const float ui_height = window_size.y * 0.01f; + if(ui_resize) { + ui_resize = false; + video_player_ui_window->setSize(sf::Vector2u(window_size.x, ui_height)); + video_player_ui_window->setPosition(sf::Vector2i(0, window_size.y - ui_height)); + } + + // TODO: Make window transparent, so the ui overlay for the video has transparency + video_player_ui_window->clear(sf::Color(33, 33, 33)); + rect.setSize(sf::Vector2f(window_size.x * progress, window_size.y * 0.01)); + video_player_ui_window->draw(rect); + video_player_ui_window->display(); + + 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); + } + } + } } } diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 1094883..b967224 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -31,6 +31,7 @@ namespace QuickMedia { void SearchBar::update() { if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= text_autosearch_delay) { + time_since_search_update.restart(); updated_search = false; sf::String str = text.getString(); if(show_placeholder) diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index e1c8f73..0e6735a 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -2,6 +2,7 @@ #include "../include/Program.h" #include <string> #include <json/reader.h> +#include <memory> #include <assert.h> #include <sys/socket.h> @@ -11,18 +12,29 @@ #include <signal.h> const int RETRY_TIME_MS = 1000; -const int MAX_RETRIES = 5; +const int MAX_RETRIES_CONNECT = 5; +const int MAX_RETRIES_FIND_WINDOW = 10; +const int READ_TIMEOUT_MS = 200; namespace QuickMedia { - VideoPlayer::VideoPlayer(EventCallbackFunc _event_callback) : + VideoPlayer::VideoPlayer(EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback) : video_process_id(-1), ipc_socket(-1), connected_to_ipc(false), connect_tries(0), + find_window_tries(0), event_callback(_event_callback), - alive(true) + window_create_callback(_window_create_callback), + window_handle(0), + parent_window(0), + display(nullptr), + request_id(1), + expected_request_id(0), + request_response_data(Json::nullValue) { - + display = XOpenDisplay(NULL); + if (!display) + throw std::runtime_error("Failed to open display to X11 server"); } VideoPlayer::~VideoPlayer() { @@ -35,12 +47,13 @@ namespace QuickMedia { if(video_process_id != -1) remove(ipc_server_path); - alive = false; - if(event_read_thread.joinable()) - event_read_thread.join(); + if(display) + XCloseDisplay(display); } - VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, sf::WindowHandle parent_window) { + VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, sf::WindowHandle _parent_window) { + parent_window = _parent_window; + if(!tmpnam(ipc_server_path)) { perror("Failed to generate ipc file name"); return Error::FAIL_TO_GENERATE_IPC_FILENAME; @@ -49,7 +62,7 @@ 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, "--no-config", "--no-input-default-bindings", "--input-vo-keyboard=no", "--no-input-cursor", - "--cache-secs=120", "--demuxer-max-bytes=20M", "--demuxer-max-back-bytes=10M", + "--cache-secs=120", "--demuxer-max-bytes=40M", "--demuxer-max-back-bytes=20M", /*"--vo=gpu", "--hwdec=auto",*/ "--wid", parent_window_str.c_str(), "--", path, nullptr }; if(exec_program_async(args, &video_process_id) != 0) @@ -71,40 +84,84 @@ namespace QuickMedia { return Error::OK; } - VideoPlayer::Error VideoPlayer::load_video(const char *path, sf::WindowHandle parent_window) { + VideoPlayer::Error VideoPlayer::load_video(const char *path, sf::WindowHandle _parent_window) { + // 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 not at least. + assert(parent_window == 0 || parent_window == _parent_window); if(video_process_id == -1) - return launch_video_process(path, parent_window); + return launch_video_process(path, _parent_window); std::string cmd = "loadfile "; cmd += path; return send_command(cmd.c_str(), cmd.size()); } + static std::vector<Window> get_child_window(Display *display, Window window) { + std::vector<Window> result; + Window root_window; + Window parent_window; + Window *child_window; + unsigned int num_children; + if(XQueryTree(display, window, &root_window, &parent_window, &child_window, &num_children) != 0) { + for(unsigned int i = 0; i < num_children; i++) + result.push_back(child_window[i]); + } + return result; + } + VideoPlayer::Error VideoPlayer::update() { if(ipc_socket == -1) return Error::INIT_FAILED; - if(connect_tries == MAX_RETRIES) + if(connect_tries == MAX_RETRIES_CONNECT) return Error::FAIL_TO_CONNECT_TIMEOUT; - if(!connected_to_ipc && ipc_connect_retry_timer.getElapsedTime().asMilliseconds() >= RETRY_TIME_MS) { + if(find_window_tries == MAX_RETRIES_FIND_WINDOW) + return Error::FAIL_TO_FIND_WINDOW; + + if(!connected_to_ipc && retry_timer.getElapsedTime().asMilliseconds() >= RETRY_TIME_MS) { + retry_timer.restart(); if(connect(ipc_socket, (struct sockaddr*)&ipc_addr, sizeof(ipc_addr)) == -1) { ++connect_tries; - if(connect_tries == MAX_RETRIES) { - fprintf(stderr, "Failed to connect to mpv ipc after 5 seconds, last error: %s\n", strerror(errno)); + if(connect_tries == MAX_RETRIES_CONNECT) { + fprintf(stderr, "Failed to connect to mpv ipc after %d seconds, last error: %s\n", RETRY_TIME_MS * MAX_RETRIES_CONNECT, strerror(errno)); return Error::FAIL_TO_CONNECT_TIMEOUT; } } else { connected_to_ipc = true; - if(event_callback) - event_read_thread = std::thread(&VideoPlayer::read_ipc_func, this); } } + if(connected_to_ipc && window_handle == 0 && retry_timer.getElapsedTime().asMilliseconds() >= RETRY_TIME_MS) { + retry_timer.restart(); + std::vector<Window> child_windows = get_child_window(display, parent_window); + size_t num_children = child_windows.size(); + if(num_children == 0) { + ++find_window_tries; + if(find_window_tries == MAX_RETRIES_FIND_WINDOW) { + fprintf(stderr, "Failed to find mpv window after %d seconds\n", RETRY_TIME_MS * MAX_RETRIES_FIND_WINDOW); + return Error::FAIL_TO_FIND_WINDOW_TIMEOUT; + } + } else if(num_children == 1) { + window_handle = child_windows[0]; + if(window_create_callback) + window_create_callback(window_handle); + } else { + fprintf(stderr, "Expected window to have one child (the video player) but it has %zu\n", num_children); + return Error::UNEXPECTED_WINDOW_ERROR; + } + } + + if(connected_to_ipc && event_callback) { + Error err = read_ipc_func(); + if(err != Error::OK) + return err; + } + return Error::OK; } - void VideoPlayer::read_ipc_func() { + VideoPlayer::Error VideoPlayer::read_ipc_func() { assert(connected_to_ipc); assert(event_callback); @@ -114,35 +171,35 @@ namespace QuickMedia { std::string json_errors; char buffer[2048]; - while(alive) { - ssize_t bytes_read = read(ipc_socket, buffer, sizeof(buffer)); - if(bytes_read == -1) { - int err = errno; - if(err == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + ssize_t bytes_read = read(ipc_socket, buffer, sizeof(buffer)); + if(bytes_read == -1) { + int err = errno; + if(err != EAGAIN) { + fprintf(stderr, "Failed to read from ipc socket, error: %s\n", strerror(err)); + return Error::FAIL_TO_READ; + } + } else if(bytes_read > 0) { + int start = 0; + for(int i = 0; i < bytes_read; ++i) { + if(buffer[i] != '\n') continue; - } - fprintf(stderr, "Failed to read from ipc socket, error: %s\n", strerror(err)); - break; - } else if(bytes_read > 0) { - int start = 0; - for(int i = 0; i < bytes_read; ++i) { - if(buffer[i] != '\n') - continue; - - if(json_reader->parse(buffer + start, buffer + i, &json_root, &json_errors)) { - const Json::Value &event = json_root["event"]; - if(event.isString()) - event_callback(event.asCString()); - } else { - fprintf(stderr, "Failed to parse json for ipc: |%.*s|, reason: %s\n", (int)bytes_read, buffer, json_errors.c_str()); + 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.asCString()); + else if(expected_request_id != 0 && request_id_json.isNumeric() && request_id_json.asUInt() == expected_request_id) { + request_response_data = json_root["data"]; } - - start = i + 1; + } else { + fprintf(stderr, "Failed to parse json for ipc: |%.*s|, reason: %s\n", (int)bytes_read, buffer, json_errors.c_str()); } + + start = i + 1; } } + return Error::OK; } VideoPlayer::Error VideoPlayer::toggle_pause() { @@ -150,6 +207,55 @@ namespace QuickMedia { return send_command(cmd, sizeof(cmd) - 1); } + VideoPlayer::Error VideoPlayer::get_progress(double *result) { + 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()); + if(err != Error::OK) + return err; + + sf::Clock read_timer; + expected_request_id = cmd_request_id; + do { + err = read_ipc_func(); + if(err != Error::OK) + goto cleanup; + + if(!request_response_data.isNull()) + break; + } while(read_timer.getElapsedTime().asMilliseconds() < READ_TIMEOUT_MS); + + if(request_response_data.isNull()) { + err = Error::READ_TIMEOUT; + 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; + goto cleanup; + } + + *result = request_response_data.asDouble() * 0.01; + + cleanup: + expected_request_id = 0; + request_response_data = Json::Value(Json::nullValue); + return err; + } + + 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::send_command(const char *cmd, size_t size) { if(!connected_to_ipc) return Error::FAIL_NOT_CONNECTED; diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index e91baf0..ec522d2 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -3,7 +3,7 @@ #include <json/reader.h> namespace QuickMedia { - SearchResult Manganelo::search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items) { + SearchResult Manganelo::search(const std::string &url, BodyItems &result_items) { std::string website_data; if(download_to_string(url, website_data) != DownloadResult::OK) return SearchResult::NET_ERR; @@ -15,7 +15,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='chapter-list']/div[class='row']//a", [](QuickMediaHtmlNode *node, void *userdata) { - auto *item_data = (std::vector<std::unique_ptr<BodyItem>>*)userdata; + auto *item_data = (BodyItems*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *text = quickmedia_html_node_get_text(node); if(href && text) { @@ -50,7 +50,7 @@ namespace QuickMedia { return true; } - SuggestionResult Manganelo::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) { + SuggestionResult Manganelo::update_search_suggestions(const std::string &text, BodyItems &result_items) { std::string url = "https://manganelo.com/home_json_search"; std::string search_term = "searchword="; search_term += url_param_encode(text); diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp index 5d81aad..79c6403 100644 --- a/src/plugins/Plugin.cpp +++ b/src/plugins/Plugin.cpp @@ -10,19 +10,19 @@ static int accumulate_string(char *data, int size, void *userdata) { } namespace QuickMedia { - SearchResult Plugin::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) { + SearchResult Plugin::search(const std::string &text, BodyItems &result_items) { (void)text; (void)result_items; return SearchResult::OK; } - SuggestionResult Plugin::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) { + SuggestionResult Plugin::update_search_suggestions(const std::string &text, BodyItems &result_items) { (void)text; (void)result_items; return SuggestionResult::OK; } - std::vector<std::unique_ptr<BodyItem>> Plugin::get_related_media(const std::string &url) { + BodyItems Plugin::get_related_media(const std::string &url) { (void)url; return {}; } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 2a3f011..56e8574 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -12,7 +12,7 @@ namespace QuickMedia { return strstr(str, substr); } - static void iterate_suggestion_result(const Json::Value &value, std::vector<std::unique_ptr<BodyItem>> &result_items, int &iterate_count) { + static void iterate_suggestion_result(const Json::Value &value, BodyItems &result_items, int &iterate_count) { ++iterate_count; if(value.isArray()) { for(const Json::Value &child : value) { @@ -26,7 +26,7 @@ namespace QuickMedia { } // TODO: Speed this up by using string.find instead of parsing html - SuggestionResult Youtube::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) { + SuggestionResult Youtube::update_search_suggestions(const std::string &text, BodyItems &result_items) { // Keep this for backup. This is using search suggestion the same way youtube does it, but the results // are not as good as doing an actual search. #if 0 @@ -79,7 +79,7 @@ namespace QuickMedia { return SuggestionResult::NET_ERR; struct ItemData { - std::vector<std::unique_ptr<BodyItem>> *result_items; + BodyItems *result_items; size_t index; }; ItemData item_data = { &result_items, 0 }; @@ -91,7 +91,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"yt-lockup-title\"]/a", [](QuickMediaHtmlNode *node, void *userdata) { - auto *result_items = (std::vector<std::unique_ptr<BodyItem>>*)userdata; + auto *result_items = (BodyItems*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *title = quickmedia_html_node_get_attribute_value(node, "title"); // Checking for watch?v helps skipping ads @@ -127,8 +127,8 @@ namespace QuickMedia { return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR; } - std::vector<std::unique_ptr<BodyItem>> Youtube::get_related_media(const std::string &url) { - std::vector<std::unique_ptr<BodyItem>> result_items; + BodyItems Youtube::get_related_media(const std::string &url) { + BodyItems result_items; std::string website_data; if(download_to_string(url, website_data) != DownloadResult::OK) @@ -141,7 +141,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//ul[class=\"video-list\"]//div[class=\"content-wrapper\"]/a", [](QuickMediaHtmlNode *node, void *userdata) { - auto *result_items = (std::vector<std::unique_ptr<BodyItem>>*)userdata; + auto *result_items = (BodyItems*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); // TODO: Also add title for related media if(href && begins_with(href, "/watch?v=")) { |