From 62a29abd372a39a413e43a8f75146af823fe7bb3 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 4 Aug 2019 14:58:03 +0200 Subject: Add video seek, play next video on end file --- README.md | 3 + include/VideoPlayer.hpp | 11 +++- plugins/Plugin.hpp | 1 + plugins/Youtube.hpp | 1 + src/Manganelo.cpp | 10 +-- src/Plugin.cpp | 5 ++ src/QuickMedia.cpp | 19 ++++-- src/VideoPlayer.cpp | 157 ++++++++++++++++++++++++------------------------ src/Youtube.cpp | 35 ++++++++++- 9 files changed, 151 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 0f1d1f1..39250de 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ +# QuickMedia Native clients of websites with fast access to what you want to see +# Dependencies +See project.conf \[dependencies] \ No newline at end of file diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index e98221e..9fbad33 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include class mpv_handle; class mpv_opengl_cb_context; @@ -17,6 +19,8 @@ namespace QuickMedia { public: VideoInitializationException(const std::string &errMsg) : std::runtime_error(errMsg) {} }; + + using PlaybackEndedCallback = std::function; class VideoPlayer { public: @@ -27,11 +31,14 @@ namespace QuickMedia { void setPosition(float x, float y); bool resize(const sf::Vector2i &size); void draw(sf::RenderWindow &window); + + void load_file(const std::string &path); // This counter is incremented when mpv wants to redraw content std::atomic_int redrawCounter; - private: sf::Context context; + PlaybackEndedCallback onPlaybackEndedCallback; + private: mpv_handle *mpv; mpv_opengl_cb_context *mpvGl; std::thread renderThread; @@ -42,5 +49,7 @@ namespace QuickMedia { bool alive; sf::Vector2i video_size; sf::Vector2i desired_size; + sf::RectangleShape seekbar; + sf::RectangleShape seekbar_background; }; } diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp index dd50998..09ce09a 100644 --- a/plugins/Plugin.hpp +++ b/plugins/Plugin.hpp @@ -46,6 +46,7 @@ namespace QuickMedia { virtual SearchResult search(const std::string &text, std::vector> &result_items) = 0; virtual SuggestionResult update_search_suggestions(const std::string &text, std::vector> &result_items); + virtual std::vector> get_related_media(const std::string &url); protected: DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args = {}); std::string url_param_encode(const std::string ¶m) const; diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index 073cb0c..eda8a1f 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -7,5 +7,6 @@ namespace QuickMedia { public: SearchResult search(const std::string &text, std::vector> &result_items) override; SuggestionResult update_search_suggestions(const std::string &text, std::vector> &result_items) override; + std::vector> get_related_media(const std::string &url) override; }; } \ No newline at end of file diff --git a/src/Manganelo.cpp b/src/Manganelo.cpp index 8bbaa9f..1d3929e 100644 --- a/src/Manganelo.cpp +++ b/src/Manganelo.cpp @@ -28,9 +28,11 @@ namespace QuickMedia { ItemData *item_data = (ItemData*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *text = quickmedia_html_node_get_text(node); - auto item = std::make_unique(text); - item->url = href; - item_data->result_items.push_back(std::move(item)); + if(href && text) { + auto item = std::make_unique(text); + item->url = href; + item_data->result_items.push_back(std::move(item)); + } }, &item_data); if (result != 0) goto cleanup; @@ -39,7 +41,7 @@ namespace QuickMedia { [](QuickMediaHtmlNode *node, void *userdata) { ItemData *item_data = (ItemData*)userdata; const char *src = quickmedia_html_node_get_attribute_value(node, "src"); - if(item_data->item_index < item_data->result_items.size()) { + if(src && item_data->item_index < item_data->result_items.size()) { item_data->result_items[item_data->item_index]->thumbnail_url = src; ++item_data->item_index; } diff --git a/src/Plugin.cpp b/src/Plugin.cpp index 382ebbf..c31e715 100644 --- a/src/Plugin.cpp +++ b/src/Plugin.cpp @@ -16,6 +16,11 @@ namespace QuickMedia { return SuggestionResult::OK; } + std::vector> Plugin::get_related_media(const std::string &url) { + (void)url; + return {}; + } + DownloadResult Plugin::download_to_string(const std::string &url, std::string &result, const std::vector &additional_args) { std::vector args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L", url.c_str() }; for(const CommandArg &arg : additional_args) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index a63f430..b74cd02 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -253,9 +253,9 @@ namespace QuickMedia { sf::Vector2f body_pos; sf::Vector2f body_size; bool resized = true; + sf::Event event; while (window.isOpen() && current_page == Page::SEARCH_SUGGESTION) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); @@ -324,9 +324,9 @@ namespace QuickMedia { sf::Vector2f body_pos; sf::Vector2f body_size; bool resized = true; + sf::Event event; while (window.isOpen() && current_page == Page::SEARCH_RESULT) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); @@ -390,11 +390,22 @@ namespace QuickMedia { fprintf(stderr, "Failed to create video player!. TODO: Show this to the user"); } - bool resized = false; + std::vector> related_media = current_plugin->get_related_media(video_url); + + if(video_player) { + video_player->onPlaybackEndedCallback = [this, &related_media, &video_player]() { + if(related_media.empty()) + return; + video_player->load_file(related_media.front()->url); + related_media = current_plugin->get_related_media(video_url); + }; + } + + bool resized = true; sf::Clock resize_timer; + sf::Event event; while (window.isOpen() && current_page == Page::VIDEO_CONTENT) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 304ef2d..ecbca34 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -1,66 +1,31 @@ #include "../include/VideoPlayer.hpp" +#include #include #include +#include #include -#include - -#if defined(SFML_SYSTEM_WINDOWS) - #ifdef _MSC_VER - #include - #endif - #include - #include -#elif defined(SFML_SYSTEM_LINUX) || defined(SFML_SYSTEM_FREEBSD) - #if defined(SFML_OPENGL_ES) - #include - #include - #else - #include - #endif - #include - #define glGetProcAddress glXGetProcAddress -#elif defined(SFML_SYSTEM_MACOS) - #include -#elif defined (SFML_SYSTEM_IOS) - #include - #include -#elif defined (SFML_SYSTEM_ANDROID) - #include - #include - // We're not using OpenGL ES 2+ yet, but we can use the sRGB extension - #include -#endif - -using namespace std; - namespace QuickMedia { void* getProcAddressMpv(void *funcContext, const char *name) { - return (void*)glGetProcAddress((const GLubyte*)name); + VideoPlayer *video_player = (VideoPlayer*)funcContext; + return (void*)video_player->context.getFunction(name); } void onMpvRedraw(void *rawVideo) { - VideoPlayer *video = (VideoPlayer*)rawVideo; - ++video->redrawCounter; + VideoPlayer *video_player = (VideoPlayer*)rawVideo; + ++video_player->redrawCounter; } VideoPlayer::VideoPlayer(unsigned int width, unsigned int height, const char *file, bool loop) : redrawCounter(0), context(sf::ContextSettings(), width, height), + onPlaybackEndedCallback(nullptr), mpv(nullptr), mpvGl(nullptr), - textureBuffer((sf::Uint8*)malloc(width * height * 4)), // 4 = red, green, blue and alpha - alive(true), - video_size(width, height), - desired_size(width, height) + textureBuffer(nullptr), + alive(true) { - if(!textureBuffer) - throw VideoInitializationException("Failed to allocate memory for video"); - context.setActive(true); - - if(!texture.create(width, height)) - throw VideoInitializationException("Failed to create texture for video"); texture.setSmooth(true); // mpv_create requires LC_NUMERIC to be set to "C" for some reason, see mpv_create documentation @@ -68,27 +33,29 @@ namespace QuickMedia { mpv = mpv_create(); if(!mpv) throw VideoInitializationException("Failed to create mpv handle"); - - if(mpv_initialize(mpv) < 0) - throw VideoInitializationException("Failed to initialize mpv"); mpv_set_option_string(mpv, "input-default-bindings", "yes"); - // Enable keyboard input on the X11 window mpv_set_option_string(mpv, "input-vo-keyboard", "yes"); mpv_set_option_string(mpv, "vo", "opengl-cb"); mpv_set_option_string(mpv, "hwdec", "auto"); + if(loop) mpv_set_option_string(mpv, "loop", "inf"); + + if(mpv_initialize(mpv) < 0) + throw VideoInitializationException("Failed to initialize mpv"); + 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"); mpv_opengl_cb_set_update_callback(mpvGl, onMpvRedraw, this); - if(mpv_opengl_cb_init_gl(mpvGl, nullptr, getProcAddressMpv, nullptr) < 0) + if(mpv_opengl_cb_init_gl(mpvGl, nullptr, getProcAddressMpv, this) < 0) throw VideoInitializationException("Failed to initialize mpv gl callback func"); - renderThread = thread([this]() { + context.setActive(false); + renderThread = std::thread([this]() { context.setActive(true); while(alive) { while(true) { @@ -103,26 +70,32 @@ namespace QuickMedia { mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && w > 0 && h > 0 && (w != video_size.x || h != video_size.y)) { - { - lock_guard lock(renderMutex); - video_size.x = w; - video_size.y = h; - context.setActive(true); - if(texture.create(w, h)) { - void *newTextureBuf = realloc(textureBuffer, w * h * 4); - if(newTextureBuf) - textureBuffer = (sf::Uint8*)newTextureBuf; - } + std::lock_guard lock(renderMutex); + video_size.x = w; + video_size.y = 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; } - resize(desired_size); + glViewport(0, 0, w, h); } + resize(desired_size); + } else if(mpvEvent->event_id == MPV_EVENT_END_FILE) { + if(onPlaybackEndedCallback) + onPlaybackEndedCallback(); + } else { + //printf("Mpv event: %s\n", mpv_event_name(mpvEvent->event_id)); } } - if(redrawCounter > 0) { + if(redrawCounter > 0 && textureBuffer) { --redrawCounter; context.setActive(true); - lock_guard lock(renderMutex); + std::lock_guard lock(renderMutex); auto textureSize = texture.getSize(); //mpv_render_context_render(mpvGl, params); mpv_opengl_cb_draw(mpvGl, 0, textureSize.x, textureSize.y); @@ -135,16 +108,16 @@ namespace QuickMedia { } }); - const char *cmd[] = { "loadfile", file, nullptr }; - mpv_command(mpv, cmd); - context.setActive(false); + seekbar.setFillColor(sf::Color::White); + seekbar_background.setFillColor(sf::Color(0, 0, 0, 150)); + load_file(file); } VideoPlayer::~VideoPlayer() { alive = false; renderThread.join(); - lock_guard lock(renderMutex); + std::lock_guard lock(renderMutex); context.setActive(true); if(mpvGl) mpv_opengl_cb_set_update_callback(mpvGl, nullptr, nullptr); @@ -159,7 +132,9 @@ namespace QuickMedia { } bool VideoPlayer::resize(const sf::Vector2i &size) { - lock_guard lock(renderMutex); + desired_size = size; + if(!textureBuffer) + return true; float video_ratio = (double)video_size.x / (double)video_size.y; float scale_x = 1.0f; float scale_y = 1.0f; @@ -175,21 +150,43 @@ namespace QuickMedia { sprite.setPosition(size.x * 0.5f - video_size.x * scale_x * 0.5f, 0.0f); } sprite.setScale(scale_x, scale_y); - desired_size = size; - #if 0 - void *newTextureBuf = realloc(textureBuffer, size.x * size.y * 4); - if(!newTextureBuf) - return false; - textureBuffer = (sf::Uint8*)newTextureBuf; - if(!texture.create(size.x, size.y)) - return false; - return true; - #endif return true; } void VideoPlayer::draw(sf::RenderWindow &window) { - lock_guard lock(renderMutex); - window.draw(sprite); + { + std::lock_guard lock(renderMutex); + window.draw(sprite); + } + double pos = 0.0; + mpv_get_property(mpv, "percent-pos", MPV_FORMAT_DOUBLE, &pos); + pos *= 0.01; + + auto textureSize = sprite.getTextureRect(); + auto scale = sprite.getScale(); + auto video_size = sf::Vector2f(textureSize.width * scale.x, textureSize.height * scale.y); + + const float seekbar_height = video_size.y * 0.025f; + seekbar.setPosition(sprite.getPosition() + sf::Vector2f(0.0f, video_size.y - seekbar_height)); + seekbar.setSize(sf::Vector2f(video_size.x * pos, seekbar_height)); + window.draw(seekbar); + seekbar_background.setPosition(seekbar.getPosition() + sf::Vector2f(video_size.x * pos, 0.0f)); + seekbar_background.setSize(sf::Vector2f(video_size.x - video_size.x * pos, seekbar_height)); + window.draw(seekbar_background); + + 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(diff_x >= 0.0f && diff_x <= video_size.x && mouse_pos.y >= seekbar_pos.y && mouse_pos.y <= seekbar_pos.y + seekbar_height) { + double new_pos = ((double)diff_x / video_size.x) * 100.0; + mpv_set_property(mpv, "percent-pos", MPV_FORMAT_DOUBLE, &new_pos); + } + } + } + + void VideoPlayer::load_file(const std::string &path) { + const char *cmd[] = { "loadfile", path.c_str(), nullptr }; + mpv_command(mpv, cmd); } } diff --git a/src/Youtube.cpp b/src/Youtube.cpp index 85969b9..4647887 100644 --- a/src/Youtube.cpp +++ b/src/Youtube.cpp @@ -21,9 +21,11 @@ namespace QuickMedia { auto *result_items = (std::vector>*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *title = quickmedia_html_node_get_attribute_value(node, "title"); + if(href && title) { auto item = std::make_unique(title); - item->url = std::string("https://www.youtube.com") + href; - result_items->push_back(std::move(item)); + item->url = std::string("https://www.youtube.com") + href; + result_items->push_back(std::move(item)); + } }, &result_items); cleanup: @@ -78,4 +80,33 @@ namespace QuickMedia { iterate_suggestion_result(json_root, text, result_items); return SuggestionResult::OK; } + + std::vector> Youtube::get_related_media(const std::string &url) { + std::vector> result_items; + + std::string website_data; + if(download_to_string(url, website_data) != DownloadResult::OK) + return result_items; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + 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>*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + // TODO: Also add title for related media + if(href) { + auto item = std::make_unique(""); + item->url = std::string("https://www.youtube.com") + href; + result_items->push_back(std::move(item)); + } + }, &result_items); + + cleanup: + quickmedia_html_search_deinit(&html_search); + return result_items; + } } \ No newline at end of file -- cgit v1.2.3