Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mpvqt for openSUSE:Factory checked in at 2026-05-24 19:35:12 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mpvqt (Old) and /work/SRC/openSUSE:Factory/.mpvqt.new.2084 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mpvqt" Sun May 24 19:35:12 2026 rev:4 rq:1354901 version:1.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/mpvqt/mpvqt.changes 2025-04-20 20:04:01.327214697 +0200 +++ /work/SRC/openSUSE:Factory/.mpvqt.new.2084/mpvqt.changes 2026-05-24 19:36:17.888859757 +0200 @@ -1,0 +2,10 @@ +Sun May 24 06:59:39 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 1.2.0 + * cmake: install Qt metatypes files + * mpvcontroller: eventHandler: handle `stop` reason for + `MPV_EVENT_END_FILE` + * Refactor code + * Update examples + +------------------------------------------------------------------- Old: ---- mpvqt-1.1.1.tar.xz mpvqt-1.1.1.tar.xz.sig New: ---- mpvqt-1.2.0.tar.xz mpvqt-1.2.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mpvqt.spec ++++++ --- /var/tmp/diff_new_pack.c7PjwV/_old 2026-05-24 19:36:18.544886652 +0200 +++ /var/tmp/diff_new_pack.c7PjwV/_new 2026-05-24 19:36:18.548886816 +0200 @@ -1,7 +1,7 @@ # # spec file for package mpvqt # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,12 +16,12 @@ # -%define kf6_version 6.0.0 +%define kf6_version 6.15.0 %define qt6_version 6.5.0 %bcond_without released Name: mpvqt -Version: 1.1.1 +Version: 1.2.0 Release: 0 Summary: Libmpv wrapper for QtQuick2 and QML License: LGPL-2.1-or-later @@ -39,17 +39,17 @@ %description MpvQt is a libmpv wrapper for QtQuick2 and QML. -%package -n libMpvQt2 +%package -n libMpvQt3 Summary: Libmpv wrapper for QtQuick2 and QML # Marked as runtime deps in the build system, but looks unneeded # Recommends: (yt-dlp or youtube-dl) -%description -n libMpvQt2 +%description -n libMpvQt3 MpvQt is a libmpv wrapper for QtQuick2 and QML. %package devel Summary: Development files for mpvqt -Requires: libMpvQt2 = %{version} +Requires: libMpvQt3 = %{version} Requires: cmake(Qt6Quick) >= %{qt6_version} Requires: pkgconfig(mpv) @@ -67,9 +67,9 @@ %install %kf6_install -%ldconfig_scriptlets -n libMpvQt2 +%ldconfig_scriptlets -n libMpvQt3 -%files -n libMpvQt2 +%files -n libMpvQt3 %license LICENSES/* %{_kf6_libdir}/libMpvQt.so.* @@ -78,4 +78,7 @@ %{_includedir}/MpvQt/ %{_kf6_cmakedir}/MpvQt/ %{_kf6_libdir}/libMpvQt.so +%if %{pkg_vcmp kf6-extra-cmake-modules >= 6.27} +%{_qt6_metatypesdir}/qt6mpvqt_metatypes.json +%endif ++++++ mpvqt-1.1.1.tar.xz -> mpvqt-1.2.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/.gitlab-ci.yml new/mpvqt-1.2.0/.gitlab-ci.yml --- old/mpvqt-1.1.1/.gitlab-ci.yml 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/.gitlab-ci.yml 2026-05-18 10:42:52.000000000 +0200 @@ -8,4 +8,4 @@ - /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6-next.yml - /gitlab-templates/freebsd-qt6.yml - - /gitlab-templates/android-qt6.yml + # - /gitlab-templates/android-qt6.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/.kde-ci.yml new/mpvqt-1.2.0/.kde-ci.yml --- old/mpvqt-1.1.1/.kde-ci.yml 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/.kde-ci.yml 2026-05-18 10:42:52.000000000 +0200 @@ -5,3 +5,7 @@ - 'on': ['@all'] 'require': 'frameworks/extra-cmake-modules': '@latest-kf6' + +Options: + require-passing-tests-on: [ '@all' ] + enable-lsan: True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/CMakeLists.txt new/mpvqt-1.2.0/CMakeLists.txt --- old/mpvqt-1.1.1/CMakeLists.txt 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/CMakeLists.txt 2026-05-18 10:42:52.000000000 +0200 @@ -4,19 +4,52 @@ # SPDX-License-Identifier: BSD-3-Clause # -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16...3.31) -project(MpvQt VERSION 1.1.1 LANGUAGES CXX) +project(MpvQt VERSION 1.2.0 LANGUAGES CXX) set(REQUIRED_QT_VERSION 6.5.0) include(FeatureSummary) -find_package(ECM 6.0.0 NO_MODULE) +find_package(ECM 6.15.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED URL "https://api.kde.org/ecm/" - DESCRIPTION "extra cmake modules") -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + DESCRIPTION "Extra modules and scripts for CMake") + +if(ECM_FOUND) + set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + + include(KDEInstallDirs) + include(KDECMakeSettings NO_POLICY_SCOPE) + include(KDECompilerSettings NO_POLICY_SCOPE) + include(ECMGenerateHeaders) + include(ECMGenerateExportHeader) + + set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/MpvQt") + + include(KDEClangFormat) + file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.hpp *.c) + kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) + + include(KDEGitCommitHooks) + kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) + + include(ECMSetupVersion) + ecm_setup_version(PROJECT + VARIABLE_PREFIX MPVQT + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/mpvqt_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/MpvQtConfigVersion.cmake" + SOVERSION 3 + ) + + include(CMakePackageConfigHelpers) + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/MpvQtConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/MpvQtConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} + ) +endif() find_package(Qt6Quick ${REQUIRED_QT_VERSION}) set_package_properties(Qt6Quick PROPERTIES TYPE REQUIRED) @@ -27,39 +60,10 @@ feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) -include(KDEInstallDirs) -include(KDECMakeSettings NO_POLICY_SCOPE) -include(KDECompilerSettings NO_POLICY_SCOPE) -include(ECMGenerateHeaders) -include(ECMGenerateExportHeader) - -set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/MpvQt") - -include(KDEClangFormat) -file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.hpp *.c) -kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) - -include(KDEGitCommitHooks) -kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) - -include(ECMSetupVersion) -ecm_setup_version(PROJECT - VARIABLE_PREFIX MPVQT - VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/mpvqt_version.h" - PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/MpvQtConfigVersion.cmake" - SOVERSION 2 -) - -include(CMakePackageConfigHelpers) -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/MpvQtConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/MpvQtConfig.cmake" - INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} -) - add_subdirectory(src) option(BUILD_EXAMPLES "Whether to build the examples" OFF) if (BUILD_EXAMPLES) add_subdirectory(examples/video-player) + add_subdirectory(examples/multiple-video-players) endif() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/README.md new/mpvqt-1.2.0/README.md --- old/mpvqt-1.1.1/README.md 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/README.md 2026-05-18 10:42:52.000000000 +0200 @@ -8,10 +8,44 @@ ## How to use -- Create a class extending `MpvAbstractItem` (check the [mpvitem.h](examples/video-player/mpvitem.h)/[mpvitem.cpp](examples/video-player/mpvitem.cpp) files in the example) -- Register the class `qmlRegisterType<ClassName>("com.example.mpv", 1, 0, "NameUsedInQml");` -- In your qml file import mpv `import com.example.mpv 1.0` -- Then create an instance `NameUsedInQml {}` (check the [Main.qml](examples/video-player/Main.qml) file in the example) +- Create a class extending `MpvAbstractItem` + +```c++ +class MpvItem : public MpvAbstractItem +{ + Q_OBJECT + QML_ELEMENT + // ... +} +``` +- Add the class to a qml module + +```cmake +qt_add_qml_module(videoplayer + URI com.example.mpvqt + QML_FILES + Main.qml +) + +target_sources(videoplayer + PRIVATE + mpvitem.h mpvitem.cpp +) +``` +or +```cmake +qt_add_qml_module(videoplayer + URI com.example.mpvqt + QML_FILES + Main.qml + SOURCES + mpvitem.h mpvitem.cpp +) +``` + +- In your qml file import mpv `import com.example.mpvqt` +- Then create an instance `MpvItem {}`, `MpvItem` is the class name extending MpvAbstractItem, + if you want to use another name replace `QML_ELEMENT` with `QML_NAMED_ELEMENT(VideoPlayer)` ## Config file MpvQt loads a config file located at `<config_folder>/mpvqt/mpvqt.conf`, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/CMakeLists.txt new/mpvqt-1.2.0/examples/multiple-video-players/CMakeLists.txt --- old/mpvqt-1.1.1/examples/multiple-video-players/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/CMakeLists.txt 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,48 @@ +# +# SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> +# +# SPDX-License-Identifier: BSD-3-Clause +# + +cmake_minimum_required(VERSION 3.16...3.31) + +project(multiplevideoplayers LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) + +find_package(ECM 6.0.0 REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +include(KDEInstallDirs) +include(KDECMakeSettings NO_POLICY_SCOPE) +include(KDECompilerSettings NO_POLICY_SCOPE) + +find_package(Qt6 COMPONENTS Core Quick REQUIRED) + +find_package(MpvQt) + +qt_standard_project_setup(REQUIRES 6.5) +qt_add_executable(multiplevideoplayers) + +qt_policy(SET QTP0001 NEW) +qt_add_qml_module(multiplevideoplayers + URI com.example.mpvqt + QML_FILES + Main.qml +) + +target_sources(multiplevideoplayers + PRIVATE + main.cpp + mpvitem.h mpvitem.cpp + mpvproperties.h +) + +target_link_libraries(multiplevideoplayers + PRIVATE + Qt6::Core + Qt6::Quick + MpvQt::MpvQt +) + +install(TARGETS multiplevideoplayers DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/Main.qml new/mpvqt-1.2.0/examples/multiple-video-players/Main.qml --- old/mpvqt-1.1.1/examples/multiple-video-players/Main.qml 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/Main.qml 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> + * + * SPDX-License-Identifier: MIT + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import com.example.mpvqt + +Window { + id: window + + width: 1000 + height: 600 + visible: true + title: "multiple videos" + + function addTab(videoFile: string) { + const index = tabBar.count + const tabPage = mpvComponent.createObject(tabLayout, {index: index, videoFile: videoFile}) + var tab = tabComponent.createObject(tabBar, {index: index, mpvItem: tabPage}) + + tabBar.currentIndex = index + } + + Component.onCompleted: { + // window.addTab("/path/to/video/file1.mkv") + // window.addTab("/path/to/video/file2.mkv") + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + + background: Item {} + + Layout.fillWidth: true + Layout.preferredHeight: 50 + + } + + StackLayout { + id: tabLayout + + Layout.fillWidth: true + Layout.fillHeight: true + + currentIndex: tabBar.currentIndex + } + } + + Component { + id: tabComponent + + TabButton { + id: tabDelegate + + required property int index + required property MpvItem mpvItem + + implicitHeight: parent.height + text: `Tab ${index}` + width: 190 + + TapHandler { + acceptedButtons: Qt.MiddleButton + onSingleTapped: { + tabDelegate.destroy() + tabDelegate.mpvItem.destroy() + } + } + } + } + + Component { + id: mpvComponent + + MpvItem { + id: mpv + + required property int index + required property string videoFile + + Layout.fillWidth: true + Layout.fillHeight: true + + // do not load file on Component.onCompleted + onReady: loadFile([videoFile]) + volume: index === 0 ? 80 : 0 + + Rectangle { + width: mpv.width + height: 40 + color: Qt.alpha("#333", 0.5) + anchors.bottom: mpv.bottom + + RowLayout { + anchors.fill: parent + + Text { + text: `${mpv.formattedPosition} / ${mpv.formattedDuration}` + color: "#fff" + Layout.leftMargin: 10 + } + + Slider { + id: slider + from: 0 + to: mpv.duration + value: mpv.position + onValueChanged: mpv.position = value + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.rightMargin: 10 + } + } + } + } + } + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/main.cpp new/mpvqt-1.2.0/examples/multiple-video-players/main.cpp --- old/mpvqt-1.1.1/examples/multiple-video-players/main.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/main.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> + * + * SPDX-License-Identifier: MIT + */ + +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QQuickWindow> + +int main(int argc, char *argv[]) +{ + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine(&app); + // clang-format off + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + // clang-format on + engine.loadFromModule("com.example.mpvqt", "Main"); + + return app.exec(); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/mpvitem.cpp new/mpvqt-1.2.0/examples/multiple-video-players/mpvitem.cpp --- old/mpvqt-1.1.1/examples/multiple-video-players/mpvitem.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/mpvitem.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,180 @@ +/* + * SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> + * + * SPDX-License-Identifier: MIT + */ + +#include "mpvitem.h" + +#include <MpvController> + +#include "mpvproperties.h" + +MpvItem::MpvItem(QQuickItem *parent) + : MpvAbstractItem(parent) +{ + observeProperty(MpvProperties::self()->MediaTitle, MPV_FORMAT_STRING); + observeProperty(MpvProperties::self()->Position, MPV_FORMAT_DOUBLE); + observeProperty(MpvProperties::self()->Duration, MPV_FORMAT_DOUBLE); + observeProperty(MpvProperties::self()->Pause, MPV_FORMAT_FLAG); + observeProperty(MpvProperties::self()->Volume, MPV_FORMAT_INT64); + + setupConnections(); + setProperty(QStringLiteral("mute"), false); + setProperty(QStringLiteral("volume"), 80); +} + +void MpvItem::setupConnections() +{ + // clang-format off + connect(mpvController(), &MpvController::propertyChanged, + this, &MpvItem::onPropertyChanged, Qt::QueuedConnection); + + connect(mpvController(), &MpvController::fileStarted, + this, &MpvItem::fileStarted, Qt::QueuedConnection); + + connect(mpvController(), &MpvController::fileLoaded, + this, &MpvItem::fileLoaded, Qt::QueuedConnection); + + connect(mpvController(), &MpvController::endFile, + this, &MpvItem::endFile, Qt::QueuedConnection); + + connect(mpvController(), &MpvController::videoReconfig, + this, &MpvItem::videoReconfig, Qt::QueuedConnection); + + connect(mpvController(), &MpvController::asyncReply, + this, &MpvItem::onAsyncReply, Qt::QueuedConnection); + // clang-format on +} + +void MpvItem::onPropertyChanged(const QString &property, const QVariant &value) +{ + if (property == MpvProperties::self()->MediaTitle) { + Q_EMIT mediaTitleChanged(); + + } else if (property == MpvProperties::self()->Position) { + m_formattedPosition = formatTime(value.toDouble()); + Q_EMIT positionChanged(); + + } else if (property == MpvProperties::self()->Duration) { + m_formattedDuration = formatTime(value.toDouble()); + Q_EMIT durationChanged(); + + } else if (property == MpvProperties::self()->Pause) { + Q_EMIT pauseChanged(); + + } else if (property == MpvProperties::self()->Volume) { + Q_EMIT volumeChanged(); + } +} + +void MpvItem::onAsyncReply(const QVariant &data, mpv_event event) +{ + switch (static_cast<AsyncIds>(event.reply_userdata)) { + case AsyncIds::None: { + break; + } + case AsyncIds::SetVolume: { + qDebug() << "onSetPropertyReply" << event.reply_userdata; + break; + } + case AsyncIds::GetVolume: { + qDebug() << "onGetPropertyReply" << event.reply_userdata << data; + break; + } + case AsyncIds::ExpandText: { + qDebug() << "onGetPropertyReply" << event.reply_userdata << data; + break; + } + } +} + +QString MpvItem::formatTime(const double time) +{ + int totalNumberOfSeconds = static_cast<int>(time); + int seconds = totalNumberOfSeconds % 60; + int minutes = (totalNumberOfSeconds / 60) % 60; + int hours = (totalNumberOfSeconds / 60 / 60); + + QString timeString = + QStringLiteral("%1:%2:%3").arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')); + + return timeString; +} + +void MpvItem::loadFile(const QString &file) +{ + auto url = QUrl::fromUserInput(file); + if (m_currentUrl != url) { + m_currentUrl = url; + Q_EMIT currentUrlChanged(); + } + + command(QStringList() << QStringLiteral("loadfile") << m_currentUrl.toString(QUrl::PreferLocalFile)); +} + +QString MpvItem::mediaTitle() +{ + return getProperty(MpvProperties::self()->MediaTitle).toString(); +} + +double MpvItem::position() +{ + return getProperty(MpvProperties::self()->Position).toDouble(); +} + +void MpvItem::setPosition(double value) +{ + if (qFuzzyCompare(value, position())) { + return; + } + setPropertyAsync(MpvProperties::self()->Position, value); +} + +double MpvItem::duration() +{ + return getProperty(MpvProperties::self()->Duration).toDouble(); +} + +bool MpvItem::pause() +{ + return getProperty(MpvProperties::self()->Pause).toBool(); +} + +void MpvItem::setPause(bool value) +{ + if (value == pause()) { + return; + } + setPropertyAsync(MpvProperties::self()->Pause, value); +} + +int MpvItem::volume() +{ + return getProperty(MpvProperties::self()->Volume).toInt(); +} + +void MpvItem::setVolume(int value) +{ + if (value == volume()) { + return; + } + setPropertyAsync(MpvProperties::self()->Volume, value); +} + +QString MpvItem::formattedDuration() const +{ + return m_formattedDuration; +} + +QString MpvItem::formattedPosition() const +{ + return m_formattedPosition; +} + +QUrl MpvItem::currentUrl() const +{ + return m_currentUrl; +} + +#include "moc_mpvitem.cpp" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/mpvitem.h new/mpvqt-1.2.0/examples/multiple-video-players/mpvitem.h --- old/mpvqt-1.1.1/examples/multiple-video-players/mpvitem.h 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/mpvitem.h 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef MPVOBJECT_H +#define MPVOBJECT_H + +#include <MpvAbstractItem> +#include <qqmlintegration.h> + +class MpvRenderer; + +class MpvItem : public MpvAbstractItem +{ + Q_OBJECT + QML_ELEMENT +public: + explicit MpvItem(QQuickItem *parent = nullptr); + ~MpvItem() = default; + + enum class AsyncIds { + None, + SetVolume, + GetVolume, + ExpandText, + }; + Q_ENUM(AsyncIds) + + Q_PROPERTY(QString mediaTitle READ mediaTitle NOTIFY mediaTitleChanged) + QString mediaTitle(); + + Q_PROPERTY(double position READ position WRITE setPosition NOTIFY positionChanged) + double position(); + void setPosition(double value); + + Q_PROPERTY(double duration READ duration NOTIFY durationChanged) + double duration(); + + Q_PROPERTY(QString formattedPosition READ formattedPosition NOTIFY positionChanged) + QString formattedPosition() const; + + Q_PROPERTY(QString formattedDuration READ formattedDuration NOTIFY durationChanged) + QString formattedDuration() const; + + Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged) + bool pause(); + void setPause(bool value); + + Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) + int volume(); + void setVolume(int value); + + Q_PROPERTY(QUrl currentUrl READ currentUrl NOTIFY currentUrlChanged) + QUrl currentUrl() const; + + Q_INVOKABLE void loadFile(const QString &file); + +Q_SIGNALS: + void mediaTitleChanged(); + void currentUrlChanged(); + void positionChanged(); + void durationChanged(); + void pauseChanged(); + void volumeChanged(); + + void fileStarted(); + void fileLoaded(); + void endFile(QString reason); + void videoReconfig(); + +private: + void setupConnections(); + void onPropertyChanged(const QString &property, const QVariant &value); + void onAsyncReply(const QVariant &data, mpv_event event); + QString formatTime(const double time); + + double m_position{0.0}; + QString m_formattedPosition; + double m_duration{0.0}; + QString m_formattedDuration; + QUrl m_currentUrl; +}; + +#endif // MPVOBJECT_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/multiple-video-players/mpvproperties.h new/mpvqt-1.2.0/examples/multiple-video-players/mpvproperties.h --- old/mpvqt-1.1.1/examples/multiple-video-players/mpvproperties.h 1970-01-01 01:00:00.000000000 +0100 +++ new/mpvqt-1.2.0/examples/multiple-video-players/mpvproperties.h 2026-05-18 10:42:52.000000000 +0200 @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2023 George Florea Bănuș <[email protected]> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef MPVPROPERTIES_H +#define MPVPROPERTIES_H + +#include <QObject> +#include <qqmlintegration.h> + +class MpvProperties : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + explicit MpvProperties(QObject *parent = nullptr) + : QObject(parent) + { + } + + static MpvProperties *self() + { + static MpvProperties p; + return &p; + } + + Q_PROPERTY(QString MediaTitle MEMBER MediaTitle CONSTANT) + const QString MediaTitle{QStringLiteral("media-title")}; + + Q_PROPERTY(QString Position MEMBER Position CONSTANT) + const QString Position{QStringLiteral("time-pos")}; + + Q_PROPERTY(QString Duration MEMBER Duration CONSTANT) + const QString Duration{QStringLiteral("duration")}; + + Q_PROPERTY(QString Pause MEMBER Pause CONSTANT) + const QString Pause{QStringLiteral("pause")}; + + Q_PROPERTY(QString Volume MEMBER Volume CONSTANT) + const QString Volume{QStringLiteral("volume")}; + + Q_PROPERTY(QString Mute MEMBER Mute CONSTANT) + const QString Mute{QStringLiteral("mute")}; + +private: + Q_DISABLE_COPY_MOVE(MpvProperties) +}; + +#endif // MPVPROPERTIES_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/video-player/CMakeLists.txt new/mpvqt-1.2.0/examples/video-player/CMakeLists.txt --- old/mpvqt-1.1.1/examples/video-player/CMakeLists.txt 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/examples/video-player/CMakeLists.txt 2026-05-18 10:42:52.000000000 +0200 @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause # -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16...3.31) project(videoplayer LANGUAGES CXX) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/examples/video-player/mpvitem.cpp new/mpvqt-1.2.0/examples/video-player/mpvitem.cpp --- old/mpvqt-1.1.1/examples/video-player/mpvitem.cpp 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/examples/video-player/mpvitem.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -119,7 +119,7 @@ Q_EMIT currentUrlChanged(); } - Q_EMIT command(QStringList() << QStringLiteral("loadfile") << m_currentUrl.toString(QUrl::PreferLocalFile)); + command(QStringList() << QStringLiteral("loadfile") << m_currentUrl.toString(QUrl::PreferLocalFile)); } QString MpvItem::mediaTitle() @@ -137,7 +137,7 @@ if (qFuzzyCompare(value, position())) { return; } - Q_EMIT setPropertyAsync(MpvProperties::self()->Position, value); + setPropertyAsync(MpvProperties::self()->Position, value); } double MpvItem::duration() @@ -155,7 +155,7 @@ if (value == pause()) { return; } - Q_EMIT setPropertyAsync(MpvProperties::self()->Pause, value); + setPropertyAsync(MpvProperties::self()->Pause, value); } int MpvItem::volume() @@ -168,7 +168,7 @@ if (value == volume()) { return; } - Q_EMIT setPropertyAsync(MpvProperties::self()->Volume, value); + setPropertyAsync(MpvProperties::self()->Volume, value); } QString MpvItem::formattedDuration() const diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/CMakeLists.txt new/mpvqt-1.2.0/src/CMakeLists.txt --- old/mpvqt-1.1.1/src/CMakeLists.txt 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/CMakeLists.txt 2026-05-18 10:42:52.000000000 +0200 @@ -13,6 +13,15 @@ EXPORT_NAME MpvQt ) +if(${ECM_VERSION} VERSION_GREATER_EQUAL "6.27") + qt_extract_metatypes(MpvQt OUTPUT_FILES METATYPES_FILE) + get_filename_component(METATYPES_FILE_NAME ${METATYPES_FILE} NAME) + target_sources(MpvQt + INTERFACE + $<INSTALL_INTERFACE:${KDE_INSTALL_QTMETATYPESDIR}/${METATYPES_FILE_NAME}> + ) +endif() + target_sources(MpvQt PRIVATE mpvabstractitem.h mpvabstractitem.cpp @@ -22,7 +31,7 @@ ecm_generate_export_header(MpvQt BASE_NAME MpvQt - VERSION ${CMAKE_PROJECT_VERSION} + VERSION ${PROJECT_VERSION} USE_VERSION_HEADER ) @@ -43,6 +52,9 @@ ) install(TARGETS MpvQt EXPORT MpvQtTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +if(${ECM_VERSION} VERSION_GREATER_EQUAL "6.27") + install(FILES ${METATYPES_FILE} DESTINATION ${KDE_INSTALL_QTMETATYPESDIR}) +endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mpvqt_export.h diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvabstractitem.cpp new/mpvqt-1.2.0/src/mpvabstractitem.cpp --- old/mpvqt-1.1.1/src/mpvabstractitem.cpp 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvabstractitem.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -7,12 +7,15 @@ #include "mpvabstractitem.h" #include "mpvabstractitem_p.h" +#include <QLoggingCategory> #include <QQuickWindow> #include <QThread> #include "mpvcontroller.h" #include "mpvrenderer.h" +Q_LOGGING_CATEGORY(MpvQt_MpvAbstractItem, "MpvQt.MpvAbstractItem") + MpvAbstractItemPrivate::MpvAbstractItemPrivate(MpvAbstractItem *q) : q_ptr(q) { @@ -23,42 +26,39 @@ , d_ptr{std::make_unique<MpvAbstractItemPrivate>(this)} { if (QQuickWindow::graphicsApi() != QSGRendererInterface::OpenGL) { - qDebug() << "MpvAbstractItem: " - "The graphics api must be set to opengl or mpv won't be able to render the video.\n" - "QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL)\n" - "The call to the function must happen before constructing " - "the first QQuickWindow in the application."; + qCCritical(MpvQt_MpvAbstractItem) << "The graphics api must be set to opengl " + "or mpv won't be able to render the video.\n" + "QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL)\n" + "The call to the function must happen before constructing " + "the first QQuickWindow in the application."; } - d_ptr->m_workerThread = new QThread; + d_ptr->m_workerThread = new QThread(this); d_ptr->m_mpvController = new MpvController; - d_ptr->m_workerThread->start(); + + connect(d_ptr->m_workerThread, &QThread::finished, d_ptr->m_mpvController, &MpvController::deleteLater); + d_ptr->m_mpvController->moveToThread(d_ptr->m_workerThread); + d_ptr->m_workerThread->start(); + + // must wait for init to finish or the mpv object could be accessed while not initialized QMetaObject::invokeMethod(d_ptr->m_mpvController, &MpvController::init, Qt::BlockingQueuedConnection); - d_ptr->m_mpv = d_ptr->m_mpvController->mpv(); - connect(d_ptr->m_workerThread, &QThread::finished, d_ptr->m_mpvController, &MpvController::deleteLater); - connect(this, &MpvAbstractItem::observeProperty, d_ptr->m_mpvController, &MpvController::observeProperty, Qt::QueuedConnection); - connect(this, &MpvAbstractItem::setProperty, d_ptr->m_mpvController, &MpvController::setProperty, Qt::QueuedConnection); - connect(this, &MpvAbstractItem::command, d_ptr->m_mpvController, &MpvController::command, Qt::QueuedConnection); + auto mpvHandleManager = mpvController()->mpvHandleManager(); + auto renderContext{nullptr}; + d_ptr->m_mpvResourceManager = std::make_shared<MpvResourceManager>(renderContext, mpvHandleManager); } MpvAbstractItem::~MpvAbstractItem() { - if (d_ptr->m_mpv_gl) { - mpv_render_context_free(d_ptr->m_mpv_gl); - } - mpv_set_wakeup_callback(d_ptr->m_mpv, nullptr, nullptr); - d_ptr->m_workerThread->quit(); d_ptr->m_workerThread->wait(); d_ptr->m_workerThread->deleteLater(); - mpv_terminate_destroy(d_ptr->m_mpv); } QQuickFramebufferObject::Renderer *MpvAbstractItem::createRenderer() const { - return new MpvRenderer(const_cast<MpvAbstractItem *>(this)); + return new MpvRenderer(); } MpvController *MpvAbstractItem::mpvController() @@ -66,15 +66,48 @@ return d_ptr->m_mpvController; } +// clang-format off + +void MpvAbstractItem::observeProperty(const QString &property, mpv_format format, uint64_t id) +{ + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::observeProperty, + Qt::QueuedConnection, + property, + format, + id); +} + +int MpvAbstractItem::unobserveProperty(uint64_t id) +{ + int result = 0; + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::unobserveProperty, + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(int, result), + id); + + return result; +} + +void MpvAbstractItem::setProperty(const QString &property, const QVariant &value) +{ + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::setProperty, + Qt::QueuedConnection, + property, + value); +} + int MpvAbstractItem::setPropertyBlocking(const QString &property, const QVariant &value) { - int error; + int error = 0; QMetaObject::invokeMethod(d_ptr->m_mpvController, - "setProperty", + &MpvController::setProperty, Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, error), - Q_ARG(QString, property), - Q_ARG(QVariant, value)); + property, + value); return error; } @@ -82,52 +115,78 @@ void MpvAbstractItem::setPropertyAsync(const QString &property, const QVariant &value, int id) { QMetaObject::invokeMethod(d_ptr->m_mpvController, - "setPropertyAsync", + &MpvController::setPropertyAsync, Qt::QueuedConnection, - Q_ARG(QString, property), - Q_ARG(QVariant, value), - Q_ARG(int, id)); + property, + value, + id); } QVariant MpvAbstractItem::getProperty(const QString &property) { QVariant value; - QMetaObject::invokeMethod(d_ptr->m_mpvController, "getProperty", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), Q_ARG(QString, property)); + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::getProperty, + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, value), + property); return value; } void MpvAbstractItem::getPropertyAsync(const QString &property, int id) { - QMetaObject::invokeMethod(d_ptr->m_mpvController, "getPropertyAsync", Qt::QueuedConnection, Q_ARG(QString, property), Q_ARG(int, id)); + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::getPropertyAsync, + Qt::QueuedConnection, + property, + id); +} + +void MpvAbstractItem::command(const QStringList ¶ms) +{ + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::command, + Qt::QueuedConnection, + params); } -QVariant MpvAbstractItem::commandBlocking(const QVariant ¶ms) +QVariant MpvAbstractItem::commandBlocking(const QStringList ¶ms) { QVariant value; - QMetaObject::invokeMethod(d_ptr->m_mpvController, "command", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), Q_ARG(QVariant, params)); + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::command, + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, value), + params); return value; } void MpvAbstractItem::commandAsync(const QStringList ¶ms, int id) { - QMetaObject::invokeMethod(d_ptr->m_mpvController, "commandAsync", Qt::QueuedConnection, Q_ARG(QVariant, params), Q_ARG(int, id)); + QMetaObject::invokeMethod(d_ptr->m_mpvController, + &MpvController::commandAsync, + Qt::QueuedConnection, + params, + id); } QVariant MpvAbstractItem::expandText(const QString &text) { QVariant value; QMetaObject::invokeMethod(d_ptr->m_mpvController, - "command", + &MpvController::command, Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, value), - Q_ARG(QVariant, QVariant::fromValue(QStringList{QStringLiteral("expand-text"), text}))); + QStringList() << QStringLiteral("expand-text") << text); return value; } -int MpvAbstractItem::unobserveProperty(uint64_t id) +// clang-format on + +void MpvAbstractItem::requestUpdateFromRenderer() { - return mpvController()->unobserveProperty(id); + update(); } #include "moc_mpvabstractitem.cpp" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvabstractitem.h new/mpvqt-1.2.0/src/mpvabstractitem.h --- old/mpvqt-1.1.1/src/mpvabstractitem.h 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvabstractitem.h 2026-05-18 10:42:52.000000000 +0200 @@ -7,15 +7,55 @@ #ifndef MPVABSTRACTITEM_H #define MPVABSTRACTITEM_H +#include "mpvcontroller.h" #include "mpvqt_export.h" #include <QtQuick/QQuickFramebufferObject> + #include <mpv/client.h> #include <mpv/render_gl.h> class MpvController; class MpvAbstractItemPrivate; +/** + * MpvResourceManager is a lifecycle management utility designed + * to handle the safe initialization and destruction of mpv_render_context. + * Its primary goal is to ensure that GPU-bound resources are released + * on the correct thread and that the underlying mpv_handle remains valid + * until all rendering resources are fully cleaned up + */ +struct MpvResourceManager { + // raw pointer to the mpv OpenGL render context + mpv_render_context *mpvRenderContext{nullptr}; + // Shared pointer to the manager owning the mpv_handle + // Ensures the core mpv instance outlives the rendering context + std::shared_ptr<MpvHandleManager> mpvHandleManager; + + MpvResourceManager(mpv_render_context *c, std::shared_ptr<MpvHandleManager> owner) + : mpvRenderContext(c) + , mpvHandleManager(owner) + { + } + + /** + * cleans up mpv's rendering context + * + * MUST be called from the Qt Render Thread (MpvRenderer) + * + * An OpenGL context must be current in the calling thread. + * This must be the same context used to create the mpvRenderContext. + */ + void freeContext() + { + if (mpvRenderContext) { + mpv_render_context_set_update_callback(mpvRenderContext, nullptr, nullptr); + mpv_render_context_free(mpvRenderContext); + mpvRenderContext = nullptr; + } + } +}; + class MPVQT_EXPORT MpvAbstractItem : public QQuickFramebufferObject { Q_OBJECT @@ -24,22 +64,28 @@ ~MpvAbstractItem(); Renderer *createRenderer() const override; - Q_INVOKABLE int setPropertyBlocking(const QString &property, const QVariant &value); + + Q_INVOKABLE void observeProperty(const QString &property, mpv_format format, uint64_t id = 0); + Q_INVOKABLE int unobserveProperty(uint64_t id); + + Q_INVOKABLE void setProperty(const QString &property, const QVariant &value); Q_INVOKABLE void setPropertyAsync(const QString &property, const QVariant &value, int id = 0); + Q_INVOKABLE int setPropertyBlocking(const QString &property, const QVariant &value); + Q_INVOKABLE QVariant getProperty(const QString &property); Q_INVOKABLE void getPropertyAsync(const QString &property, int id = 0); - Q_INVOKABLE QVariant commandBlocking(const QVariant ¶ms); + + Q_INVOKABLE void command(const QStringList ¶ms); + Q_INVOKABLE QVariant commandBlocking(const QStringList ¶ms); Q_INVOKABLE void commandAsync(const QStringList ¶ms, int id = 0); + Q_INVOKABLE QVariant expandText(const QString &text); - Q_INVOKABLE int unobserveProperty(uint64_t id); + Q_INVOKABLE void requestUpdateFromRenderer(); friend class MpvRenderer; Q_SIGNALS: void ready(); - void observeProperty(const QString &property, mpv_format format, uint64_t id = 0); - void setProperty(const QString &property, const QVariant &value); - void command(const QStringList ¶ms); protected: MpvController *mpvController(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvabstractitem_p.h new/mpvqt-1.2.0/src/mpvabstractitem_p.h --- old/mpvqt-1.1.1/src/mpvabstractitem_p.h 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvabstractitem_p.h 2026-05-18 10:42:52.000000000 +0200 @@ -17,8 +17,8 @@ MpvAbstractItem *q_ptr; QThread *m_workerThread{nullptr}; MpvController *m_mpvController{nullptr}; - mpv_handle *m_mpv{nullptr}; - mpv_render_context *m_mpv_gl{nullptr}; + bool m_isRendererReady{false}; + std::shared_ptr<MpvResourceManager> m_mpvResourceManager; }; #endif // MPVABSTRACTITEM_P_H_INCLUDED diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvcontroller.cpp new/mpvqt-1.2.0/src/mpvcontroller.cpp --- old/mpvqt-1.1.1/src/mpvcontroller.cpp 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvcontroller.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -7,11 +7,14 @@ #include "mpvcontroller.h" #include "mpvcontroller_p.h" +#include <QLoggingCategory> #include <QStandardPaths> #include <QVariant> #include <clocale> +Q_LOGGING_CATEGORY(MpvQt_MpvController, "MpvQt.MpvController") + MpvControllerPrivate::MpvControllerPrivate(MpvController *q) : q_ptr(q) { @@ -168,6 +171,13 @@ { } +MpvController::~MpvController() +{ + if (d_ptr && d_ptr->m_mpv) { + mpv_set_wakeup_callback(d_ptr->m_mpv, nullptr, nullptr); + } +} + void MpvController::init() { d_ptr = std::make_unique<MpvControllerPrivate>(this); @@ -190,6 +200,7 @@ configPath.append(QStringLiteral("/mpvqt.conf")); setProperty(QStringLiteral("include"), configPath); setProperty(QStringLiteral("vo"), QStringLiteral("libmpv")); + d_ptr->m_mpvHandleManager = std::make_shared<MpvHandleManager>(d_ptr->m_mpv); } void MpvController::mpvEvents(void *ctx) @@ -219,6 +230,8 @@ auto prop = static_cast<mpv_event_end_file *>(event->data); if (prop->reason == MPV_END_FILE_REASON_EOF) { Q_EMIT endFile(QStringLiteral("eof")); + } else if (prop->reason == MPV_END_FILE_REASON_STOP) { + Q_EMIT endFile(QStringLiteral("stop")); } else if (prop->reason == MPV_END_FILE_REASON_ERROR) { Q_EMIT endFile(QStringLiteral("error")); } @@ -257,7 +270,7 @@ data = *reinterpret_cast<double *>(prop->data); break; case MPV_FORMAT_STRING: - data = QString::fromStdString(*reinterpret_cast<char **>(prop->data)); + data = QString::fromUtf8(*reinterpret_cast<char **>(prop->data)); break; case MPV_FORMAT_INT64: data = qlonglong(*reinterpret_cast<int64_t *>(prop->data)); @@ -275,7 +288,7 @@ case MPV_FORMAT_BYTE_ARRAY: break; } - Q_EMIT propertyChanged(QString::fromStdString(prop->name), data); + Q_EMIT propertyChanged(QString::fromUtf8(prop->name), data); break; } case MPV_EVENT_NONE: @@ -343,14 +356,14 @@ return err; } -QVariant MpvController::command(const QVariant ¶ms) +QVariant MpvController::command(const QStringList ¶ms) { mpv_node node; d_ptr->setNode(&node, params); mpv_node result; int err = mpv_command_node(d_ptr->m_mpv, &node, &result); if (err < 0) { - qDebug() << getError(err) << params; + qCDebug(MpvQt_MpvController) << getError(err) << params; return QVariant::fromValue(ErrorReturn(err)); } node_autofree f(&result); @@ -364,6 +377,11 @@ return mpv_command_node_async(d_ptr->m_mpv, id, &node); } +std::shared_ptr<MpvHandleManager> MpvController::mpvHandleManager() const +{ + return d_ptr->m_mpvHandleManager; +} + QString MpvController::getError(int error) { ErrorReturn err{error}; @@ -373,7 +391,7 @@ case MPV_ERROR_EVENT_QUEUE_FULL: return QStringLiteral("MPV_ERROR_EVENT_QUEUE_FULL"); case MPV_ERROR_NOMEM: - return QStringLiteral("MPV_ERROR_EVENT_QUEUE_FULL"); + return QStringLiteral("MPV_ERROR_NOMEM"); case MPV_ERROR_UNINITIALIZED: return QStringLiteral("MPV_ERROR_UNINITIALIZED"); case MPV_ERROR_INVALID_PARAMETER: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvcontroller.h new/mpvqt-1.2.0/src/mpvcontroller.h --- old/mpvqt-1.1.1/src/mpvcontroller.h 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvcontroller.h 2026-05-18 10:42:52.000000000 +0200 @@ -18,6 +18,20 @@ #include <memory> +struct MpvHandleManager { + mpv_handle *mpvHandle{nullptr}; + explicit MpvHandleManager(mpv_handle *h) + : mpvHandle(h) + { + } + ~MpvHandleManager() + { + if (mpvHandle) { + mpv_terminate_destroy(mpvHandle); + } + } +}; + class MpvControllerPrivate; /** @@ -62,13 +76,16 @@ class MPVQT_EXPORT MpvController : public QObject { Q_OBJECT + friend class MpvAbstractItem; + public: explicit MpvController(QObject *parent = nullptr); + ~MpvController(); /** * Return an error string from an ErrorReturn. */ - QString getError(int error); + static QString getError(int error); static void mpvEvents(void *ctx); void eventHandler(); @@ -145,7 +162,7 @@ * @param `params` command arguments, with args[0] being the command name as string * @return the property value, or an ErrorReturn with the error code */ - QVariant command(const QVariant ¶ms); + QVariant command(const QStringList ¶ms); /** * Run commands asynchronously. The result of the operation as well @@ -168,6 +185,7 @@ void videoReconfig(); private: + std::shared_ptr<MpvHandleManager> mpvHandleManager() const; std::unique_ptr<MpvControllerPrivate> d_ptr; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvcontroller_p.h new/mpvqt-1.2.0/src/mpvcontroller_p.h --- old/mpvqt-1.1.1/src/mpvcontroller_p.h 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvcontroller_p.h 2026-05-18 10:42:52.000000000 +0200 @@ -22,6 +22,7 @@ MpvController *q_ptr; mpv_handle *m_mpv{nullptr}; + std::shared_ptr<MpvHandleManager> m_mpvHandleManager; }; #endif // MPVCONTROLLER_P_H_INCLUDED diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvrenderer.cpp new/mpvqt-1.2.0/src/mpvrenderer.cpp --- old/mpvqt-1.1.1/src/mpvrenderer.cpp 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvrenderer.cpp 2026-05-18 10:42:52.000000000 +0200 @@ -5,15 +5,19 @@ */ #include "mpvrenderer.h" -#include "mpvabstractitem.h" -#include "mpvabstractitem_p.h" -#include "mpvcontroller.h" #include <QGuiApplication> +#include <QLoggingCategory> #include <QOpenGLContext> #include <QOpenGLFramebufferObject> #include <QQuickWindow> +#include "mpvabstractitem.h" +#include "mpvabstractitem_p.h" +#include "mpvcontroller.h" + +Q_STATIC_LOGGING_CATEGORY(MpvQt_MpvRenderer, "MpvQt.MpvRenderer") + static void *get_proc_address_mpv(void *ctx, const char *name) { Q_UNUSED(ctx) @@ -28,13 +32,35 @@ void on_mpv_redraw(void *ctx) { - QMetaObject::invokeMethod(static_cast<MpvAbstractItem *>(ctx), &MpvAbstractItem::update, Qt::QueuedConnection); + auto *r = static_cast<MpvRenderer *>(ctx); + r->requestUpdate(); } -MpvRenderer::MpvRenderer(MpvAbstractItem *new_obj) - : m_mpvAItem{new_obj} +MpvRenderer::MpvRenderer() { - m_mpvAItem->window()->setPersistentSceneGraph(true); +} + +MpvRenderer::~MpvRenderer() +{ + if (m_mpvResourceManager) { + m_mpvResourceManager->freeContext(); + } +} + +void MpvRenderer::synchronize(QQuickFramebufferObject *item) +{ + MpvAbstractItem *mpvAItem = static_cast<MpvAbstractItem *>(item); + m_mpvAItem = mpvAItem; + + if (!m_mpvResourceManager) { + m_mpvResourceManager = mpvAItem->d_ptr->m_mpvResourceManager; + } + + if (mpvAItem->d_ptr->m_isRendererReady != m_isFramebufferReady) { + mpvAItem->d_ptr->m_isRendererReady = m_isFramebufferReady; + + Q_EMIT mpvAItem->ready(); + } } void MpvRenderer::render() @@ -57,44 +83,60 @@ {MPV_RENDER_PARAM_INVALID, nullptr}}; // See render_gl.h on what OpenGL environment mpv expects, and // other API details. - mpv_render_context_render(m_mpvAItem->d_ptr->m_mpv_gl, params); + int result = mpv_render_context_render(m_mpvResourceManager->mpvRenderContext, params); + if (result < 0) { + qCWarning(MpvQt_MpvRenderer) << "mpv_render_context_render failed:" << MpvController::getError(result); + return; + } } QOpenGLFramebufferObject *MpvRenderer::createFramebufferObject(const QSize &size) { - // init mpv_gl: - if (!m_mpvAItem->d_ptr->m_mpv_gl) { -#if MPV_CLIENT_API_VERSION < MPV_MAKE_VERSION(2, 0) - mpv_opengl_init_params gl_init_params{get_proc_address_mpv, nullptr, nullptr}; -#else - mpv_opengl_init_params gl_init_params{get_proc_address_mpv, nullptr}; -#endif + if (m_mpvResourceManager && !m_mpvResourceManager->mpvRenderContext) { + m_mpvResourceManager->mpvRenderContext = createMpvRenderContext(); + m_isFramebufferReady = true; + } + + return QQuickFramebufferObject::Renderer::createFramebufferObject(size); +} + +mpv_render_context *MpvRenderer::createMpvRenderContext() +{ + mpv_opengl_init_params gl_init_params{get_proc_address_mpv, nullptr}; - mpv_render_param display{MPV_RENDER_PARAM_INVALID, nullptr}; + mpv_render_param display{MPV_RENDER_PARAM_INVALID, nullptr}; #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) - if (QGuiApplication::platformName() == QStringLiteral("xcb")) { - display.type = MPV_RENDER_PARAM_X11_DISPLAY; - display.data = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display(); - } - - if (QGuiApplication::platformName() == QStringLiteral("wayland")) { - display.type = MPV_RENDER_PARAM_WL_DISPLAY; - display.data = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()->display(); - } + if (QGuiApplication::platformName() == QStringLiteral("xcb")) { + display.type = MPV_RENDER_PARAM_X11_DISPLAY; + display.data = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display(); + } + + if (QGuiApplication::platformName() == QStringLiteral("wayland")) { + display.type = MPV_RENDER_PARAM_WL_DISPLAY; + display.data = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()->display(); + } #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}}; - - int result = mpv_render_context_create(&m_mpvAItem->d_ptr->m_mpv_gl, m_mpvAItem->d_ptr->m_mpv, params); - if (result < 0) { - qFatal("failed to initialize mpv GL context"); - } - mpv_render_context_set_update_callback(m_mpvAItem->d_ptr->m_mpv_gl, on_mpv_redraw, m_mpvAItem); - Q_EMIT m_mpvAItem->ready(); + 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}}; + + mpv_render_context *renderCtx = nullptr; + mpv_handle *handle = m_mpvResourceManager->mpvHandleManager->mpvHandle; + int result = mpv_render_context_create(&renderCtx, handle, params); + if (result < 0) { + qCritical() << "failed to initialize mpv GL context:" << mpv_error_string(result); + return nullptr; } - return QQuickFramebufferObject::Renderer::createFramebufferObject(size); + mpv_render_context_set_update_callback(renderCtx, on_mpv_redraw, this); + return renderCtx; +} + +void MpvRenderer::requestUpdate() +{ + if (m_mpvAItem) { + QMetaObject::invokeMethod(m_mpvAItem.data(), "requestUpdateFromRenderer", Qt::QueuedConnection); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpvqt-1.1.1/src/mpvrenderer.h new/mpvqt-1.2.0/src/mpvrenderer.h --- old/mpvqt-1.1.1/src/mpvrenderer.h 2025-04-15 16:03:38.000000000 +0200 +++ new/mpvqt-1.2.0/src/mpvrenderer.h 2026-05-18 10:42:52.000000000 +0200 @@ -9,21 +9,31 @@ #include <QtQuick/QQuickFramebufferObject> +#include <mpv/render_gl.h> + +#include "mpvabstractitem.h" + class MpvAbstractItem; class MpvRenderer : public QQuickFramebufferObject::Renderer { public: - explicit MpvRenderer(MpvAbstractItem *new_obj); - ~MpvRenderer() = default; - - MpvAbstractItem *m_mpvAItem{nullptr}; + explicit MpvRenderer(); + ~MpvRenderer(); // This function is called when a new FBO is needed. // This happens on the initial frame. QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override; void render() override; + void synchronize(QQuickFramebufferObject *item) override; + void requestUpdate(); + +private: + mpv_render_context *createMpvRenderContext(); + QPointer<MpvAbstractItem> m_mpvAItem{nullptr}; + bool m_isFramebufferReady{false}; + std::shared_ptr<MpvResourceManager> m_mpvResourceManager; }; #endif // MPVRENDERER_H
