mpv.py: improve shutdown handling, replace wait_for_playback

This commit is contained in:
jaseg 2020-07-18 00:06:04 +02:00
parent cc368710bd
commit ad68ec5927

49
mpv.py
View file

@ -53,6 +53,9 @@ else:
fs_enc = sys.getfilesystemencoding() fs_enc = sys.getfilesystemencoding()
class ShutdownError(SystemError):
pass
class MpvHandle(c_void_p): class MpvHandle(c_void_p):
pass pass
@ -633,7 +636,7 @@ def _event_generator(handle):
yield event yield event
def _event_loop(event_handle, playback_cond, event_callbacks, message_handlers, property_handlers, log_handler): def _event_loop(event_handle, event_callbacks, message_handlers, property_handlers, log_handler):
for event in _event_generator(event_handle): for event in _event_generator(event_handle):
try: try:
devent = event.as_dict(decoder=lazy_decoder) # copy data from ctypes devent = event.as_dict(decoder=lazy_decoder) # copy data from ctypes
@ -641,10 +644,6 @@ def _event_loop(event_handle, playback_cond, event_callbacks, message_handlers,
for callback in event_callbacks: for callback in event_callbacks:
callback(devent) callback(devent)
if eid in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE):
with playback_cond:
playback_cond.notify_all()
if eid == MpvEventID.PROPERTY_CHANGE: if eid == MpvEventID.PROPERTY_CHANGE:
pc = devent['event'] pc = devent['event']
name, value, _fmt = pc['name'], pc['value'], pc['format'] name, value, _fmt = pc['name'], pc['value'], pc['format']
@ -859,11 +858,11 @@ class MPV(object):
self._event_callbacks = [] self._event_callbacks = []
self._property_handlers = collections.defaultdict(lambda: []) self._property_handlers = collections.defaultdict(lambda: [])
self._quit_handlers = set()
self._message_handlers = {} self._message_handlers = {}
self._key_binding_handlers = {} self._key_binding_handlers = {}
self._playback_cond = threading.Condition()
self._event_handle = _mpv_create_client(self.handle, b'py_event_handler') self._event_handle = _mpv_create_client(self.handle, b'py_event_handler')
self._loop = partial(_event_loop, self._event_handle, self._playback_cond, self._event_callbacks, self._loop = partial(_event_loop, self._event_handle, self._event_callbacks,
self._message_handlers, self._property_handlers, log_handler) self._message_handlers, self._property_handlers, log_handler)
self._stream_protocol_cbs = {} self._stream_protocol_cbs = {}
self._stream_protocol_frontends = collections.defaultdict(lambda: {}) self._stream_protocol_frontends = collections.defaultdict(lambda: {})
@ -881,15 +880,30 @@ class MPV(object):
else: else:
self._event_thread = None self._event_thread = None
def wait_for_playback(self): self._core_shutdown = False
"""Waits until playback of the current title is paused or done.""" # This is the first callback in line, so other event callback-based mechanisms can use core_shutdown
with self._playback_cond: @self.event_callback('shutdown')
self._playback_cond.wait() def shutdown_event_callback(event):
nonlocal self
self._core_shutdown = True
@property
def core_shutdown(self):
return self._core_shutdown
def wait_until_paused(self): def wait_until_paused(self):
"""Waits until playback of the current title is paused or done.""" """Waits until playback of the current title is paused or done."""
self.wait_for_property('core-idle') self.wait_for_property('core-idle')
def wait_for_playback(self):
"""Waits until playback of the current title is paused or done.
NOTE: This function changed from an event-based implementation to a property observer-based implementation in
v0.5.0. This may cause different results in certain cases. If you find one such case, for documentation please
tell the world in an issue on the github project."""
self.wait_until_playing()
self.wait_until_paused()
def wait_until_playing(self): def wait_until_playing(self):
"""Waits until playback of the current title has started.""" """Waits until playback of the current title has started."""
self.wait_for_property('core-idle', lambda idle: not idle) self.wait_for_property('core-idle', lambda idle: not idle)
@ -899,12 +913,22 @@ class MPV(object):
properties such as ``idle_active`` indicating the player is done with regular playback and just idling around properties such as ``idle_active`` indicating the player is done with regular playback and just idling around
""" """
sema = threading.Semaphore(value=0) sema = threading.Semaphore(value=0)
def observer(name, val): def observer(name, val):
if cond(val): if cond(val):
sema.release() sema.release()
self.observe_property(name, observer) self.observe_property(name, observer)
@self.event_callback('shutdown')
def shutdown_handler(event):
sema.release()
if not level_sensitive or not cond(getattr(self, name.replace('-', '_'))): if not level_sensitive or not cond(getattr(self, name.replace('-', '_'))):
sema.acquire() sema.acquire()
if self._core_shutdown:
raise ShutdownError('libmpv core has been shutdown')
self.unobserve_property(name, observer) self.unobserve_property(name, observer)
def __del__(self): def __del__(self):
@ -1229,6 +1253,9 @@ class MPV(object):
print("It's loud!", volume) print("It's loud!", volume)
my_handler.unregister_mpv_properties() my_handler.unregister_mpv_properties()
exit_handler is a function taking no arguments that is called when the underlying mpv handle is terminated (e.g.
from calling MPV.terminate() or issuing a "quit" input command).
""" """
self._property_handlers[name].append(handler) self._property_handlers[name].append(handler)
_mpv_observe_property(self._event_handle, hash(name)&0xffffffffffffffff, name.encode('utf-8'), MpvFormat.NODE) _mpv_observe_property(self._event_handle, hash(name)&0xffffffffffffffff, name.encode('utf-8'), MpvFormat.NODE)