Describe the bug
The three native skip paths (previous_track, force_previous_track, next_track in src/core/app/transport.rs) spawn a detached thread that sleeps 300-500 ms and then issues activate()/play() (plus prev() in the force path) against the streaming player, to work around librespot occasionally landing paused after a skip. No thread checks native_is_playing, pause intent, or a generation value before firing, so:
- Skip, then pause within the delay window:
pause_native_playback records paused intent and pauses the player, and the thread then resumes playback anyway.
- Rapid repeated skips stack multiple pending
play()/prev() commands against a track that has already changed.
Surfaced by CodeRabbit on PR #446 (thread: #446 (comment)). Deferred there because PR #446 is pure code movement and this touches the most regression-prone area of the codebase.
To Reproduce
- Play natively (spotatui as the Connect device)
- Press next-track, then pause within ~300 ms
- Playback resumes on its own when the deferred thread fires
Expected behavior
A deferred resume only lands if the skip it belongs to is still the current intent. Capture native_playback_generation (or introduce a dedicated counter) plus the pause intent before spawning, and have the thread re-check both before issuing commands. The force path needs the guard on both the delayed prev() and the delayed play().
Constraint from CLAUDE.md: native playback changes must be verified with the full cargo run build and a manual smoke test, not only the slim gate.
Desktop (please complete the following information):
Describe the bug
The three native skip paths (
previous_track,force_previous_track,next_trackinsrc/core/app/transport.rs) spawn a detached thread that sleeps 300-500 ms and then issuesactivate()/play()(plusprev()in the force path) against the streaming player, to work around librespot occasionally landing paused after a skip. No thread checksnative_is_playing, pause intent, or a generation value before firing, so:pause_native_playbackrecords paused intent and pauses the player, and the thread then resumes playback anyway.play()/prev()commands against a track that has already changed.Surfaced by CodeRabbit on PR #446 (thread: #446 (comment)). Deferred there because PR #446 is pure code movement and this touches the most regression-prone area of the codebase.
To Reproduce
Expected behavior
A deferred resume only lands if the skip it belongs to is still the current intent. Capture
native_playback_generation(or introduce a dedicated counter) plus the pause intent before spawning, and have the thread re-check both before issuing commands. The force path needs the guard on both the delayedprev()and the delayedplay().Constraint from CLAUDE.md: native playback changes must be verified with the full
cargo runbuild and a manual smoke test, not only the slim gate.Desktop (please complete the following information):