from PyQt5.QtCore import QUrl, QSize, pyqtSignal, pyqtSlot from PyQt5.QtGui import QOpenGLFramebufferObject from PyQt5.QtOpenGL import QGLContext from PyQt5.QtQuick import QQuickFramebufferObject from mpv import MPV, MpvRenderContext, OpenGlCbGetProcAddrFn def get_process_address(_, name): glctx = QGLContext.currentContext() if glctx is None: return 0 return int(glctx.getProcAddress(name.decode('utf-8'))) class MpvObject(QQuickFramebufferObject): """MpvObject: This is a QML widget that can be used to embed the output of a mpv instance. It extends the QQuickFramebufferObject class to implement this functionality.""" # This signal allows triggers the update function to run on the correct thread onUpdate = pyqtSignal() def __init__(self, parent=None): print("Creating MpvObject") super(MpvObject, self).__init__(parent) self.mpv = MPV(ytdl=True) self.mpv_gl = None self._proc_addr_wrapper = OpenGlCbGetProcAddrFn(get_process_address) self.onUpdate.connect(self.doUpdate) def on_update(self): """Function for mpv to call to trigger a framebuffer update""" self.onUpdate.emit() @pyqtSlot() def doUpdate(self): """Slot for receiving the update event on the correct thread""" self.update() def createRenderer(self) -> 'QQuickFramebufferObject.Renderer': """Overrides the default createRenderer function to create a MpvRenderer instance""" print("Calling overridden createRenderer") return MpvRenderer(self) @pyqtSlot(str) def play(self, url): """Temporary adapter fuction that allowing playing media from QML""" self.mpv.play(url) class MpvRenderer(QQuickFramebufferObject.Renderer): """MpvRenderer: This class implements the QQuickFramebufferObject's Renderer subsystem. It augments the base renderer with an instance of mpv's render API.""" def __init__(self, parent=None): print("Creating MpvRenderer") super(MpvRenderer, self).__init__() self.obj = parent self.ctx = None def createFramebufferObject(self, size: QSize) -> QOpenGLFramebufferObject: """Overrides the base createFramebufferObject function, augmenting it to create an MpvRenderContext using opengl""" if self.obj.mpv_gl is None: print("Creating mpv gl") self.ctx = MpvRenderContext(self.obj.mpv, 'opengl', opengl_init_params={ 'get_proc_address': self.obj._proc_addr_wrapper }) self.ctx.update_cb = self.obj.on_update return QQuickFramebufferObject.Renderer.createFramebufferObject(self, size) def render(self): """Overrides the base render function, calling mpv's render functions instead""" if self.ctx: factor = self.obj.scale() rect = self.obj.size() # width and height are floats width = int(rect.width() * factor) height = int(rect.height() * factor) fbo = int(self.framebufferObject().handle()) self.ctx.render(flip_y=False, opengl_fbo={'w': width, 'h': height, 'fbo': fbo})