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_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()

View File

@ -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}
)

View File

@ -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;
}

View File

@ -4,6 +4,10 @@
#include <mpv/client.h>
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QOpenGLContext>
#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<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
#define LIBMPVWIDGET_H
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QOpenGLWidget>
#include <mpv/render_gl.h>
#define BASE_WIDGET QOpenGLWidget
#else
#include <QWidget>
#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

View File

@ -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);
});

View File

@ -44,6 +44,10 @@
#include <QThreadPool>
#include <QTimer>
#if defined(MEDIAPLAYER_LIBMPV_OPENGL)
#include <QQuickWindow>
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include <QDBusConnection>
#include <QDBusMessage>
@ -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);