Milestone 2: ingestion daemon driving the stream
Add the Python `ingest` container exposing `GET /next`, which returns the next track as an annotated Liquidsoap URI (or an empty body when nothing is ready). Liquidsoap switches from a static playlist to a `request.dynamic` source pulling from the daemon, with the local cache as fallback and mksafe for guaranteed continuous output. For now the daemon just cycles through the files already in the cache; the download providers (Navidrome, yt-dlp, ListenBrainz) come in later milestones. Also commit the implementation plan (PLAN.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
29ab0be7cb
commit
f8eb0655eb
9 changed files with 247 additions and 19 deletions
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/liquidsoap
|
||||
|
||||
# radieo — couche diffusion (jalon 1)
|
||||
# Joue le dossier /cache en boucle aléatoire et le diffuse en HTTP.
|
||||
# Les jalons suivants remplaceront la source par un request.dynamic piloté
|
||||
# par le daemon d'ingestion, en gardant ce dossier comme secours.
|
||||
# radieo — couche diffusion (jalon 2)
|
||||
# La source principale est pilotée par le daemon d'ingestion via GET /next.
|
||||
# Le dossier /cache sert de secours quand le daemon n'a rien à proposer
|
||||
# (daemon indisponible, file momentanément vide…). Si tout est vide : silence.
|
||||
|
||||
# --- Journalisation : tout sur la sortie standard (pratique en conteneur) ---
|
||||
settings.log.stdout := true
|
||||
|
|
@ -13,11 +13,33 @@ settings.log.level := 3
|
|||
# --- Harbor : écoute sur toutes les interfaces du conteneur ---
|
||||
settings.harbor.bind_addrs := ["0.0.0.0"]
|
||||
|
||||
# --- Source : le dossier de cache, rechargé quand son contenu change ---
|
||||
radio = playlist(mode="randomize", reload_mode="watch", "/cache")
|
||||
# URL du daemon d'ingestion (nom de service résolu par docker-compose).
|
||||
ingest_url = "http://ingest:8080/next"
|
||||
|
||||
# mksafe garantit un flux continu : si la source échoue ou est vide,
|
||||
# Liquidsoap émet du silence plutôt que de planter.
|
||||
# Callback appelé par request.dynamic pour obtenir le prochain morceau.
|
||||
# Renvoie une requête à jouer, ou null() si rien n'est disponible (→ secours).
|
||||
def next_track() =
|
||||
resp = http.get(ingest_url, timeout=5.0)
|
||||
body = string.trim(resp)
|
||||
if resp.status_code == 200 and body != "" then
|
||||
request.create(body)
|
||||
else
|
||||
null
|
||||
end
|
||||
end
|
||||
|
||||
# Source principale : pilotée par le daemon. prefetch=1 pour anticiper le
|
||||
# prochain morceau ; retry_delay pour ne pas marteler le daemon en cas de vide.
|
||||
main = request.dynamic(next_track, prefetch=1, retry_delay=1.0)
|
||||
|
||||
# Secours : le cache local, joué en aléatoire, rechargé quand il change.
|
||||
backup = playlist(mode="randomize", reload_mode="watch", "/cache")
|
||||
|
||||
# fallback préfère la source principale et bascule sur le cache si elle n'a
|
||||
# rien de prêt. track_sensitive=true : on ne coupe pas un morceau en cours.
|
||||
radio = fallback(track_sensitive=true, [main, backup])
|
||||
|
||||
# mksafe garantit un flux continu : silence plutôt que plantage si tout est vide.
|
||||
radio = mksafe(radio)
|
||||
|
||||
# --- Sortie : flux MP3 sur http://<hote>:8000/radio.mp3 ---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue