feat: Opt-in track metadata cache, next_track and GET /context/tracks - #351
Open
palchrb wants to merge 1 commit into
Open
feat: Opt-in track metadata cache, next_track and GET /context/tracks#351palchrb wants to merge 1 commit into
palchrb wants to merge 1 commit into
Conversation
palchrb
force-pushed
the
claude/upstream-metadata-cache
branch
from
August 4, 2026 05:14
a163184 to
2de359c
Compare
…ting The API describes exactly one track: the one playing. NextTracks in the player state carries bare uris, so a client that wants to render anything about the upcoming track — its name, its cover — has only the public Web API, which rate-limits aggressively. Meanwhile the daemon's authenticated spclient session already fetches this metadata for playback, over endpoints sized for client traffic, and throws it away. Keep it instead. An in-memory cache (bounded, ~1000 entries) remembers the metadata of loaded and prefetched streams, and a batched extended-metadata request fills it for the tracks around the playback position. That feeds two things: - next_track in GET /status: the upcoming track with full metadata, so a client can pre-warm its display before the user skips. - GET /context/tracks?uri=...: any playable context — playlist, album, artist — listed in order with metadata, resolved through the same context resolver playback uses. With POST /player/play's existing skip_to_uri this is enough for a client to render a browsable song list and start any entry, at a cost of one load per pick and zero Web API calls. Everything is opt-in and off by default. A headless speaker has no use for metadata beyond the playing track and must not pay network requests for it: with metadata.enabled false the caches are never constructed, every helper treats the nil caches as a no-op (locked in by test), and the endpoint answers 404. A second flag, metadata.context_sweep, additionally resolves the whole context when one starts playing — paced batches of 100, one per second, capped at metadata.max_tracks — so every track is known before the user skips anywhere. Nothing here blocks the control loop. The listing endpoint answers from what is enumerated and cached, reporting ready=false while enumeration runs in the background; enumeration is single-flighted per context and its result reused for five minutes, so a polling client does not re-page the context. Sweeps are serialised — one runs, at most one waits, a newer job replaces the waiting one — and a waiting job re-checks what is missing when it starts, so it never re-fetches what the sweep ahead of it cached. Tracks and episodes both list: each uri is resolved under its own metadata kind (TRACK_V4 or EPISODE_V4) in the same batched request, so shows enumerate to their episodes and a playlist containing episodes lists completely. A user's Liked Songs collection (spotify:user:<id>:collection) is accepted as well — its multi-segment uri just needed to pass validation; the context resolver already expands it. The listing carries no revision: an edited playlist is picked up when the five-minute reuse window expires. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FHWG4ossSBydy7jbVFWho
palchrb
force-pushed
the
claude/upstream-metadata-cache
branch
from
September 1, 2026 10:02
2de359c to
178d7e0
Compare
Author
|
Aligning with upstream as of today |
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.
Closes the second half of #346.
The API describes exactly one track — the playing one — so a client that wants to show what's coming has only the public Web API, which rate-limits. Meanwhile the daemon's own session already fetches this metadata for playback and throws it away. This keeps it: a bounded in-memory cache feeds next_track in /status and a GET /context/tracks listing for any playable context (playlist, album, artist, show, Liked Songs — built on InferSpotifyIdTypeFromContextUri from yesterday's commits).
Everything is off by default (metadata.enabled, metadata.context_sweep): disabled, no caches are constructed, every helper is a nil no-op (locked in by test), and the endpoint answers 404. Nothing blocks the control loop — enumeration runs in the background behind a ready flag, sweeps are serialised and paced at one batch of 100 per second.
Note: this and #350 both add fields to the status schema and state to AppPlayer, so they conflict trivially at a couple of insertion points. They're fully independent features — review in either order, and I'll rebase whichever is still open as soon as the other lands.