fix(player): lockscreen skip scrubs instead of advancing in background audio

onSkipToNext/onSkipToPrevious forwarded a bare next/previous to Rust, which
always advanced the queue. Correct for music, wrong for a video whose audio is
running through a background-audio handoff (UR-040): pressing skip to re-hear a
line jumped to the next episode instead of scrubbing.

resolve_skip_action in player/seek.rs maps the command to Advance or SeekTo, and
is_background_audio_active() is the whole test — the handoff exists only for
video, and an episode played through it reports MediaType::Audio, so media type
cannot distinguish the case. Forward 30s, back 10s, both clamped to [0, duration]
so a skip near either end cannot seek negative or read as EOF and advance.

Routed through the same spawn-then-seek_absolute path as the scrubber, because a
handoff seek re-opens the stream and must not run under the blocking lock
(DR-159). Kotlin keeps sending the opaque command; it only gains FAST_FORWARD/
REWIND in the PlaybackStateCompat so the system stops drawing skip arrows for a
control that scrubs. The remote-volume action block is deliberately untouched:
the handoff never applies to cast sessions, where skip really does mean advance.

Tests written first and watched fail (left: Advance, right: SeekTo). 706 Rust
tests pass, clippy 0, coverage 90%.
This commit is contained in:
2026-08-16 23:54:43 +02:00
parent 42e7d86ec4
commit 6dfc6b259a
5 changed files with 221 additions and 7 deletions
+4 -3
View File
@@ -264,14 +264,15 @@ describe("live requirements.md", () => {
expect(defined.UR).toBe(75);
expect(defined.IR).toBe(32);
// 191 = 187 + four requirements added independently on four branches
// 192 = 187 + four requirements added independently on four audit branches,
// plus DR-201 (lockscreen skip resolution). Originally 191 = 187 + four
// that landed together: DR-189 (control-bar auto-hide), DR-198 (asset
// scope/CSP), DR-199 (webview mixed-content) and DR-200 (the
// POST_NOTIFICATIONS media-session exemption; renumbered from 198 on
// merge, where it collided). Each branch bumped for its own — merged,
// they sum. Resolve this by summing, never by taking one side.
expect(defined.DR).toBe(191);
expect(defined.DR).toBe(192);
expect(defined.JA).toBe(36);
expect(defined.total).toBe(334);
expect(defined.total).toBe(335);
});
});