154 lines
4.8 KiB
C++
154 lines
4.8 KiB
C++
// For license of this file, see <project-root-folder>/LICENSE.md.
|
|
|
|
#include "gui/mediaplayer/libmpv/libmpvwidget.h"
|
|
|
|
#include "miscellaneous/application.h"
|
|
|
|
#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
|
|
// the Qt GUI thread to wake up (so that it can process events with
|
|
// mpv_wait_event()), and return as quickly as possible.
|
|
LibMpvWidget* backend = (LibMpvWidget*)ctx;
|
|
emit backend->launchMpvEvents();
|
|
}
|
|
|
|
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)
|
|
// Truncate to 32-bit, as all Windows handles are. This also ensures
|
|
// it doesn't go negative.
|
|
int64_t wid = static_cast<uint32_t>(raw_wid);
|
|
#else
|
|
int64_t wid = raw_wid;
|
|
#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::onMpvRedraw(void* ctx) {
|
|
QMetaObject::invokeMethod((LibMpvWidget*)ctx, &LibMpvWidget::maybeUpdate, Qt::ConnectionType::QueuedConnection);
|
|
}
|
|
|
|
void LibMpvWidget::initializeGL() {
|
|
mpv_opengl_init_params gl_init_params[1] = {get_proc_address, nullptr};
|
|
mpv_render_param display{MPV_RENDER_PARAM_INVALID, nullptr};
|
|
|
|
#if QT_VERSION_MAJOR == 6 && defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
|
|
#if defined(QT_FEATURE_xcb)
|
|
if (QGuiApplication::platformName() == QStringLiteral("xcb")) {
|
|
display.type = MPV_RENDER_PARAM_X11_DISPLAY;
|
|
display.data = qApp->nativeInterface<QNativeInterface::QX11Application>()->display();
|
|
}
|
|
#endif
|
|
|
|
#if defined(QT_FEATURE_wayland)
|
|
if (QGuiApplication::platformName() == QStringLiteral("wayland")) {
|
|
display.type = MPV_RENDER_PARAM_WL_DISPLAY;
|
|
display.data = qApp->nativeInterface<QNativeInterface::QWaylandApplication>()->display();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
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},
|
|
display,
|
|
{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::onMpvRedraw, reinterpret_cast<void*>(this));
|
|
}
|
|
|
|
void LibMpvWidget::paintGL() {
|
|
qreal scaling_ratio = window()->devicePixelRatio();
|
|
mpv_opengl_fbo mpfbo{static_cast<int>(defaultFramebufferObject()),
|
|
int(width() * scaling_ratio),
|
|
int(height() * scaling_ratio),
|
|
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}};
|
|
|
|
mpv_render_context_render(m_mpvGL, params);
|
|
}
|
|
#endif
|