stream: surface ingest prefetch progress in the player

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-07-03 12:22:53 +08:00
commit 1648030eba
4 changed files with 66 additions and 2 deletions

View file

@ -5,9 +5,12 @@ Endpoints:
is ready (Liquidsoap then falls back to /fallback.m3u).
GET /fallback.m3u -> playlist of already-aired files, the stream's safety
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 /healthz -> "ok"
"""
import json
import logging
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
@ -50,6 +53,8 @@ class _Handler(BaseHTTPRequestHandler):
self._serve_next()
elif self.path == "/fallback.m3u":
self._serve_fallback()
elif self.path == "/status":
self._serve_status()
elif self.path == "/healthz":
self._text(200, "ok\n")
else:
@ -73,6 +78,15 @@ class _Handler(BaseHTTPRequestHandler):
lines.append(p)
self._text(200, "\n".join(lines) + "\n", "audio/x-mpegurl; charset=utf-8")
def _serve_status(self):
# Prefetch progress: how many tracks are buffered vs. the target. The
# stream's initial buffer is "full" once ready reaches PREFETCH.
body = json.dumps({
"ready": self.server.queue.ready_count(),
"prefetch": config.PREFETCH,
})
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

@ -74,6 +74,13 @@ class TrackQueue:
with self._lock:
self._ready.append((path, track))
# --- introspection ----------------------------------------------------
def ready_count(self) -> int:
"""Number of tracks currently downloaded and ready to play."""
with self._lock:
return len(self._ready)
# --- serving ----------------------------------------------------------
def pop_next(self) -> tuple[Path, Track] | None: