VideoPlayer: add playback_restart Signal for state machine adapter
Adds a Qt Signal that mirrors mpv's `playback-restart` event. The upcoming popout state machine refactor (Prompt 3) needs a clean, event-driven "seek/load completed" edge to drive its SeekingVideo → PlayingVideo and LoadingVideo → PlayingVideo transitions, replacing the current 500ms timestamp suppression window in `_seek_pending_until`. mpv's `playback-restart` fires once after each loadfile (when playback actually starts producing frames) and once after each completed seek. Empirically verified by the pre-commit-1 probe in docs/POPOUT_REFACTOR_PLAN.md: a load + 2 seeks produces exactly 3 events, with `seeking=False` at every event (the event represents the completion edge, not the in-progress state). The state machine adapter will distinguish "load done" from "seek done" by checking the state machine's current state at dispatch time: - `playback-restart` while in LoadingVideo → VideoStarted event - `playback-restart` while in SeekingVideo → SeekCompleted event Implementation: - One Signal added near the existing play_next / media_ready / video_size definitions, with a doc comment explaining what fires it and which state machine consumes it. - One event_callback registration in `_ensure_mpv` (alongside the existing observe_property calls). The callback runs on mpv's event thread; emitting a Qt Signal is thread-safe and the receiving slot runs on the GUI thread via Qt's default AutoConnection (sender and receiver in the same thread by the time the popout adapter wires the connection). - The decorator-based `@self._mpv.event_callback(...)` form is used to match the rest of the python-mpv idioms in the file. The inner function name `_emit_playback_restart` is local-scoped — mpv keeps its own reference, so there's no leak from re-creation across popout open/close cycles (each popout gets a fresh VideoPlayer with its own _ensure_mpv call). This is the only commit in the popout state machine refactor that touches `media/video_player.py`. All subsequent commits land in `popout/` (state.py, effects.py, hyprland.py, window.py) or `gui/main_window.py` interface updates. 21 lines added, 0 removed. Verification: - Phase A test suite (16 tests) still passes - Module imports cleanly with the new Signal in place - App launches without errors (smoke test) Test cases for state machine adapter (Prompt 3 popout/state.py): - VideoPlayer.playback_restart fires once on play_file completion - VideoPlayer.playback_restart fires once per _seek call
This commit is contained in:
parent
bf14466382
commit
9cba7d5583
@ -36,6 +36,17 @@ class VideoPlayer(QWidget):
|
|||||||
play_next = Signal() # emitted when video ends in "Next" mode
|
play_next = Signal() # emitted when video ends in "Next" mode
|
||||||
media_ready = Signal() # emitted when media is loaded and duration is known
|
media_ready = Signal() # emitted when media is loaded and duration is known
|
||||||
video_size = Signal(int, int) # (width, height) emitted when video dimensions are known
|
video_size = Signal(int, int) # (width, height) emitted when video dimensions are known
|
||||||
|
# Emitted whenever mpv fires its `playback-restart` event. This event
|
||||||
|
# arrives once after each loadfile (when playback actually starts
|
||||||
|
# producing frames) and once after each completed seek. The popout's
|
||||||
|
# state machine adapter listens to this signal and dispatches either
|
||||||
|
# VideoStarted or SeekCompleted depending on which state it's in
|
||||||
|
# (LoadingVideo vs SeekingVideo). The pre-state-machine code did not
|
||||||
|
# need this signal because it used a 500ms timestamp window to fake
|
||||||
|
# a seek-done edge; the state machine refactor replaces that window
|
||||||
|
# with this real event. Probe results in docs/POPOUT_REFACTOR_PLAN.md
|
||||||
|
# confirm exactly one event per load and one per seek.
|
||||||
|
playback_restart = Signal()
|
||||||
|
|
||||||
# QSS-controllable letterbox / pillarbox color. mpv paints the area
|
# QSS-controllable letterbox / pillarbox color. mpv paints the area
|
||||||
# around the video frame in this color instead of the default black,
|
# around the video frame in this color instead of the default black,
|
||||||
@ -246,6 +257,16 @@ class VideoPlayer(QWidget):
|
|||||||
self._mpv.observe_property('duration', self._on_duration_change)
|
self._mpv.observe_property('duration', self._on_duration_change)
|
||||||
self._mpv.observe_property('eof-reached', self._on_eof_reached)
|
self._mpv.observe_property('eof-reached', self._on_eof_reached)
|
||||||
self._mpv.observe_property('video-params', self._on_video_params)
|
self._mpv.observe_property('video-params', self._on_video_params)
|
||||||
|
# Forward mpv's `playback-restart` event to the Qt-side signal so
|
||||||
|
# the popout's state machine adapter can dispatch VideoStarted /
|
||||||
|
# SeekCompleted events on the GUI thread. mpv's event_callback
|
||||||
|
# decorator runs on mpv's event thread; emitting a Qt Signal is
|
||||||
|
# thread-safe and the receiving slot runs on the connection's
|
||||||
|
# target thread (typically the GUI main loop via the default
|
||||||
|
# AutoConnection from the same-thread receiver).
|
||||||
|
@self._mpv.event_callback('playback-restart')
|
||||||
|
def _emit_playback_restart(_event):
|
||||||
|
self.playback_restart.emit()
|
||||||
self._pending_video_size: tuple[int, int] | None = None
|
self._pending_video_size: tuple[int, int] | None = None
|
||||||
# Push any QSS-set letterbox color into mpv now that the instance
|
# Push any QSS-set letterbox color into mpv now that the instance
|
||||||
# exists. The qproperty-letterboxColor setter is a no-op if mpv
|
# exists. The qproperty-letterboxColor setter is a no-op if mpv
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user