add optional opengl-based rendering for libmpv

This commit is contained in:
Martin Rotter 2023-12-19 12:20:28 +01:00
parent a242564f9c
commit 61e15f9042
7 changed files with 166 additions and 6 deletions

View File

@ -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_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_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(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. # Import Qt libraries.
set(QT6_MIN_VERSION 6.3.0) set(QT6_MIN_VERSION 6.3.0)
@ -168,6 +169,16 @@ if(ENABLE_MEDIAPLAYER_LIBMPV)
set(LibMPV_ROOT "${CMAKE_SOURCE_DIR}/resources/scripts/libmpv") set(LibMPV_ROOT "${CMAKE_SOURCE_DIR}/resources/scripts/libmpv")
endif() 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) add_compile_definitions(ENABLE_MEDIAPLAYER_LIBMPV)
endif() endif()

View File

@ -832,13 +832,27 @@ endif()
if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA) if(ENABLE_MEDIAPLAYER_QTMULTIMEDIA)
target_link_libraries(rssguard PUBLIC target_link_libraries(rssguard PUBLIC
Qt${QT_VERSION_MAJOR}::OpenGL
Qt${QT_VERSION_MAJOR}::MultimediaWidgets Qt${QT_VERSION_MAJOR}::MultimediaWidgets
) )
elseif(ENABLE_MEDIAPLAYER_LIBMPV) 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 target_include_directories(rssguard AFTER
PRIVATE PRIVATE
${LibMPV_INCLUDE_DIRS} ${LibMPV_INCLUDE_DIRS}
) )
target_link_libraries(rssguard PUBLIC target_link_libraries(rssguard PUBLIC
${LibMPV_LIBRARIES} ${LibMPV_LIBRARIES}
) )

View File

@ -48,12 +48,10 @@ LibMpvBackend::LibMpvBackend(Application* app, QWidget* parent)
setMouseTracking(true); setMouseTracking(true);
layout()->addWidget(m_mpvContainer); layout()->addWidget(m_mpvContainer);
m_mpvContainer->bind(); m_mpvContainer->bind();
mpv_set_option_string(m_mpvHandle, "msg-level", "all=v"); mpv_set_option_string(m_mpvHandle, "msg-level", "all=v");
mpv_set_option_string(m_mpvHandle, "config", "yes"); 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, "script-opts", "osc-idlescreen=no");
mpv_set_option_string(m_mpvHandle, "hwdec", "auto"); mpv_set_option_string(m_mpvHandle, "hwdec", "auto");
mpv_set_option_string(m_mpvHandle, "osd-playing-msg", "${media-title}"); mpv_set_option_string(m_mpvHandle, "osd-playing-msg", "${media-title}");
@ -121,6 +119,7 @@ void LibMpvBackend::loadSettings() {
} }
LibMpvBackend::~LibMpvBackend() { LibMpvBackend::~LibMpvBackend() {
m_mpvContainer->destroyHandle();
destroyHandle(); destroyHandle();
} }
@ -155,7 +154,7 @@ void LibMpvBackend::handleMpvEvent(mpv_event* event) {
} }
case MPV_EVENT_SHUTDOWN: { case MPV_EVENT_SHUTDOWN: {
destroyHandle(); // destroyHandle();
emit closed(); emit closed();
break; break;
} }

View File

@ -4,6 +4,10 @@
#include <mpv/client.h> #include <mpv/client.h>
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QOpenGLContext>
#endif
static void wakeup(void* ctx) { static void wakeup(void* ctx) {
// This callback is invoked from any mpv thread (but possibly also // This callback is invoked from any mpv thread (but possibly also
// recursively from a thread that is calling the mpv API). Just notify // recursively from a thread that is calling the mpv API). Just notify
@ -13,13 +17,27 @@ static void wakeup(void* ctx) {
emit backend->launchMpvEvents(); 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_DontCreateNativeAncestors);
setAttribute(Qt::WidgetAttribute::WA_NativeWindow); setAttribute(Qt::WidgetAttribute::WA_NativeWindow);
#endif
setMouseTracking(true); setMouseTracking(true);
} }
LibMpvWidget::~LibMpvWidget() {
destroyHandle();
}
void LibMpvWidget::bind() { void LibMpvWidget::bind() {
#if !defined(MEDIAPLAYER_LIBMPV_OPENGL)
auto raw_wid = winId(); auto raw_wid = winId();
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@ -31,6 +49,82 @@ void LibMpvWidget::bind() {
#endif #endif
mpv_set_option(m_mpvHandle, "wid", MPV_FORMAT_INT64, &wid); mpv_set_option(m_mpvHandle, "wid", MPV_FORMAT_INT64, &wid);
#endif
mpv_set_wakeup_callback(m_mpvHandle, wakeup, this); 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<void*>(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<char*>(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<void*>(this));
}
void LibMpvWidget::paintGL() {
mpv_opengl_fbo mpfbo{static_cast<int>(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

View File

@ -3,15 +3,27 @@
#ifndef LIBMPVWIDGET_H #ifndef LIBMPVWIDGET_H
#define LIBMPVWIDGET_H #define LIBMPVWIDGET_H
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QOpenGLWidget>
#include <mpv/render_gl.h>
#define BASE_WIDGET QOpenGLWidget
#else
#include <QWidget> #include <QWidget>
#define BASE_WIDGET QWidget
#endif
struct mpv_handle; struct mpv_handle;
class LibMpvWidget : public QWidget { class LibMpvWidget : public BASE_WIDGET {
Q_OBJECT Q_OBJECT
friend class LibMpvBackend;
public: public:
explicit LibMpvWidget(mpv_handle* mpv_handle, QWidget* parent = nullptr); explicit LibMpvWidget(mpv_handle* mpv_handle, QWidget* parent = nullptr);
virtual ~LibMpvWidget();
void bind(); void bind();
@ -19,7 +31,23 @@ class LibMpvWidget : public QWidget {
void launchMpvEvents(); void launchMpvEvents();
private: private:
void destroyHandle();
mpv_handle* m_mpvHandle; 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 #endif // LIBMPVWIDGET_H

View File

@ -266,7 +266,7 @@ int TabWidget::addMediaPlayer(const QString& url, bool make_active) {
player->setFocus(Qt::FocusReason::OtherFocusReason); player->setFocus(Qt::FocusReason::OtherFocusReason);
} }
QTimer::singleShot(500, player, [player, url]() { QTimer::singleShot(3000, player, [player, url]() {
player->playUrl(url); player->playUrl(url);
}); });

View File

@ -44,6 +44,10 @@
#include <QThreadPool> #include <QThreadPool>
#include <QTimer> #include <QTimer>
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QQuickWindow>
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusMessage> #include <QDBusMessage>
@ -79,6 +83,16 @@
Application::Application(const QString& id, int& argc, char** argv, const QStringList& raw_cli_args) 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()) { : 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; QString custom_ua;
parseCmdArgumentsFromMyInstance(raw_cli_args, custom_ua); parseCmdArgumentsFromMyInstance(raw_cli_args, custom_ua);