Skip past tracks Spotify is playing that are already in the library - #40
Merged
Conversation
Keeping the file on disk was only half the answer. Offstream declined to
record the song and Spotify played it to nobody for three minutes, so
working through a playlist that is mostly recorded meant sitting through
the parts that already are.
The command goes through the Windows media transport controls — the same
one the keyboard's next-track key sends. The Web API has POST
/me/player/next, but it wants the user-modify-playback-state scope, a
signed-in account and a Premium subscription for a convenience feature,
and a third scope on a consent screen CLAUDE.md holds to two. SMTC needs
none of those and reaches Spotify while it is minimised to the tray.
Four things this had to get right, each a distinct failure:
Armed from the second track a session admits, never the first. Pressing
record is not an instruction to start rearranging what is playing: the
song already under way is one the user chose and is part-way through,
and it was never going to be recorded whole anyway. It is also the only
defence against the media session's opening report, which is the
previous track with the play state already true for a few hundred
milliseconds — so a session acting on the first thing it sees fires the
command at the song the user has just started.
Asked once per track, marked before the call rather than compared after
it. Spotify goes on reporting the outgoing track for a moment after it
takes the command — the same staleness as above — so a session deciding
per observation would ask twice, and the second skip lands on a song the
user has not recorded. Marking the track and clearing it only when a
different one is considered covers both that window and repeat-one.
Asked at two checkpoints, not one. The existing-file check in Consider
runs before the metadata lookup, so a template built on {album}, {year}
or {track} renders a path nothing will ever be written to and every
track looks new. Wired only there, skipping would silently never fire
for exactly the libraries organised well enough to want it. The second
checkpoint runs when the lookup lands, gated on the recorder still being
the current one — a lookup outlives its track on purpose, and by the
time it returns the user may be two songs on.
Stopped after fifty in a row, and said so. A queue Offstream already has,
left on repeat, has no other terminating condition: every skip lands on
another track that is also on disk and the session drives Spotify round
it forever, at the speed of a media command rather than of a song. The
budget is restored by a recording actually reaching the library — not by
one merely starting, or the enrichment checkpoint above would reset the
cap on every track and never reach it.
Off by default: it reaches out and changes what the user is listening
to. Greyed out under Overwrite and Duplicate, which write the file again
and leave nothing to skip past — and the core reads the two settings
together rather than trusting the page, since a hand-edited
settings.json can disagree.
The switch sits beside the existing-file dropdown rather than under it,
which meant making that grid's right-hand column auto-size: it held the
counter field, hidden for every template without {count}, and a star
column reserves its half of the card whether the thing in it is visible
or not. The row had 450 units for a dropdown, a switch and its label,
and the label was cut off mid-word.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keeping the file on disk was only half the answer. Offstream declined to record the song and Spotify played it to nobody for three minutes, so working through a playlist that is mostly recorded meant sitting through the parts that already are.
A new switch beside the existing-file setting on the Advanced page — "Also tell Spotify to move on" — sends a skip through the Windows media transport controls when the track playing is one already in the library. Off by default, greyed out under the two policies that write the file again.
Why SMTC and not the Web API
POST /me/player/nextwould work, but it wants theuser-modify-playback-statescope, a signed-in account and a Premium subscription for a convenience feature — and a third scope on a consent screenCLAUDE.mdholds to two. SMTC is the same command the keyboard's next-track key sends: no account, no scope, no network, and it reaches Spotify while it is minimised to the tray.IPlaybackControlis deliberately a second interface rather than a method onISmtcSessions. Reading what is playing and changing what plays are different privileges, and a session handed no implementation simply never skips.Four things this had to get right
Each is a distinct failure, and each has a test.
Armed from the second track a session admits, never the first. Pressing record is not an instruction to start rearranging what is playing: the song already under way is one the user chose and is part-way through, and was never going to be recorded whole anyway. It is also the only defence against the media session's opening report, which is the previous track with the play state already true for a few hundred milliseconds — so a session acting on the first thing it sees fires the command at the song the user has just started.
Asked once per track, marked before the call rather than compared after it. Spotify goes on reporting the outgoing track for a moment after it takes the command — the same staleness — so a session deciding per observation asks twice, and the second skip lands on a song the user has not recorded. Marking the track and clearing it only when a different one is considered covers both that window and repeat-one.
Asked at two checkpoints, not one. The existing-file check in
Considerruns before the metadata lookup, so a template built on{album},{year}or{track}renders a path nothing will ever be written to and every track looks new. Wired only there, skipping would silently never fire for exactly the libraries organised well enough to want it. The second checkpoint runs when the lookup lands, gated on the recorder still being the current one — a lookup outlives its track on purpose, and by the time it returns the user may be two songs on.Stopped after fifty in a row, and said so. A queue Offstream already has, left on repeat, has no other terminating condition: every skip lands on another track that is also on disk. The budget is restored by a recording actually reaching the library — not by one merely starting, or the enrichment checkpoint above would reset the cap on every track and never reach it.
The core reads
SkipAlreadyRecordedTracksand the existing-file policy together rather than trusting the page, since a hand-editedsettings.jsoncan disagree with a greyed-out switch.Layout
The switch sits beside the dropdown rather than under it, which meant auto-sizing that grid's right-hand column. It holds the counter field, hidden for every template without
{count}— and a star column reserves its half of the card whether the thing in it is visible or not, so the row had 450 units for a dropdown, a switch and its label, and the label was cut off mid-word.Testing
920 Core + 201 UI tests green; solution builds with 0 warnings under analyzers-as-errors.
Not covered:
WindowsSmtcSessions.TrySkipNextAsyncand itsIsNextEnabledguard are the untestable seam by design, so every test runs against a fake transport. What is proven is when Offstream decides to skip, never that the command reaches Spotify. Same shape as the routing gap the plan already records — wants a live try before this is relied on.🤖 Generated with Claude Code