Skip to content

feat: Opt-in track metadata cache, next_track and GET /context/tracks - #351

Merged
devgianlu merged 1 commit into
devgianlu:masterfrom
palchrb:claude/upstream-metadata-cache
Sep 14, 2026
Merged

devgianlu merged 1 commit into
devgianlu:masterfrom
palchrb:claude/upstream-metadata-cache

Conversation

@palchrb

@palchrb palchrb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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.

@palchrb

palchrb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Aligning with upstream as of today

@ds17f

ds17f commented Sep 10, 2026

Copy link
Copy Markdown

NOTE: I'm not a go dev, so it is tough for me to review the code. My AI liked it, but whatever. What I was able to do was test it and I got the following result:

I tested this branch on real hardware. It works.

Test setup

  • I built the branch on Fedora with Go 1.26.8 and the ALSA backend.
  • I set metadata.enabled: true and metadata.context_sweep: true.
  • I connected from a phone with Spotify Connect over zeroconf.
  • I played an album (Dire Straits, Love Over Gold).

Result

  • GET /context/tracks?uri=spotify:album:... returned all 5 tracks.
  • Each track had a uri, a name, an artist name, and a duration.
  • The response showed ready=true and cached == length.
  • GET /status also showed the new next_track field.
  • The log had no 429 error. The data came from the internal spclient API.

Two notes that can help

  1. The uri parameter is required, and /status does not give the active context uri. So a client cannot go from "what plays now" to "its track list" without the uri already. This is the same gap as Add context_uri, context_name and the play origin device to /status #380. A small GET /context for the active context would close it.
  2. I tested a 5-track album only. The background enumeration and the poll loop (ready=false, then cached < length) are not yet tested with a large playlist.

I can test a large playlist next if that helps.

@devgianlu

Copy link
Copy Markdown
Owner

This looks good, I did a large refactor so this needs rebasing. I would also use an exernal module for the LRU: https://github.com/hashicorp/golang-lru

…ting

Clients that render an upcoming-track preview or a browsable song list had to
hit the public Web API for metadata and were getting rate limited. The daemon
already talks to the internal extended-metadata endpoint for playback, so it
can serve that itself: an in-memory LRU cache fed by every loaded and
prefetched stream, topped up for the connect-state window around the playback
position, and exposed as next_track in /status and GET /context/tracks.

Everything is opt-in behind metadata.enabled: off, no cache is allocated and
no request playback does not need is made. All metadata work runs detached
from the player and loader lanes, coalesced onto a timer so a burst of skips
costs one fetch. The LRU is hashicorp/golang-lru per review.

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-metadata-cache branch from 178d7e0 to 1405b6c Compare September 12, 2026 22:08
@devgianlu
devgianlu merged commit bf208de into devgianlu:master Sep 14, 2026
11 checks passed
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.

3 participants