stream: show the queue of upcoming tracks (/queue)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-07-04 11:10:37 +08:00
commit 62302ac21d
4 changed files with 98 additions and 11 deletions

View file

@ -7,6 +7,8 @@ Endpoints:
net; empty ( silence) until something has played.
GET /status -> JSON prefetch state {ready, prefetch}, surfaced to the
player (proxied by the stream) so it can show buffering.
GET /queue -> JSON list of the upcoming (prefetched) tracks, oldest
first, surfaced to the player (proxied by the stream).
GET /healthz -> "ok"
"""
@ -61,6 +63,8 @@ class _Handler(BaseHTTPRequestHandler):
self._serve_fallback()
elif self.path == "/status":
self._serve_status()
elif self.path == "/queue":
self._serve_queue()
elif self.path == "/healthz":
self._text(200, "ok\n")
else:
@ -93,6 +97,11 @@ class _Handler(BaseHTTPRequestHandler):
})
self._text(200, body + "\n", "application/json; charset=utf-8")
def _serve_queue(self):
# Upcoming prefetched tracks, next first. A peek, nothing consumed.
body = json.dumps(self.server.queue.snapshot())
self._text(200, body + "\n", "application/json; charset=utf-8")
def _text(self, code: int, body: str, ctype: str = "text/plain; charset=utf-8"):
data = body.encode("utf-8")
self.send_response(code)

View file

@ -81,6 +81,27 @@ class TrackQueue:
with self._lock:
return len(self._ready)
def snapshot(self) -> list[dict]:
"""Display metadata of the upcoming tracks, oldest (next) first.
A peek at the prefetch buffer for the player's "up next" view; it does
not consume anything. Mirrors the fields exposed for the current track
(see ``annotate_uri``): a source ``url`` only for http(s) locators.
"""
with self._lock:
ready = list(self._ready)
items = []
for _path, track in ready:
entry = {
"title": track.title,
"artist": track.artist,
"origin": track.origin,
}
if track.locator.startswith(("http://", "https://")):
entry["url"] = track.locator
items.append(entry)
return items
# --- serving ----------------------------------------------------------
def pop_next(self) -> tuple[Path, Track] | None: