diff --git a/mpv.py b/mpv.py index f9bf561..ded3c8e 100644 --- a/mpv.py +++ b/mpv.py @@ -685,11 +685,59 @@ def _make_node_str_map(d): def _event_generator(handle): - while True: - event = _mpv_wait_event(handle, -1).contents - if event.event_id.value == MpvEventID.NONE: - raise StopIteration() - yield event + """Yield events from the mpv event queue. + + Uses a pipe-based wakeup mechanism to avoid deadlocks on macOS where + ``mpv_wait_event`` with an infinite timeout can block forever when the + Cocoa main-loop is blocked (see issue #61). + """ + import select as _select + + # Create a self-pipe for wakeup signaling. + _wakeup_fd_r, _wakeup_fd_w = os.pipe() + + # Keep a reference to the callback so the GC doesn't reclaim it while + # libmpv still holds a pointer to it. + @WakeupCallback + def _wakeup_cb(_userdata): + try: + os.write(_wakeup_fd_w, b'\x00') + except OSError: + pass + + _mpv_set_wakeup_callback(handle, _wakeup_cb, None) + + try: + while True: + # Wait for either a wakeup byte or a 100 ms timeout. + try: + _ready, _, _ = _select.select([_wakeup_fd_r], [], [], 0.1) + except (OSError, ValueError): + break + + if _ready: + # Drain the pipe. + try: + os.read(_wakeup_fd_r, 4096) + except OSError: + break + + # Drain all pending mpv events without blocking. + while True: + event = _mpv_wait_event(handle, 0).contents + if event.event_id.value == MpvEventID.NONE: + break + yield event + finally: + # Clean up: close the pipe and unregister the wakeup callback. + # The handle may already be destroyed if we're cleaning up after a + # SHUTDOWN event, so guard the unregister call. + os.close(_wakeup_fd_r) + os.close(_wakeup_fd_w) + try: + _mpv_set_wakeup_callback(handle, WakeupCallback(), None) + except Exception: + pass def _create_null_term_cmd_arg_array(name, args):