Skip to content

Deferred transport resume threads ignore pause intent and stack across rapid skips #448

Description

@LargeModGames

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

  1. Play natively (spotatui as the Connect device)
  2. Press next-track, then pause within ~300 ms
  3. 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):

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions