diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | include/Program.h | 20 | ||||
-rw-r--r-- | include/VideoPlayer.hpp | 85 | ||||
-rw-r--r-- | project.conf | 2 | ||||
-rw-r--r-- | src/Program.c | 60 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 171 | ||||
-rw-r--r-- | src/VideoPlayer.cpp | 357 | ||||
-rw-r--r-- | src/plugins/Manganelo.cpp | 2 | ||||
-rw-r--r-- | src/plugins/Plugin.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 3 |
10 files changed, 369 insertions, 341 deletions
@@ -11,8 +11,9 @@ Press `space` to pause/unpause video. `Double-click` video to fullscreen or leav See project.conf \[dependencies]. ## Runtime ### Required -`curl` needs to be downloaded for network requests. +`curl` is required for network requests. ### Optional +`mpv` is required for playing videos. This is not required if you dont plan on playing videos.\ `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 @@ -29,4 +30,5 @@ 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.
\ No newline at end of file +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).
\ No newline at end of file diff --git a/include/Program.h b/include/Program.h index ba7e523..69ee564 100644 --- a/include/Program.h +++ b/include/Program.h @@ -1,10 +1,18 @@ #ifndef QUICKMEDIA_PROGRAM_H #define QUICKMEDIA_PROGRAM_H +#include <sys/types.h> + #ifdef __cplusplus extern "C" { #endif +typedef struct { + pid_t pid; + int read_fd; + int write_fd; +} ProgramPipe; + /* Return 0 if you want to continue reading. @data is null-terminated */ typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); @@ -14,6 +22,18 @@ typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); */ int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata); +/* + @args need to have at least 2 arguments. The first which is the program name + and the last which is NULL, which indicates end of args +*/ +int exec_program_async(const char **args, pid_t *result_process_id); +#if 0 + +int program_pipe_write(ProgramPipe *self, const char *data, size_t size); +int program_pipe_read(ProgramPipe *self, ProgramOutputCallback output_callback, void *userdata); +void program_pipe_close(ProgramPipe *self); +#endif + #ifdef __cplusplus } #endif diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index a9b177c..9d30dc5 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -1,63 +1,56 @@ #pragma once -#include <SFML/Graphics/RenderWindow.hpp> -#include <SFML/Window/Event.hpp> -#include <SFML/Graphics/Texture.hpp> -#include <SFML/Graphics/Sprite.hpp> -#include <SFML/Graphics/RectangleShape.hpp> -#include <SFML/Window/Context.hpp> -#include <thread> -#include <mutex> -#include <atomic> -#include <stdexcept> +#include <SFML/Window/WindowHandle.hpp> +#include <SFML/System/Clock.hpp> +#include <stdio.h> #include <functional> +#include <thread> -class mpv_handle; -class mpv_render_context; +#include <sys/un.h> namespace QuickMedia { - class VideoInitializationException : public std::runtime_error { - public: - VideoInitializationException(const std::string &errMsg) : std::runtime_error(errMsg) {} - }; + using EventCallbackFunc = std::function<void(const char *event_name)>; - using PlaybackEndedCallback = std::function<void()>; - + // Currently this video player launches mpv and embeds it into the QuickMedia window class VideoPlayer { public: - // Throws VideoInitializationException on error - VideoPlayer(sf::RenderWindow *window, unsigned int width, unsigned int height, const char *file, bool loop = false); - ~VideoPlayer(); + enum class Error { + OK, + FAIL_TO_LAUNCH_PROCESS, + FAIL_TO_CREATE_SOCKET, + FAIL_TO_GENERATE_IPC_FILENAME, + FAIL_TO_CONNECT_TIMEOUT, + FAIL_NOT_CONNECTED, + FAIL_TO_SEND, + INIT_FAILED + }; + // @event_callback is called from another thread + VideoPlayer(EventCallbackFunc event_callback); + ~VideoPlayer(); VideoPlayer(const VideoPlayer&) = delete; VideoPlayer& operator=(const VideoPlayer&) = delete; - - void handle_event(sf::Event &event); - void setPosition(float x, float y); - void resize(const sf::Vector2f &size); - void draw(sf::RenderWindow &window); - // @path can also be an url if youtube-dl is installed - void load_file(const std::string &path); - - // This is updated when mpv wants to render - std::atomic_bool redraw; - std::atomic_bool event_update; - // Important: Do not destroy the video player in this callback - PlaybackEndedCallback onPlaybackEndedCallback; + // @path can also be an url if youtube-dl is installed and accessible to mpv + Error load_video(const char *path, sf::WindowHandle parent_window); + // Should be called every update frame + Error update(); + + Error toggle_pause(); private: - void handle_mpv_events(); + Error send_command(const char *cmd, size_t size); + Error launch_video_process(const char *path, sf::WindowHandle parent_window); + void read_ipc_func(); private: - mpv_handle *mpv; - mpv_render_context *mpvGl; - std::unique_ptr<sf::Context> context; - sf::Sprite sprite; - sf::Texture texture; - sf::Uint8 *textureBuffer; - sf::Vector2i video_size; - sf::Vector2f desired_size; - sf::RectangleShape seekbar; - sf::RectangleShape seekbar_background; - sf::Clock cursor_last_active_timer; + pid_t video_process_id; + int ipc_socket; + bool connected_to_ipc; + sf::Clock ipc_connect_retry_timer; + int connect_tries; + struct sockaddr_un ipc_addr; + char ipc_server_path[L_tmpnam]; + EventCallbackFunc event_callback; + std::thread event_read_thread; + bool alive; }; } diff --git a/project.conf b/project.conf index 7e4e124..38f11e3 100644 --- a/project.conf +++ b/project.conf @@ -6,8 +6,6 @@ platforms = ["posix"] [dependencies] sfml-graphics = "2" -mpv = "1.25.0" -gl = ">=17.3" x11 = "1.6.5" jsoncpp = "1.5" cppcodec-1 = "0.1"
\ No newline at end of file diff --git a/src/Program.c b/src/Program.c index 731a20e..a863fcd 100644 --- a/src/Program.c +++ b/src/Program.c @@ -4,6 +4,7 @@ #include <errno.h> #include <string.h> #include <stdio.h> +#include <assert.h> #define READ_END 0 #define WRITE_END 1 @@ -85,3 +86,62 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void return result; } } + +int exec_program_async(const char **args, pid_t *result_process_id) { + /* 1 arguments */ + if(args[0] == NULL) + return -1; + + pid_t pid = fork(); + if(pid == -1) { + int err = errno; + perror("Failed to fork"); + return -err; + } else if(pid == 0) { /* child */ + execvp(args[0], args); + } else { /* parent */ + if(result_process_id) + *result_process_id = pid; + } + return 0; +} + +#if 0 +int program_pipe_write(ProgramPipe *self, const char *data, size_t size) { + ssize_t bytes_written = write(self->write_fd, data, size); + if(bytes_written == -1) { + int err = errno; + perror("Failed to write to pipe to program"); + return -err; + } + return 0; +} + +int program_pipe_read(ProgramPipe *self, ProgramOutputCallback output_callback, void *userdata) { + char buffer[2048]; + + for(;;) { + ssize_t bytes_read = read(self->read_fd, buffer, sizeof(buffer) - 1); + if(bytes_read == 0) { + break; + } else if(bytes_read == -1) { + int err = errno; + perror("Failed to read from pipe to program"); + return -err; + } + + buffer[bytes_read] = '\0'; + if(output_callback && output_callback(buffer, bytes_read, userdata) != 0) + break; + } + + return 0; +} + +void program_pipe_close(ProgramPipe *self) { + close(self->read_fd); + close(self->write_fd); + self->read_fd = -1; + self->write_fd = -1; +} +#endif diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 0a34508..cb58a1d 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1,12 +1,13 @@ #include "../include/QuickMedia.hpp" #include "../plugins/Manganelo.hpp" #include "../plugins/Youtube.hpp" -#include "../include/VideoPlayer.hpp" #include "../include/Scale.hpp" #include "../include/Program.h" +#include "../include/VideoPlayer.hpp" #include <cppcodec/base64_rfc4648.hpp> #include <SFML/Graphics/RectangleShape.hpp> +#include <SFML/Graphics/Sprite.hpp> #include <SFML/Graphics/Text.hpp> #include <SFML/Window/Event.hpp> #include <json/reader.h> @@ -14,11 +15,19 @@ #include <assert.h> #include <cmath> #include <string.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <signal.h> const sf::Color front_color(43, 45, 47); const sf::Color back_color(33, 35, 37); const int DOUBLE_CLICK_TIME = 500; +// Prevent writing to broken pipe from exiting the program +static void sigpipe_handler(int unused) { + +} + namespace QuickMedia { Program::Program() : window(sf::VideoMode(800, 600), "QuickMedia"), @@ -35,6 +44,12 @@ namespace QuickMedia { } body = new Body(font); search_bar = std::make_unique<SearchBar>(font); + + struct sigaction action; + action.sa_handler = sigpipe_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + sigaction(SIGPIPE, &action, NULL); } Program::~Program() { @@ -316,64 +331,86 @@ namespace QuickMedia { #endif } + struct XDisplayScope { + XDisplayScope(Display *_display) : display(_display) { + + } + + ~XDisplayScope() { + if(display) + XCloseDisplay(display); + } + + Display *display; + }; + void Program::video_content_page() { search_bar->onTextUpdateCallback = nullptr; search_bar->onTextSubmitCallback = nullptr; + Display* disp = XOpenDisplay(NULL); + if (!disp) + 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 + // This variable is needed because calling play_video is not possible in onPlaybackEndedCallback bool play_next_video = false; - auto onPlaybackEndedCallback = [this, &play_next_video]() { - std::string new_video_url; - std::vector<std::unique_ptr<BodyItem>> 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; - } - } + std::unique_ptr<VideoPlayer> video_player; - // 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"); - return; - } + auto play_video = [this, &video_player, &play_next_video]() { + 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) { + if(strcmp(event_name, "end-file") == 0) { + std::string new_video_url; + std::vector<std::unique_ptr<BodyItem>> 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; + } + } - 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); - }; + // 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; + } - std::unique_ptr<VideoPlayer> video_player = nullptr; - auto play_video = [this, &video_player, &onPlaybackEndedCallback]() { - watched_videos.insert(content_url); - try { - printf("Play video: %s\n", content_url.c_str()); - video_player.reset(new VideoPlayer(&window, window_size.x, window_size.y, content_url.c_str())); - video_player->onPlaybackEndedCallback = onPlaybackEndedCallback; - } catch(VideoInitializationException &e) { - show_notification("Video player", "Failed to create video player", Urgency::CRITICAL); - video_player = nullptr; + 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); + } + }); + + 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: "; + err_msg += content_url; + show_notification("Video player", err_msg.c_str(), Urgency::CRITICAL); + current_page = Page::SEARCH_SUGGESTION; } }; play_video(); sf::Clock time_since_last_left_click; int left_click_counter; - bool video_is_fullscreen = false; sf::Event event; - auto on_doubleclick = [this, &video_is_fullscreen]() { - if(video_is_fullscreen) { - window.create(sf::VideoMode::getDesktopMode(), "QuickMedia", sf::Style::Default); - } else { - window.create(sf::VideoMode::getDesktopMode(), "QuickMedia", sf::Style::Fullscreen); - } - window.setVerticalSyncEnabled(true); - video_is_fullscreen = !video_is_fullscreen; - }; + sf::RectangleShape rect(sf::Vector2f(500, 500)); while (current_page == Page::VIDEO_CONTENT) { if(play_next_video) { @@ -383,33 +420,61 @@ 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->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) { 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 { left_click_counter = 1; } } + } + #endif - if(video_player) { - if(event.type == sf::Event::Resized) - video_player->resize(window_size); - video_player->handle_event(event); - } + 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; } window.clear(); - if(video_player) - video_player->draw(window); window.display(); - } - if(video_is_fullscreen) { - window.create(sf::VideoMode::getDesktopMode(), "QuickMedia", sf::Style::Default); - window.setVerticalSyncEnabled(true); + // TODO: Show loading video animation + //video_window.clear(sf::Color::Red); + //video_window.display(); } } diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 4c19c61..e0e69df 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -1,275 +1,164 @@ #include "../include/VideoPlayer.hpp" -#include "../include/Scale.hpp" -#include <SFML/Window/Mouse.hpp> -#include <mpv/client.h> -#include <mpv/render_gl.h> -#include <SFML/OpenGL.hpp> -#include <clocale> -#include <cmath> +#include "../include/Program.h" +#include <string> +#include <json/reader.h> +#include <assert.h> -const int UI_VISIBLE_TIMEOUT_MS = 2500; -const auto pause_key = sf::Keyboard::Space; +#include <sys/socket.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> -namespace QuickMedia { - static void* getProcAddressMpv(void *funcContext, const char *name) { - return (void*)sf::Context::getFunction(name); - } - - static void onMpvRedraw(void *ctx) { - VideoPlayer *video_player = (VideoPlayer*)ctx; - video_player->redraw = true; - } +const int RETRY_TIME_MS = 1000; +const int MAX_RETRIES = 5; - static void on_mpv_events(void *ctx) { - VideoPlayer *video_player = (VideoPlayer*)ctx; - video_player->event_update = true; +namespace QuickMedia { + VideoPlayer::VideoPlayer(EventCallbackFunc _event_callback) : + video_process_id(-1), + ipc_socket(-1), + connected_to_ipc(false), + connect_tries(0), + event_callback(_event_callback), + alive(true) + { + } - static void check_error(int status) { - if(status < 0) - fprintf(stderr, "mpv api error: %s\n", mpv_error_string(status)); - } + VideoPlayer::~VideoPlayer() { + if(video_process_id != -1) + kill(video_process_id, SIGTERM); - static void mpv_set_option_bool(mpv_handle *mpv, const char *option, bool value) { - int int_value = value; - check_error(mpv_set_option(mpv, option, MPV_FORMAT_FLAG, &int_value)); - } + if(ipc_socket != -1) + close(ipc_socket); + + if(video_process_id != -1) + remove(ipc_server_path); - static void mpv_set_option_int64(mpv_handle *mpv, const char *option, int64_t value) { - check_error(mpv_set_option(mpv, option, MPV_FORMAT_INT64, &value)); + alive = false; + if(event_read_thread.joinable()) + event_read_thread.join(); } - class ContextScope { - public: - ContextScope(sf::Context *_context) : context(_context) { - context->setActive(true); - } - - ~ContextScope() { - context->setActive(false); + VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, sf::WindowHandle parent_window) { + if(!tmpnam(ipc_server_path)) { + perror("Failed to generate ipc file name"); + return Error::FAIL_TO_GENERATE_IPC_FILENAME; } - sf::Context *context; - }; - - VideoPlayer::VideoPlayer(sf::RenderWindow *_window, unsigned int width, unsigned int height, const char *file, bool loop) : - redraw(false), - event_update(false), - onPlaybackEndedCallback(nullptr), - mpv(nullptr), - mpvGl(nullptr), - context(nullptr), - textureBuffer(nullptr), - desired_size(width, height) - { - //ContextScope context_scope(context.get()); - texture.setSmooth(true); - - // mpv_create requires LC_NUMERIC to be set to "C" for some reason, see mpv_create documentation - std::setlocale(LC_NUMERIC, "C"); - mpv = mpv_create(); - if(!mpv) - throw VideoInitializationException("Failed to create mpv handle"); - - //check_error(mpv_set_option_string(mpv, "input-default-bindings", "yes")); - //check_error(mpv_set_option_string(mpv, "input-vo-keyboard", "yes")); - check_error(mpv_set_option_string(mpv, "cache-secs", "120")); - check_error(mpv_set_option_string(mpv, "demuxer-max-bytes", "20M")); - check_error(mpv_set_option_string(mpv, "demuxer-max-back-bytes", "10M")); + 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", + "--vo=gpu", "--hwdec=auto", + "--wid", parent_window_str.c_str(), "--", path, nullptr }; + if(exec_program_async(args, &video_process_id) != 0) + return Error::FAIL_TO_LAUNCH_PROCESS; - //mpv_set_option_bool(mpv, "osc", true); - //mpv_set_option_int64(mpv, "wid", window.getSystemHandle()); - - if(mpv_initialize(mpv) < 0) - throw VideoInitializationException("Failed to initialize mpv"); + printf("mpv input ipc server: %s\n", ipc_server_path); - // TODO: Enabling vo=gpu will make mpv create its own window, or take over the QuickMedia window fully - // if "wid" option is used. To take advantage of vo=gpu, QuickMedia should create a parent window - // and make mpv use that and then embed that into the parent QuickMedia window. - // This will also remove the need for rendering with sfml (no need for texture copy!). - //check_error(mpv_set_option_string(mpv, "vo", "gpu")); - //check_error(mpv_set_option_string(mpv, "hwdec", "auto")); + if((ipc_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + perror("Failed to create socket for video player"); + return Error::FAIL_TO_CREATE_SOCKET; + } - //check_error(mpv_set_option_string(mpv, "terminal", "yes")); - //check_error(mpv_set_option_string(mpv, "msg-level", "all=v")); + ipc_addr.sun_family = AF_UNIX; + strcpy(ipc_addr.sun_path, ipc_server_path); - if(loop) - check_error(mpv_set_option_string(mpv, "loop", "inf")); + int flags = fcntl(ipc_socket, F_GETFL, 0); + fcntl(ipc_socket, F_SETFL, flags | O_NONBLOCK); - //mpvGl = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); - //if(!mpvGl) - // throw VideoInitializationException("Failed to initialize mpv opengl render context"); + return Error::OK; + } - mpv_opengl_init_params opengl_init_params; - opengl_init_params.get_proc_address = getProcAddressMpv; - opengl_init_params.get_proc_address_ctx = nullptr; - opengl_init_params.extra_exts = nullptr; - mpv_render_param params[] = { - { MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_OPENGL }, - { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &opengl_init_params }, - { (mpv_render_param_type)0, nullptr } - }; + VideoPlayer::Error VideoPlayer::load_video(const char *path, sf::WindowHandle parent_window) { + if(video_process_id == -1) + return launch_video_process(path, parent_window); - if (mpv_render_context_create(&mpvGl, mpv, params) < 0) - throw VideoInitializationException("failed to initialize mpv GL context"); - - mpv_render_context_set_update_callback(mpvGl, onMpvRedraw, this); - mpv_set_wakeup_callback(mpv, on_mpv_events, this); - - seekbar.setFillColor(sf::Color(255, 20, 60, 200)); - seekbar_background.setFillColor(sf::Color(33, 33, 33, 200)); - load_file(file); + std::string cmd = "loadfile "; + cmd += path; + return send_command(cmd.c_str(), cmd.size()); } - - VideoPlayer::~VideoPlayer() { - if(mpvGl) { - //mpv_render_context_set_update_callback(mpvGl, nullptr, nullptr); - mpv_render_context_free(mpvGl); - } - free(textureBuffer); + VideoPlayer::Error VideoPlayer::update() { + if(ipc_socket == -1) + return Error::INIT_FAILED; - if(mpv) { - //mpv_set_wakeup_callback(mpv, nullptr, nullptr); - mpv_destroy(mpv); - //mpv_terminate_destroy(mpv); - } - } + if(connect_tries == MAX_RETRIES) + return Error::FAIL_TO_CONNECT_TIMEOUT; - void VideoPlayer::handle_event(sf::Event &event) { - if(event.type == sf::Event::MouseMoved) { - cursor_last_active_timer.restart(); - } else if(event.type == sf::Event::KeyPressed) { - if(event.key.code == pause_key) { - mpv_command_string(mpv, "cycle pause"); + if(!connected_to_ipc && ipc_connect_retry_timer.getElapsedTime().asMilliseconds() >= RETRY_TIME_MS) { + 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)); + return Error::FAIL_TO_CONNECT_TIMEOUT; + } + } else { + connected_to_ipc = true; + if(event_callback) + event_read_thread = std::thread(&VideoPlayer::read_ipc_func, this); } } - } - - void VideoPlayer::setPosition(float x, float y) { - sprite.setPosition(x, y); - } - // TODO: Make this work in the future when video size and sprite texture size wont be the same - void VideoPlayer::resize(const sf::Vector2f &size) { - desired_size = sf::Vector2f(size.x, size.y); - if(!textureBuffer) - return; - - sf::Vector2f video_size_f(video_size.x, video_size.y); - auto video_scale = get_ratio(video_size_f, wrap_to_size(video_size_f, desired_size)); - sprite.setScale(video_scale); - - auto image_size = video_size_f; - image_size.x *= video_scale.x; - image_size.y *= video_scale.y; - sprite.setPosition(std::floor(desired_size.x * 0.5f - image_size.x * 0.5f), std::floor(desired_size.y * 0.5f - image_size.y * 0.5f)); + return Error::OK; } - void VideoPlayer::handle_mpv_events() { - while(true) { - mpv_event *mpvEvent = mpv_wait_event(mpv, 0.0); - if(mpvEvent->event_id == MPV_EVENT_NONE) + void VideoPlayer::read_ipc_func() { + assert(connected_to_ipc); + assert(event_callback); + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader()); + 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)); + continue; + } + + fprintf(stderr, "Failed to read from ipc socket, error: %s\n", strerror(err)); break; - else if(mpvEvent->event_id == MPV_EVENT_SHUTDOWN) - return; - else if(mpvEvent->event_id == MPV_EVENT_VIDEO_RECONFIG) { - int64_t w, h; - if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 && - mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && - w > 0 && h > 0 && (w != video_size.x || h != video_size.y)) - { - video_size.x = w; - video_size.y = h; - context.reset(new sf::Context(sf::ContextSettings(), w, h)); - context->setActive(true); - // TODO: Verify if it's valid to re-create the texture like this, - // instead of using deconstructor - if(texture.create(w, h)) { - void *newTextureBuf = realloc(textureBuffer, w * h * 4); - if(newTextureBuf) - textureBuffer = (sf::Uint8*)newTextureBuf; + } 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()); } - glViewport(0, 0, w, h); - context->setActive(false); - resize(desired_size); + + start = i + 1; } - } else if(mpvEvent->event_id == MPV_EVENT_END_FILE) { - if(onPlaybackEndedCallback) - onPlaybackEndedCallback(); - } else { - //printf("Mpv event: %s\n", mpv_event_name(mpvEvent->event_id)); } } } - - void VideoPlayer::draw(sf::RenderWindow &window) { - if(event_update.exchange(false)) - handle_mpv_events(); - - if(textureBuffer && redraw) { - uint64_t update_flags = mpv_render_context_update(mpvGl); - if((update_flags & MPV_RENDER_UPDATE_FRAME) && redraw.exchange(false)) { - auto textureSize = texture.getSize(); - - mpv_opengl_fbo opengl_fbo; - opengl_fbo.fbo = 0; - opengl_fbo.w = textureSize.x; - opengl_fbo.h = textureSize.y; - opengl_fbo.internal_format = 0; - mpv_render_param params[] = - { - { MPV_RENDER_PARAM_OPENGL_FBO, &opengl_fbo }, - { (mpv_render_param_type)0, nullptr } - }; - - context->setActive(true); - mpv_render_context_render(mpvGl, params); - // TODO: Instead of copying video to cpu buffer and then to texture, copy directly from video buffer to texture buffer - glReadPixels(0, 0, textureSize.x, textureSize.y, GL_RGBA, GL_UNSIGNED_BYTE, textureBuffer); - context->setActive(false); - texture.update(textureBuffer); - sprite.setTexture(texture, true); - mpv_render_context_report_swap(mpvGl); - } - } - window.draw(sprite); - - if(cursor_last_active_timer.getElapsedTime().asMilliseconds() > UI_VISIBLE_TIMEOUT_MS) - return; - double pos = 0.0; - mpv_get_property(mpv, "percent-pos", MPV_FORMAT_DOUBLE, &pos); - pos *= 0.01; - - auto window_size = window.getSize(); - - const float seekbar_height = std::floor(window_size.y * 0.02f); - const float seekbar_max_size = window_size.x; - sf::Vector2f seekbar_size(seekbar_max_size * pos, seekbar_height); - seekbar.setPosition(0.0f, window_size.y - seekbar_height); - seekbar.setSize(seekbar_size); - window.draw(seekbar); + VideoPlayer::Error VideoPlayer::toggle_pause() { + const char cmd[] = "cycle pause\n"; + return send_command(cmd, sizeof(cmd) - 1); + } - seekbar_background.setPosition(seekbar.getPosition() + sf::Vector2f(seekbar_size.x, 0.0f)); - seekbar_background.setSize(sf::Vector2f(seekbar_max_size - seekbar_size.x, seekbar_height)); - window.draw(seekbar_background); + VideoPlayer::Error VideoPlayer::send_command(const char *cmd, size_t size) { + if(!connected_to_ipc) + return Error::FAIL_NOT_CONNECTED; - if(sf::Mouse::isButtonPressed(sf::Mouse::Left)) { - auto mouse_pos = sf::Mouse::getPosition(window); - auto seekbar_pos = seekbar.getPosition(); - float diff_x = mouse_pos.x - seekbar_pos.x; - if(mouse_pos.x >= seekbar_pos.x && mouse_pos.x <= seekbar_pos.x + seekbar_max_size && - mouse_pos.y >= seekbar_pos.y && mouse_pos.y <= seekbar_pos.y + seekbar_height) - { - double new_pos = ((double)diff_x / seekbar_max_size) * 100.0; - mpv_set_property(mpv, "percent-pos", MPV_FORMAT_DOUBLE, &new_pos); - } + if(send(ipc_socket, cmd, size, 0) == -1) { + fprintf(stderr, "Failed to send to ipc socket, error: %s, command: %.*s\n", strerror(errno), (int)size, cmd); + return Error::FAIL_TO_SEND; } - } - void VideoPlayer::load_file(const std::string &path) { - const char *cmd[] = { "loadfile", path.c_str(), nullptr }; - mpv_command(mpv, cmd); + return Error::OK; } } diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index b1f02a3..e91baf0 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -67,7 +67,7 @@ namespace QuickMedia { Json::CharReaderBuilder json_builder; std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader()); std::string json_errors; - if(json_reader->parse(&server_response.front(), &server_response.back(), &json_root, &json_errors)) { + if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) { fprintf(stderr, "Manganelo suggestions json error: %s\n", json_errors.c_str()); return SuggestionResult::ERR; } diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp index 86f5d7d..5d81aad 100644 --- a/src/plugins/Plugin.cpp +++ b/src/plugins/Plugin.cpp @@ -28,11 +28,13 @@ namespace QuickMedia { } DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args) { - std::vector<const char*> args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L", url.c_str() }; + std::vector<const char*> args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L" }; for(const CommandArg &arg : additional_args) { args.push_back(arg.option.c_str()); args.push_back(arg.value.c_str()); } + args.push_back("--"); + args.push_back(url.c_str()); args.push_back(nullptr); if(exec_program(args.data(), accumulate_string, &result) != 0) return DownloadResult::NET_ERR; diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index a5670ec..2a3f011 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -48,13 +48,12 @@ namespace QuickMedia { if(json_end == 0 || json_start >= json_end) return SuggestionResult::ERR; - --json_end; Json::Value json_root; Json::CharReaderBuilder json_builder; std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader()); std::string json_errors; - if(json_reader->parse(&server_response[json_start], &server_response[json_end], &json_root, &json_errors)) { + if(!json_reader->parse(&server_response[json_start], &server_response[json_end], &json_root, &json_errors)) { fprintf(stderr, "Youtube suggestions json error: %s\n", json_errors.c_str()); return SuggestionResult::ERR; } |