jaseg / python-mpv

Python interface to the awesome mpv media player
https://git.jaseg.de/python-mpv.git
Other
555 stars 69 forks source link

Question: How to waif for key event? #155

Open dennyh opened 3 years ago

dennyh commented 3 years ago

How would I get my script to wait for one of two key presses in mpv? I want to wait for the user to press one of two keys and then do things based on what key was pressed.

Currently I have hacked together a an ugly way of waiting for keypresses using wait_for_event and on_key_press sending some text to the osd.

# send client-message
@player.on_key_press("ENTER")
    def my_ENTER_binding():
        player.show_text("text")
# waiting for key press
player.wait_for_event("CLIENT_MESSAGE", cond=lambda evt : evt["event"]["args"][2:4] == ["d-", "ENTER"])

You might be able to tell I am fairly new to python.

jaseg commented 3 years ago

The "pythonic" way to do this through python-mpv's interfaces I think would be to register two key press handlers that notify your interaction thread through a condition object from python's threading module. Here's some (untested) code as a starting point:

import threading
cond = threading.Condition()

key = None

@player.on_key_press("a")
def on_a():
    nonlocal key
    key = 'a'
    with cond:
        cond.notify()

@player.on_key_press("b")
def on_b():
    nonlocal key
    key = 'b'
    with cond:
        cond.notify()

with cond:
    cond.wait(timeout=None)
on_a.unregister_mpv_key_bindings()
on_b.unregister_mpv_key_bindings()

print(f'User pressed {key}')

Execution will stop at cond.wait until either callback fires. The callbacks are called on python-mpv's internal event handler thread, so they will work even while the thread that created them is waiting in cond.wait.

dennyh commented 3 years ago

Thanks for the help! This was what I was looking for.

When trying this out I did notice something I am unsure if it is intended behavior.

import threading
import mpv

def wait_a_or_b(player: mpv.MPV) -> str:

    cond = threading.Condition()
    key = None

    @player.on_key_press("a")
    def on_a():
        nonlocal key
        key = 'a'
        with cond:
            cond.notify()

    @player.on_key_press("b")
    def on_b():
        nonlocal key
        key = 'b'
        with cond:
            cond.notify()

    with cond:
        cond.wait(timeout=None)
    on_a.unregister_mpv_key_bindings()
    on_b.unregister_mpv_key_bindings()

    return key

player = mpv.MPV(input_default_bindings=False, input_vo_keyboard=True, osc=True, volume=10)

# Register a keybinding
@player.on_key_press("r")
def my_r_binding():
    pass

player.loop = True

player.play("ThreeEditions.mkv")
player.wait_until_playing()

keyPressed = wait_a_or_b(player) # <----
#print(f'User pressed {keyPressed}')

my_r_binding.unregister_mpv_key_bindings() # <----

input("Press enter to continue...")

This code runs fine and does what is expected. However, if we comment out my_r_binding.unregister_mpv_key_bindings() and hits the last row input("Press enter to continue...") it throws exeptions. If we also comment out the call to wait_a_or_b then no exceptions are thrown. This also happens if input is switched for time.sleep.

exception:

Exception inside python-mpv event loop:
Traceback (most recent call last):
  File "C:\Python38\lib\site-packages\mpv.py", line 881, in _loop
    self._message_handlers[target](*args)
  File "C:\Python38\lib\site-packages\mpv.py", line 1550, in _handle_key_binding_message
    self._key_binding_handlers[binding_name](key_state, key_name, key_char)
KeyError: 'py_kb_d09505b79a7d8a10'
jaseg commented 3 years ago

Oh, that looks like a bug. I'll try to reproduce this and commit a fix.

dennyh commented 3 years ago

Let me know if you need any more input from me. On another note: The docstring of observe_property in mpv.py mentions the unregister_mpv_properties attribute. I think this is meant to be unobserve_mpv_properties as it is defined in property_observer.