popout/state: implement SeekingVideo + slider pin
PlayingVideo + SeekRequested → SeekingVideo, stash target_ms, emit SeekVideoTo. SeekingVideo + SeekRequested replaces the target (user clicked again, latest seek wins). SeekingVideo + SeekCompleted → PlayingVideo. The slider pin behavior is the read-path query `compute_slider_display_ms(mpv_pos_ms)` already implemented at the skeleton stage: while in SeekingVideo, returns `seek_target_ms`; otherwise returns `mpv_pos_ms`. The Qt-side adapter's poll timer asks the state machine for the slider display value on every tick and writes whatever it gets back to the slider widget. **This replaces 96a0a9d's 500ms _seek_pending_until timestamp window at the popout layer.** The state machine has no concept of wall-clock time. The SeekingVideo state lasts exactly until mpv signals the seek is done, via the playback_restart Signal added in commit 1. The adapter distinguishes load-restart from seek-restart by checking the state machine's current state (LoadingVideo → VideoStarted; SeekingVideo → SeekCompleted). The pre-commit-1 probe verified that mpv emits playback-restart exactly once per load and exactly once per seek (3 events for 1 load + 2 seeks), so the dispatch routing is unambiguous. VideoPlayer's internal _seek_pending_until field stays in place as defense in depth — the state machine refactor's prompt explicitly forbids touching media/video_player.py beyond the playback_restart Signal addition. The popout layer no longer depends on it. Tests passing after this commit (62 total → 46 pass, 16 fail): - test_playing_video_seek_requested_transitions_and_pins - test_seeking_video_completed_returns_to_playing - test_seeking_video_seek_requested_replaces_target - test_invariant_seek_pin_uses_compute_slider_display_ms (RACE FIX!) Phase A (16 tests) still green. Tests still failing (16, scheduled for commits 7-11): - F11 round-trip (commit 7) - Persistent viewport / drift events (commit 8) - mute/volume/loop persistence events (commit 9) - DisplayingImage content arrived branch (commit 10) - Closing transitions (commit 10) Test cases for commit 7 (Fullscreen flag + F11 round-trip): - dispatch FullscreenToggled in any media state, assert flag flipped - F11 enter snapshots viewport into pre_fullscreen_viewport - F11 exit restores viewport from pre_fullscreen_viewport
This commit is contained in:
parent
a9ce01e6c1
commit
664d4e9cda
@ -757,13 +757,51 @@ class StateMachine:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def _on_seek_requested(self, event: SeekRequested) -> list[Effect]:
|
def _on_seek_requested(self, event: SeekRequested) -> list[Effect]:
|
||||||
# Real implementation: PlayingVideo → SeekingVideo, sets
|
"""**Slider pin replaces 96a0a9d's 500ms _seek_pending_until.**
|
||||||
# seek_target_ms, emits SeekVideoTo. Lands in commit 6.
|
|
||||||
|
Two valid source states:
|
||||||
|
|
||||||
|
- PlayingVideo: enter SeekingVideo, stash target_ms, emit
|
||||||
|
SeekVideoTo. The slider pin behavior is read-path:
|
||||||
|
`compute_slider_display_ms` returns `seek_target_ms`
|
||||||
|
while in SeekingVideo regardless of mpv's lagging or
|
||||||
|
keyframe-rounded `time_pos`.
|
||||||
|
|
||||||
|
- SeekingVideo: a second seek before the first one completed.
|
||||||
|
Replace the target — the user clicked again, so the new
|
||||||
|
target is what they want pinned. Emit a fresh SeekVideoTo.
|
||||||
|
Stay in SeekingVideo. mpv handles back-to-back seeks fine;
|
||||||
|
its own playback-restart event for the latest seek is what
|
||||||
|
will eventually fire SeekCompleted.
|
||||||
|
|
||||||
|
SeekRequested in any other state (AwaitingContent /
|
||||||
|
DisplayingImage / LoadingVideo / Closing): drop. There's no
|
||||||
|
video to seek into.
|
||||||
|
|
||||||
|
No timestamp window. The state machine subsumes the 500ms
|
||||||
|
suppression by holding SeekingVideo until SeekCompleted
|
||||||
|
arrives (which is mpv's `playback-restart` after the seek,
|
||||||
|
wired in the adapter).
|
||||||
|
"""
|
||||||
|
if self.state in (State.PLAYING_VIDEO, State.SEEKING_VIDEO):
|
||||||
|
self.state = State.SEEKING_VIDEO
|
||||||
|
self.seek_target_ms = event.target_ms
|
||||||
|
return [SeekVideoTo(target_ms=event.target_ms)]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _on_seek_completed(self, event: SeekCompleted) -> list[Effect]:
|
def _on_seek_completed(self, event: SeekCompleted) -> list[Effect]:
|
||||||
# Real implementation: SeekingVideo → PlayingVideo. Lands in
|
"""SeekingVideo → PlayingVideo.
|
||||||
# commit 6.
|
|
||||||
|
Triggered by the adapter receiving mpv's `playback-restart`
|
||||||
|
event AND finding the state machine in SeekingVideo (the
|
||||||
|
adapter distinguishes load-restart from seek-restart by
|
||||||
|
checking current state — see VideoStarted handler).
|
||||||
|
|
||||||
|
After this transition, `compute_slider_display_ms` returns
|
||||||
|
the actual mpv `time_pos` again instead of the pinned target.
|
||||||
|
"""
|
||||||
|
if self.state == State.SEEKING_VIDEO:
|
||||||
|
self.state = State.PLAYING_VIDEO
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _on_mute_toggle_requested(
|
def _on_mute_toggle_requested(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user