fix(playback): restore audible volume when M unmutes at zero volume

Addresses the Codex review finding on #1398: after arrowing the volume
down to zero (which mutes), M flipped muted off while leaving the volume
at 0, so the player looked unmuted but stayed silent — in all three
legacy engine adapters.

Mirror the shared controls' ControlsVolume semantics with a per-adapter
LegacyMuteMemory: muting remembers the audible volume, and unmuting while
the volume sits at zero restores it, with the same 0.5 fallback when
nothing was remembered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014h2cZi5DcFSbcmV7WgB6qB
This commit is contained in:
Claude committed 2026-08-09 20:40:14 +00:00
1 parent f22f517b84
commit a37b6c3fd9
8 files changed
+136 -8

No files matched your search

@@ -362,7 +362,10 @@ Shared legacy rules: seek is gated on authoritative `isLive` plus a finite,
positive duration (ArtPlayer gates on `art.duration`, the same value its seek
setter clamps against, so an unknown duration never jumps to zero); volume
steps by ±5% and syncs muted state the way `applyVideoVolume` does (raising
out of mute unmutes, reaching zero mutes); `isAvailable` is the host's
out of mute unmutes, reaching zero mutes); M mirrors `ControlsVolume`'s mute
memory through `LegacyMuteMemory` — muting remembers the audible volume, and
unmuting while the volume sits at zero restores it (same 0.5 fallback), so M
can never leave the player silently "unmuted"; `isAvailable` is the host's
`interactionEnabled`, so a visible playback diagnostic disables the keys; and
Escape defaults to a no-op without consuming the key, because the vendor
chrome owns its own overlays. Instances attach in the component's legacy