aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--include/VideoPlayer.hpp11
-rw-r--r--plugins/Plugin.hpp1
-rw-r--r--plugins/Youtube.hpp1
-rw-r--r--src/Manganelo.cpp10
-rw-r--r--src/Plugin.cpp5
-rw-r--r--src/QuickMedia.cpp19
-rw-r--r--src/VideoPlayer.cpp157
-rw-r--r--src/Youtube.cpp35
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 <SFML/Graphics/RenderWindow.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 <functional>
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<void()>;
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<std::unique_ptr<BodyItem>> &result_items) = 0;
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);
protected:
DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args = {});
std::string url_param_encode(const std::string &param) 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<std::unique_ptr<BodyItem>> &result_items) override;
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;
};
} \ 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<BodyItem>(text);
- item->url = href;
- item_data->result_items.push_back(std::move(item));
+ if(href && text) {
+ auto item = std::make_unique<BodyItem>(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<std::unique_ptr<BodyItem>> 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<CommandArg> &additional_args) {
std::vector<const char*> 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<std::unique_ptr<BodyItem>> 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 <SFML/Window/Mouse.hpp>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
+#include <SFML/OpenGL.hpp>
#include <clocale>
-#include <SFML/Config.hpp>
-
-#if defined(SFML_SYSTEM_WINDOWS)
- #ifdef _MSC_VER
- #include <windows.h>
- #endif
- #include <GL/gl.h>
- #include <GL/glx.h>
-#elif defined(SFML_SYSTEM_LINUX) || defined(SFML_SYSTEM_FREEBSD)
- #if defined(SFML_OPENGL_ES)
- #include <GLES/gl.h>
- #include <GLES/glext.h>
- #else
- #include <GL/gl.h>
- #endif
- #include <GL/glx.h>
- #define glGetProcAddress glXGetProcAddress
-#elif defined(SFML_SYSTEM_MACOS)
- #include <OpenGL/gl.h>
-#elif defined (SFML_SYSTEM_IOS)
- #include <OpenGLES/ES1/gl.h>
- #include <OpenGLES/ES1/glext.h>
-#elif defined (SFML_SYSTEM_ANDROID)
- #include <GLES/gl.h>
- #include <GLES/glext.h>
- // We're not using OpenGL ES 2+ yet, but we can use the sRGB extension
- #include <GLES2/gl2ext.h>
-#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<mutex> 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<std::mutex> 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<mutex> lock(renderMutex);
+ std::lock_guard<std::mutex> 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<mutex> lock(renderMutex);
+ std::lock_guard<std::mutex> 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<mutex> 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<mutex> lock(renderMutex);
- window.draw(sprite);
+ {
+ std::lock_guard<std::mutex> 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<std::unique_ptr<BodyItem>>*)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<BodyItem>(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<std::unique_ptr<BodyItem>> Youtube::get_related_media(const std::string &url) {
+ std::vector<std::unique_ptr<BodyItem>> 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<std::unique_ptr<BodyItem>>*)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<BodyItem>("");
+ 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