Improve event handling, add message handling, add key binding foo
This commit is contained in:
parent
de7b671103
commit
ab8b8b5477
|
@ -30,7 +30,7 @@ import mpv
|
|||
def my_log(loglevel, component, message):
|
||||
print('[{}] {}: {}'.format(loglevel, component, message))
|
||||
|
||||
player = mpv.MPV(log_handler=my_log, ytdl=True)
|
||||
player = mpv.MPV(log_handler=my_log, ytdl=True, input_default_bindings=True, input_vo_keyboard=True)
|
||||
|
||||
# Property access, these can be changed at runtime
|
||||
player.observe_property('time-pos', lambda _property, pos: print('Now playing at {:.2f}s'.format(pos)))
|
||||
|
@ -39,6 +39,11 @@ player.loop = 'inf'
|
|||
# Option access, in general these require the core to reinitialize
|
||||
player['vo'] = 'opengl'
|
||||
|
||||
def my_q_binding(state, key):
|
||||
if state[0] == 'd':
|
||||
print('THERE IS NO ESCAPE')
|
||||
player.register_key_binding('q', my_q_binding)
|
||||
|
||||
player.play('https://youtu.be/DLzxrzFCyOs')
|
||||
player.wait_for_playback()
|
||||
|
||||
|
|
11
mpv-test.py
11
mpv-test.py
|
@ -175,17 +175,20 @@ class TestLifecycle(unittest.TestCase):
|
|||
def test_event_callback(self):
|
||||
handler = mock.Mock()
|
||||
m = mpv.MPV('no-video')
|
||||
m.event_callbacks.append(handler)
|
||||
m.register_event_callback(handler)
|
||||
m.play(TESTVID)
|
||||
m.wait_for_playback()
|
||||
del m
|
||||
|
||||
m.unregister_event_callback(handler)
|
||||
handler.assert_has_calls([
|
||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 6, 'event': None}),
|
||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 9, 'event': None}),
|
||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 7, 'event': {'reason': 4}}),
|
||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 11, 'event': None}),
|
||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 1, 'event': None})
|
||||
], any_order=True)
|
||||
handler.reset_mock()
|
||||
|
||||
del m
|
||||
handler.assert_not_called()
|
||||
|
||||
def test_log_handler(self):
|
||||
handler = mock.Mock()
|
||||
|
|
67
mpv.py
67
mpv.py
|
@ -6,6 +6,7 @@ import os
|
|||
import sys
|
||||
from warnings import warn
|
||||
from functools import partial
|
||||
import re
|
||||
|
||||
# vim: ts=4 sw=4 et
|
||||
|
||||
|
@ -222,7 +223,7 @@ class MpvEventClientMessage(Structure):
|
|||
('args', POINTER(c_char_p))]
|
||||
|
||||
def as_dict(self):
|
||||
return { 'args': [ self.args[i].value for i in range(self.num_args.value) ] }
|
||||
return { 'args': [ self.args[i].decode('utf-8') for i in range(self.num_args) ] }
|
||||
|
||||
WakeupCallback = CFUNCTYPE(None, c_void_p)
|
||||
|
||||
|
@ -333,7 +334,7 @@ def load_lua():
|
|||
CDLL('liblua.so', mode=RTLD_GLOBAL)
|
||||
|
||||
|
||||
def _event_loop(event_handle, playback_cond, event_callbacks, property_handlers, log_handler):
|
||||
def _event_loop(event_handle, playback_cond, event_callbacks, message_handlers, property_handlers, log_handler):
|
||||
for event in _event_generator(event_handle):
|
||||
try:
|
||||
devent = event.as_dict() # copy data from ctypes
|
||||
|
@ -353,12 +354,19 @@ def _event_loop(event_handle, playback_cond, event_callbacks, property_handlers,
|
|||
if eid == MpvEventID.LOG_MESSAGE and log_handler is not None:
|
||||
ev = devent['event']
|
||||
log_handler(ev['level'], ev['prefix'], ev['text'])
|
||||
if eid == MpvEventID.CLIENT_MESSAGE:
|
||||
# {'event': {'args': ['key-binding', 'foo', 'u-', 'g']}, 'reply_userdata': 0, 'error': 0, 'event_id': 16}
|
||||
target, *args = devent['event']['args']
|
||||
if target in message_handlers:
|
||||
message_handlers[target](*args)
|
||||
for callback in event_callbacks:
|
||||
callback(devent)
|
||||
if eid == MpvEventID.SHUTDOWN:
|
||||
_mpv_detach_destroy(event_handle)
|
||||
return
|
||||
except:
|
||||
except Exception as e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
pass # It seems that when this thread runs into an exception, the MPV core is not able to terminate properly
|
||||
# anymore. FIXME
|
||||
|
||||
|
@ -380,12 +388,14 @@ class MPV(object):
|
|||
_mpv_set_option_string(self.handle, k.replace('_', '-').encode('utf-8'), istr(v).encode('utf-8'))
|
||||
_mpv_initialize(self.handle)
|
||||
|
||||
self.event_callbacks = []
|
||||
self._event_callbacks = []
|
||||
self._property_handlers = {}
|
||||
self._message_handlers = {}
|
||||
self._key_binding_handlers = {}
|
||||
self._playback_cond = threading.Condition()
|
||||
self._event_handle = _mpv_create_client(self.handle, b'mpv-python-event-handler-thread')
|
||||
loop = partial(_event_loop,
|
||||
self._event_handle, self._playback_cond, self.event_callbacks, self._property_handlers, log_handler)
|
||||
self._event_handle = _mpv_create_client(self.handle, b'py_event_handler')
|
||||
loop = partial(_event_loop, self._event_handle, self._playback_cond, self._event_callbacks,
|
||||
self._message_handlers, self._property_handlers, log_handler)
|
||||
self._event_thread = threading.Thread(target=loop, name='MPVEventHandlerThread')
|
||||
self._event_thread.setDaemon(True)
|
||||
self._event_thread.start()
|
||||
|
@ -527,6 +537,49 @@ class MPV(object):
|
|||
if handlerid in self._property_handlers:
|
||||
del self._property_handlers[handlerid]
|
||||
|
||||
def register_message_handler(self, target, handler):
|
||||
self._message_handlers[target] = handler
|
||||
|
||||
def unregister_message_handler(self, target):
|
||||
del self._message_handlers[target]
|
||||
|
||||
def register_event_callback(self, callback):
|
||||
self._event_callbacks.append(callback)
|
||||
|
||||
def unregister_event_callback(self, callback):
|
||||
self._event_callbacks.remove(callback)
|
||||
|
||||
@staticmethod
|
||||
def _binding_name(callback):
|
||||
return 'py_kb_{:016x}'.format(hash(callback)&0xffffffffffffffff)
|
||||
|
||||
def register_key_binding(self, keydef, callback):
|
||||
""" BIG FAT WARNING: mpv's key binding mechanism is pretty powerful. This means, you essentially get arbitrary
|
||||
code exectution through key bindings. This interface makes some limited effort to sanitize the keydef given in
|
||||
the first parameter, but YOU SHOULD NOT RELY ON THIS IN FOR SECURITY. If your input comes from config files,
|
||||
this is completely fine--but, if you are about to pass untrusted input into this parameter, better double-check
|
||||
whether this is secure in your case. """
|
||||
if not re.match(r'(Shift+)?(Ctrl+)?(Alt+)?(Meta+)?\w+', keydef):
|
||||
raise ValueError('Invalid keydef. Expected format: [Shift+][Ctrl+][Alt+][Meta+]<key>\n'
|
||||
'<key> is either the literal character the key produces (ASCII or Unicode character), or a '
|
||||
'symbolic name (as printed by --input-keylist')
|
||||
binding_name = MPV._binding_name(callback)
|
||||
self._key_binding_handlers[binding_name] = callback
|
||||
print('Registering', binding_name)
|
||||
self.command('define-section',
|
||||
binding_name, '{} script-binding py_event_handler/{}'.format(keydef, binding_name), 'force')
|
||||
self.command('enable-section', binding_name)
|
||||
self.register_message_handler('key-binding', self._handle_key_binding_message)
|
||||
|
||||
def _handle_key_binding_message(self, binding_name, key_state, key_name):
|
||||
self._key_binding_handlers[binding_name](key_state, key_name)
|
||||
|
||||
def unregister_key_binding(self, callback):
|
||||
binding_name = MPV._binding_name(callback)
|
||||
self.command('disable-section', binding_name)
|
||||
self.command('define-section', binding_name, '')
|
||||
del self._key_binding_handlers[binding_name]
|
||||
|
||||
# Convenience functions
|
||||
def play(self, filename):
|
||||
self.loadfile(filename)
|
||||
|
|
Loading…
Reference in New Issue