Skip to content

feat: debounce rapid next/prev into a single deferred track load - #350

Open
palchrb wants to merge 1 commit into
devgianlu:masterfrom
palchrb:claude/upstream-skip-debounce
Open

palchrb wants to merge 1 commit into
devgianlu:masterfrom
palchrb:claude/upstream-skip-debounce

Conversation

@palchrb

@palchrb palchrb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes the first half of #346.

A burst of next/prev presses currently loads every track it passes through — one audio-key request per press — and Spotify answers 429 well before the listener finds their song. This splits the skip into a pointer move and a deferred load: presses move the selection immediately (published via will_play and a new pending_track_uri in /status), and only the track the user stops on gets loaded.

The window (skip_debounce_ms, default 600, 0 disables) was measured, not guessed: across two sessions of continuous skipping from the Spotify iOS app, gaps between Connect commands ranged 74–495ms. Deferred pointer moves also skip the connect-state PUT (only the first of a burst publishes) — the burst otherwise outruns the endpoint's tolerance and reproduces the same 429 on a different endpoint. Field-tested on a Pi Zero 2 W: a 20-press burst now costs 2 loads and no rate limiting.

@palchrb

palchrb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Aligning with upstream as of today

@devgianlu

Copy link
Copy Markdown
Owner

I did a big refactor, heavy rebase needed

Every next or prev press moved the pointer and started a load at once.
A load cannot be recalled once started: NewStream has already asked for
the audio key before its context is checked, and the pump writes that
request out regardless, so cancelling the load on the next press still
costs the request. Mashing the button spent one key request per press,
and a long enough burst had Spotify answering 429 and refusing the track
the listener finally stopped on.

Give relative navigation a lane class of its own. A step forward or back
is not a destination: it is queued rather than dropped, so a burst of
twenty presses ends twenty tracks on, while it still cancels and drops
the loads and prefetches ahead of it, whose track the pointer is
leaving. That fixes a bug the old classification had on its own: a skip
that arrived while the previous one was still queued dropped its walk,
and its pointer step with it, so a burst could end short of where it was
pressed to.

Then hold the load a skip ends in. A press that follows another within
skip_debounce_ms queues its load with a time before which the lane will
not take it; the next press drops it unstarted, so a burst pays for the
one load it ends on. The first press of a burst loads immediately, so a
lone skip is as quick as before. Stopping playback, loading a context,
or a transfer drop a held load the same way they drop any other, and a
held load still queued at shutdown is answered like the rest.

Two consequences are handled on the loop. A second prev during a load no
longer reads the outgoing stream's position and restarts it; it goes to
the track before. And the outgoing stream keeps playing until the load
lands, so if it runs out meanwhile its end is among the events held for
the load: those are dropped when the load lands, since handling them
against the stream that just started would advance straight past it.

The state is still published per press. The connect-state PUTs it
raises are throttled and coalesced on the push lane, so there is nothing
to gate here, and will_play is emitted for every stop along the way.

Configured with skip_debounce_ms, defaulting to 600; 0 loads every skip
immediately.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FHWG4ossSBydy7jbVFWho
@palchrb
palchrb force-pushed the claude/upstream-skip-debounce branch from b5e20c4 to 0bc93f9 Compare September 12, 2026 22:08
@palchrb

palchrb commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

You think this is still needed, or did you address the fast skipping in your big refactor?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants