python-mpv icon indicating copy to clipboard operation
python-mpv copied to clipboard

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

Open kanehekili opened this issue 1 year ago • 1 comments
trafficstars

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 avatar May 01 '24 23:05 kanehekili

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?

kanehekili avatar May 04 '24 16:05 kanehekili

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

jaseg avatar Jul 19 '24 11:07 jaseg

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?

kanehekili avatar Jul 26 '24 22:07 kanehekili