jaseg / python-mpv

Python interface to the awesome mpv media player
https://git.jaseg.de/python-mpv.git
Other
550 stars 68 forks source link

QOpenGLWidget is displayed in its own window since ibmpv.so.2.3.0 #277

Closed kanehekili closed 2 months ago

kanehekili commented 5 months ago

Using your mpv.py for my project I'm embedding MPV with the QOpenGLWidget. With the latest libmpv.so a new window is created on X11. Not using OPENGL would render the application useless for the Wayland compositor. Is this a glitch in the underlying lib?

I can confirm that libmpv.so.2.2.0 works and libmpv.so.2.3.0 doesn't.

kanehekili commented 5 months ago

Asking upstream, I got an answer about the "glue code" (aka python-mpv) - which I didn't quite understand. To make things easier, I've created a gist-could you have a look at it?

jaseg commented 2 months ago

@kanehekili I think you just called MPV.play too early, before the MPVRenderContext was registered, which caused libmpv to fall back to the standard opengl vo. I've modified the code from your gist, and it works on my machine. It plays the test file from the tests dir of this repo when you press the play button:

#!/usr/bin/env python
'''
Created on 3 May 2024
@author: matze
(modified by jaseg)
'''
from PyQt6 import QtWidgets

"""
based on:
https://github.com/mpv-player/mpv-examples/blob/master/libmpv/qt_opengl/mpvwidget.cpp
https://gitlab.com/robozman/python-mpv-qml-example/-/blob/master/main.py?ref_type=heads
"""

from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QOpenGLContext, QPainter, QBrush, QColor, QCloseEvent
from PyQt6.QtCore import QByteArray, pyqtSignal, pyqtSlot, Qt
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from mpv import MPV, MpvGlGetProcAddressFn, MpvRenderContext
import ctypes,sys

def get_process_address(_, name):
    glctx =  QOpenGLContext.currentContext()
    address = int(glctx.getProcAddress(QByteArray(name)))
    #return ctypes.cast(address, ctypes.c_void_p).value
    return address

class Player(QOpenGLWidget):
    onUpdate = pyqtSignal()
    initialized = pyqtSignal()

    def __init__(self, parent) -> None:
        super().__init__(parent)
        self.mpv = MPV(vo='libmpv')
        self.ctx = None
        self._proc_addr_wrapper = MpvGlGetProcAddressFn(get_process_address)
        self.onUpdate.connect(self.do_update)
        self.c = 0

        self.setUpdateBehavior(QOpenGLWidget.UpdateBehavior.PartialUpdate)

    def initializeGL(self) -> None:
        self.ctx = MpvRenderContext(
            self.mpv, 'opengl',
            opengl_init_params={
                'get_proc_address': self._proc_addr_wrapper
            },    
        )

        if self.ctx:
            self.ctx.update_cb = self.on_update
            self.initialized.emit()

    def paintGL(self) -> None:
        if self.c > 100:
            self.c = 0
        else:
            self.c += 1
        rect = self.rect()
        if self.ctx:
            fbo = self.defaultFramebufferObject()
            self.ctx.render(flip_y=True, opengl_fbo={'w': rect.width(), 'h': rect.height(), 'fbo': fbo})

    def do_update(self):
        self.update()

    @pyqtSlot()
    def on_update(self):
        self.onUpdate.emit()

    def play(self, url):
        self.mpv.play(url)

    def closeEvent(self, event: QCloseEvent) -> None:
        """free mpv_context and terminate player brofre closing the widget"""
        self.ctx.free()
        self.mpv.terminate()
        event.accept()

class MainFrame(QtWidgets.QMainWindow):

    def __init__(self, qapp,aPath=None):
        self._isStarted=False
        self.__qapp=qapp
        super(MainFrame, self).__init__()
        self.initUI()
        self.centerWindow()
        self.show()

    def initUI(self):
        self.player = Player(self)

        self.uiLabel= QtWidgets.QLabel(self)
        self.uiLabel.setText("Player demo")
        self.uiPlayButton = QtWidgets.QPushButton(" Play")

        box = self._makeLayout()
        wid = QtWidgets.QWidget(self)
        self.setCentralWidget(wid)    
        wid.setLayout(box)
        self.resize(500, 600) 
        self.uiPlayButton.clicked.connect(lambda: self.player.mpv.loadfile('https://raw.githubusercontent.com/jaseg/python-mpv/main/tests/test.webm'))

    def _makeLayout(self):
        mainBox = QtWidgets.QVBoxLayout()  # for all
        btn1Box = QtWidgets.QHBoxLayout()  # test widgets
        btn1Box.setSpacing(20)
        btn1Box.addWidget(self.uiLabel)
        btn1Box.addWidget(self.uiPlayButton)

        mainBox.addWidget(self.player)
        mainBox.addLayout(btn1Box)
        return mainBox

    def centerWindow(self):
        frameGm = self.frameGeometry()
        centerPoint = self.screen().availableGeometry().center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())    

if __name__ == '__main__':

    app = QApplication(sys.argv)
    import locale
    locale.setlocale(locale.LC_NUMERIC, "C")
    WIN = MainFrame(app) 
    app.exec()
kanehekili commented 2 months ago

Well, thanks - but I need to have the first frame displayed on startup (used by videcut on GitHub). The crucial change was to set "vo:libmpv", which wasn't necessary before... Is there some kind of event that could notify me when the MPVRenderContext is initialized?