diff --git a/CMakeLists.txt b/CMakeLists.txt index bcecf71c5..c064b5ce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ option(FORCE_BUNDLE_ICONS "Forcibly bundle icon themes into RSS Guard." OFF) option(ENABLE_COMPRESSED_SITEMAP "Enable support for gzip-compressed sitemap feeds. Requires zlib." OFF) option(ENABLE_MEDIAPLAYER_QTMULTIMEDIA "Enable built-in media player. Requires QtMultimedia FFMPEG plugin." OFF) option(ENABLE_MEDIAPLAYER_LIBMPV "Enable built-in media player. Requires libmpv library." ON) +option(MEDIAPLAYER_FORCE_OPENGL "Use opengl-based render API with libmpv." ON) # Import Qt libraries. set(QT6_MIN_VERSION 6.3.0) @@ -168,6 +169,16 @@ if(ENABLE_MEDIAPLAYER_LIBMPV) set(LibMPV_ROOT "${CMAKE_SOURCE_DIR}/resources/scripts/libmpv") endif() + if(MEDIAPLAYER_FORCE_OPENGL) + list(APPEND QT_COMPONENTS OpenGL) + + if(BUILD_WITH_QT6) + list(APPEND QT_COMPONENTS OpenGLWidgets) + endif() + + add_compile_definitions(MEDIAPLAYER_LIBMPV_OPENGL) + endif() + add_compile_definitions(ENABLE_MEDIAPLAYER_LIBMPV) endif() diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 7862fdaac..6a9c8ba26 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -832,13 +832,27 @@ endif() if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA) target_link_libraries(rssguard PUBLIC + Qt${QT_VERSION_MAJOR}::OpenGL Qt${QT_VERSION_MAJOR}::MultimediaWidgets ) elseif(ENABLE_MEDIAPLAYER_LIBMPV) + if(MEDIAPLAYER_FORCE_OPENGL) + target_link_libraries(rssguard PUBLIC + Qt${QT_VERSION_MAJOR}::OpenGL + ) + + if(BUILD_WITH_QT6) + target_link_libraries(rssguard PUBLIC + Qt${QT_VERSION_MAJOR}::OpenGLWidgets + ) + endif() + endif() + target_include_directories(rssguard AFTER PRIVATE ${LibMPV_INCLUDE_DIRS} ) + target_link_libraries(rssguard PUBLIC ${LibMPV_LIBRARIES} ) diff --git a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp index d00dbe11f..791b90d3b 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp +++ b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp @@ -48,12 +48,10 @@ LibMpvBackend::LibMpvBackend(Application* app, QWidget* parent) setMouseTracking(true); layout()->addWidget(m_mpvContainer); - m_mpvContainer->bind(); mpv_set_option_string(m_mpvHandle, "msg-level", "all=v"); mpv_set_option_string(m_mpvHandle, "config", "yes"); - mpv_set_option_string(m_mpvHandle, "force-window", "yes"); mpv_set_option_string(m_mpvHandle, "script-opts", "osc-idlescreen=no"); mpv_set_option_string(m_mpvHandle, "hwdec", "auto"); mpv_set_option_string(m_mpvHandle, "osd-playing-msg", "${media-title}"); @@ -121,6 +119,7 @@ void LibMpvBackend::loadSettings() { } LibMpvBackend::~LibMpvBackend() { + m_mpvContainer->destroyHandle(); destroyHandle(); } @@ -155,7 +154,7 @@ void LibMpvBackend::handleMpvEvent(mpv_event* event) { } case MPV_EVENT_SHUTDOWN: { - destroyHandle(); + // destroyHandle(); emit closed(); break; } diff --git a/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.cpp b/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.cpp index 503b5862d..6bb286d83 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.cpp +++ b/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.cpp @@ -4,6 +4,10 @@ #include +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) +#include +#endif + static void wakeup(void* ctx) { // This callback is invoked from any mpv thread (but possibly also // recursively from a thread that is calling the mpv API). Just notify @@ -13,13 +17,27 @@ static void wakeup(void* ctx) { emit backend->launchMpvEvents(); } -LibMpvWidget::LibMpvWidget(mpv_handle* mpv_handle, QWidget* parent) : QWidget(parent), m_mpvHandle(mpv_handle) { +LibMpvWidget::LibMpvWidget(mpv_handle* mpv_handle, QWidget* parent) + : BASE_WIDGET(parent), m_mpvHandle(mpv_handle) +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) + , + m_mpvGl(nullptr) +#endif +{ +#if !defined(MEDIAPLAYER_LIBMPV_OPENGL) setAttribute(Qt::WidgetAttribute::WA_DontCreateNativeAncestors); setAttribute(Qt::WidgetAttribute::WA_NativeWindow); +#endif + setMouseTracking(true); } +LibMpvWidget::~LibMpvWidget() { + destroyHandle(); +} + void LibMpvWidget::bind() { +#if !defined(MEDIAPLAYER_LIBMPV_OPENGL) auto raw_wid = winId(); #if defined(Q_OS_WIN) @@ -31,6 +49,82 @@ void LibMpvWidget::bind() { #endif mpv_set_option(m_mpvHandle, "wid", MPV_FORMAT_INT64, &wid); +#endif mpv_set_wakeup_callback(m_mpvHandle, wakeup, this); } + +void LibMpvWidget::destroyHandle() { +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) + makeCurrent(); + + if (m_mpvGl != nullptr) { + mpv_render_context_free(m_mpvGl); + m_mpvGl = nullptr; + } + + doneCurrent(); +#endif +} + +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) +static void* get_proc_address(void* ctx, const char* name) { + Q_UNUSED(ctx); + + QOpenGLContext* glctx = QOpenGLContext::currentContext(); + + if (!glctx) { + return nullptr; + } + + return reinterpret_cast(glctx->getProcAddress(QByteArray(name))); +} + +void LibMpvWidget::maybeUpdate() { + // If the Qt window is not visible, Qt's update() will just skip rendering. + // This confuses mpv's render API, and may lead to small occasional + // freezes due to video rendering timing out. + // Handle this by manually redrawing. + // Note: Qt doesn't seem to provide a way to query whether update() will + // be skipped, and the following code still fails when e.g. switching + // to a different workspace with a reparenting window manager. + if (window()->isMinimized()) { + makeCurrent(); + paintGL(); + context()->swapBuffers(context()->surface()); + doneCurrent(); + } + else { + update(); + } +} + +void LibMpvWidget::on_update(void* ctx) { + QMetaObject::invokeMethod((LibMpvWidget*)ctx, "maybeUpdate"); +} + +void LibMpvWidget::initializeGL() { + mpv_opengl_init_params gl_init_params[1] = {get_proc_address, nullptr}; + mpv_render_param params[]{{MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, + {MPV_RENDER_PARAM_INVALID, nullptr}}; + + if (mpv_render_context_create(&m_mpvGl, m_mpvHandle, params) < 0) { + qFatal("failed to initialize mpv GL context"); + } + + mpv_render_context_set_update_callback(m_mpvGl, LibMpvWidget::on_update, reinterpret_cast(this)); +} + +void LibMpvWidget::paintGL() { + mpv_opengl_fbo mpfbo{static_cast(defaultFramebufferObject()), width(), height(), 0}; + int flip_y{1}; + + mpv_render_param params[] = {{MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo}, + {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, + {MPV_RENDER_PARAM_INVALID, nullptr}}; + // See render_gl.h on what OpenGL environment mpv expects, and + // other API details. + mpv_render_context_render(m_mpvGl, params); +} +#endif diff --git a/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.h b/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.h index 19568c65d..9248584ec 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.h +++ b/src/librssguard/gui/mediaplayer/libmpv/libmpvwidget.h @@ -3,15 +3,27 @@ #ifndef LIBMPVWIDGET_H #define LIBMPVWIDGET_H +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) +#include + +#include + +#define BASE_WIDGET QOpenGLWidget +#else #include +#define BASE_WIDGET QWidget +#endif struct mpv_handle; -class LibMpvWidget : public QWidget { +class LibMpvWidget : public BASE_WIDGET { Q_OBJECT + friend class LibMpvBackend; + public: explicit LibMpvWidget(mpv_handle* mpv_handle, QWidget* parent = nullptr); + virtual ~LibMpvWidget(); void bind(); @@ -19,7 +31,23 @@ class LibMpvWidget : public QWidget { void launchMpvEvents(); private: + void destroyHandle(); + mpv_handle* m_mpvHandle; + +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) + protected: + virtual void initializeGL(); + virtual void paintGL(); + + private slots: + void maybeUpdate(); + + private: + static void on_update(void* ctx); + + mpv_render_context* m_mpvGl; +#endif }; #endif // LIBMPVWIDGET_H diff --git a/src/librssguard/gui/tabwidget.cpp b/src/librssguard/gui/tabwidget.cpp index d7354a5b4..234b72cf9 100644 --- a/src/librssguard/gui/tabwidget.cpp +++ b/src/librssguard/gui/tabwidget.cpp @@ -266,7 +266,7 @@ int TabWidget::addMediaPlayer(const QString& url, bool make_active) { player->setFocus(Qt::FocusReason::OtherFocusReason); } - QTimer::singleShot(500, player, [player, url]() { + QTimer::singleShot(3000, player, [player, url]() { player->playUrl(url); }); diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index 2b46e1524..7998c4648 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -44,6 +44,10 @@ #include #include +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) +#include +#endif + #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #include #include @@ -79,6 +83,16 @@ Application::Application(const QString& id, int& argc, char** argv, const QStringList& raw_cli_args) : SingleApplication(id, argc, argv), m_rawCliArgs(raw_cli_args), m_updateFeedsLock(new Mutex()) { + +#if defined(MEDIAPLAYER_LIBMPV_OPENGL) + // HACK: Force rendering system to use OpenGL backend. +#if QT_VERSION_MAJOR < 6 + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::GraphicsApi::OpenGL); +#else + QQuickWindow::setGraphicsApi(QSGRendererInterface::GraphicsApi::OpenGL); +#endif +#endif + QString custom_ua; parseCmdArgumentsFromMyInstance(raw_cli_args, custom_ua);