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 []
|
||||
|
||||
def _on_seek_requested(self, event: SeekRequested) -> list[Effect]:
|
||||
# Real implementation: PlayingVideo → SeekingVideo, sets
|
||||
# seek_target_ms, emits SeekVideoTo. Lands in commit 6.
|
||||
"""**Slider pin replaces 96a0a9d's 500ms _seek_pending_until.**
|
||||
|
||||
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 []
|
||||
|
||||
def _on_seek_completed(self, event: SeekCompleted) -> list[Effect]:
|
||||
# Real implementation: SeekingVideo → PlayingVideo. Lands in
|
||||
# commit 6.
|
||||
"""SeekingVideo → PlayingVideo.
|
||||
|
||||
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 []
|
||||
|
||||
def _on_mute_toggle_requested(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user