Three event handlers, all updating state fields and emitting the
corresponding Apply effect:
MuteToggleRequested:
Flip state.mute unconditionally — independent of which media state
we're in, independent of whether mpv exists. Emit ApplyMute. The
persistence-on-load mechanism in _on_video_started already replays
state.mute into the freshly-loaded video, so toggling mute before
any video is loaded survives the load cycle.
VolumeSet:
Set state.volume (clamped 0-100), emit ApplyVolume. Same
persistence-on-load behavior.
LoopModeSet:
Set state.loop_mode, emit ApplyLoopMode. Also affects what
happens at the next EOF (PlayingVideo + VideoEofReached branches
on state.loop_mode), so changing it during playback takes effect
on the next eof without any other state mutation.
This commit makes the 0a68182 pending mute fix structural at the
popout layer. The state machine owns mute / volume / loop_mode as
the source of truth. The current VideoPlayer._pending_mute field
stays as defense in depth — the state machine refactor's prompt
forbids touching media/video_player.py beyond the playback_restart
Signal addition. The popout layer no longer depends on the lazy
replay because the state machine emits ApplyMute on every
PlayingVideo entry.
All four persistent fields (mute, volume, loop_mode, viewport)
are now state machine fields with single-writer ownership through
dispatch().
Tests passing after this commit (62 total → 54 pass, 8 fail):
- test_state_field_mute_persists_across_video_loads
- test_state_field_volume_persists_across_video_loads
- test_state_field_loop_mode_persists
- test_invariant_pending_mute_replayed_into_video (RACE FIX!)
Phase A (16 tests) still green.
Tests still failing (8, scheduled for commit 10):
- DisplayingImage content arrived branch (commit 10)
- Closing transitions (commit 10)
- Open + first content with image kind (commit 10)
Test cases for commit 10 (DisplayingImage + Closing):
- ContentArrived(IMAGE) → DisplayingImage + LoadImage(is_gif=False)
- ContentArrived(GIF) → DisplayingImage + LoadImage(is_gif=True)
- DisplayingImage + ContentArrived(IMAGE) replaces media
- CloseRequested from each state → Closing + StopMedia + EmitClosed