bigscreen-tube/bigscreen_tube/mpv_player.py

183 lines
5.7 KiB
Python

# from https://gitlab.com/robozman/python-mpv-qml-example/-/tree/master/
# (c) Robozman 2020 - Apache 2.0
from PySide2.QtCore import QUrl, QSize, Signal, Slot, Property
from PySide2.QtGui import QOpenGLFramebufferObject
import PySide2.QtWidgets as QtWidgets
from PySide2.QtQuick import QQuickFramebufferObject, QQuickView
from PySide2.QtQml import qmlRegisterType
import ctypes
from OpenGL import GL, GLX
from mpv import MPV, MpvRenderContext, MpvGlGetProcAddressFn
def get_process_address(_, name):
"""This function allows looking up OpenGL functions."""
address = GLX.glXGetProcAddress(name.decode("utf-8"))
return ctypes.cast(address, ctypes.c_void_p).value
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."""
def __init__(self, parent=None):
print("Creating MpvObject")
super(MpvObject, self).__init__(parent)
self.mpv = MPV(ytdl=True, log_handler=print, ytdl_format="bestvideo[height<=?1080][fps<=?30][vcodec!=?vp9]+bestaudio/best")
self.mpv.pause = True
self.mpv_gl = None
self._renderer = None
self._proc_addr_wrapper = MpvGlGetProcAddressFn(get_process_address)
self.onUpdate.connect(self.doUpdate)
self.registered = False
self.urlToLoad = None
self._time_pos = 0
self._duration = 0
self._seeking = False
def initPlayer(self):
print('init player')
self.mpv.observe_property('time-pos', self.observe_time_pos_handler)
self.mpv.observe_property('duration', self.observe_duration_handler)
self.mpv.observe_property('seeking', self.observe_seeking_handler)
self.load(self.urlToLoad)
@Signal
def onUpdate():
pass
def on_update(self):
"""Function for mpv to call to trigger a framebuffer update"""
self.onUpdate.emit()
def observe_time_pos_handler(self, prop_name, value):
self._time_pos = value
self.onTimePosChanged.emit()
def observe_duration_handler(self, prop_name, value):
self._duration = value
self.onDurationChanged.emit()
def observe_seeking_handler(self, prop_name, value):
self._seeking = value
self.onSeekingChanged.emit()
@Slot(result=None)
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")
self._renderer = MpvRenderer(self)
return self._renderer
@Slot(str, result=None)
def load(self, url):
"""Temporary adapter fuction that allowing playing media from QML"""
if not self.registered:
self.urlToLoad = url
elif url is not None:
self.mpv.loadfile(url)
self.pauseChanged.emit()
@Slot(int, result=None)
def seek(self, position):
if self.registered:
self.mpv.seek(position, reference='absolute')
@Slot(result=None)
def togglePause(self):
self.mpv.pause = not self.mpv.pause
self.pauseChanged.emit()
def _paused(self):
return self.mpv.pause
@Signal
def pauseChanged(self):
pass
paused = Property(bool, _paused, notify=pauseChanged)
def _time_pos_get(self):
return self._time_pos
@Signal
def onTimePosChanged(self):
pass
timePos = Property(int, _time_pos_get, notify=onTimePosChanged)
def _duration_get(self):
return self._duration
@Signal
def onDurationChanged(self):
pass
duration = Property(int, _duration_get, notify=onDurationChanged)
def _seeking_get(self):
return self._seeking
@Signal
def onSeekingChanged(self):
pass
seeking = Property(bool, _seeking_get, notify=onSeekingChanged)
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
self._fbo = 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
self.obj.registered = True
self.obj.initPlayer()
self._fbo = QQuickFramebufferObject.Renderer.createFramebufferObject(self, size)
return self._fbo
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 = GL.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING)
self.ctx.render(flip_y=False, opengl_fbo={'w': width, 'h': height, 'fbo': fbo})