stream: split radio.liq into pipeline, web and ingest-proxy parts
Extract the HTTP surface out of radio.liq into two included files: web.liq (static assets, PWA, local player API) and ingest_proxy.liq (relays to the ingest daemon). radio.liq keeps only the streaming pipeline and ends with the %include directives, evaluated after the pipeline so the handlers see radio, now_playing, history, etc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c73b71d32f
commit
8054c98dd1
4 changed files with 311 additions and 292 deletions
117
stream/ingest_proxy.liq
Normal file
117
stream/ingest_proxy.liq
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# radieo — reverse-proxy vers le daemon d'ingestion.
|
||||
#
|
||||
# Inclus par radio.liq. Le player n'a pas d'accès direct au réseau interne
|
||||
# (l'ingest n'est joignable que depuis les autres conteneurs) : on relaie donc
|
||||
# ces quelques endpoints à travers le harbor du flux (port 8000). Ces routes
|
||||
# sont autonomes — elles ne dépendent que de `http`/`url`, jamais du pipeline —
|
||||
# et renvoient une valeur neutre plutôt qu'une erreur si l'ingest est
|
||||
# injoignable, pour ne pas casser le player.
|
||||
|
||||
# File d'attente des prochains morceaux, relayée depuis le daemon d'ingestion.
|
||||
ingest_queue_url = "http://ingest:8080/queue"
|
||||
harbor.http.register(
|
||||
port=8000, method="GET", "/queue",
|
||||
fun(_, resp) -> begin
|
||||
resp.content_type("application/json; charset=utf-8")
|
||||
body = http.get(ingest_queue_url, timeout=5.0)
|
||||
if body.status_code == 200 then
|
||||
resp.data(string.trim(body) ^ "\n")
|
||||
else
|
||||
resp.data("[]")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
# État du préchargement {ready, prefetch}. Si le daemon est injoignable on
|
||||
# renvoie un objet neutre plutôt qu'une erreur, pour ne pas casser le player.
|
||||
ingest_status_url = "http://ingest:8080/status"
|
||||
harbor.http.register(
|
||||
port=8000, method="GET", "/ingest/status",
|
||||
fun(_, resp) -> begin
|
||||
resp.content_type("application/json; charset=utf-8")
|
||||
body = http.get(ingest_status_url, timeout=5.0)
|
||||
if body.status_code == 200 then
|
||||
resp.data(string.trim(body) ^ "\n")
|
||||
else
|
||||
resp.data("{}")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
# Mettre une URL yt-dlp en file d'attente (piste seule, ou playlist/album
|
||||
# entier). On relaie la demande vers l'ingest, qui résout l'URL et la place en
|
||||
# file prioritaire (le prochain /next la servira). On renvoie tel quel son code
|
||||
# et son corps JSON ({queued: N} ou une erreur). Timeout large : résoudre une
|
||||
# grosse playlist peut prendre du temps. NB : la variable locale s'appelle
|
||||
# `link`, pas `url`, pour ne pas masquer le module `url` (url.encode).
|
||||
ingest_enqueue_url = "http://ingest:8080/enqueue"
|
||||
harbor.http.register(
|
||||
port=8000, method="POST", "/enqueue",
|
||||
fun(req, resp) -> begin
|
||||
link = list.assoc(default="", "url", req.query)
|
||||
if link == "" then
|
||||
resp.status_code(400)
|
||||
resp.data("missing url")
|
||||
else
|
||||
body = http.post(
|
||||
data="", timeout=60.0, "#{ingest_enqueue_url}?url=#{url.encode(link)}"
|
||||
)
|
||||
resp.status_code(body.status_code)
|
||||
resp.content_type("application/json; charset=utf-8")
|
||||
resp.data(string.trim(body) ^ "\n")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
# Retirer un morceau de la file d'attente. Symétrique de /enqueue : on relaie la
|
||||
# demande (l'`id` opaque fourni par /queue) vers l'ingest, qui retire l'entrée
|
||||
# correspondante. On renvoie tel quel son code et son corps JSON ({removed: true}
|
||||
# ou une erreur).
|
||||
ingest_dequeue_url = "http://ingest:8080/dequeue"
|
||||
harbor.http.register(
|
||||
port=8000, method="POST", "/dequeue",
|
||||
fun(req, resp) -> begin
|
||||
id = list.assoc(default="", "id", req.query)
|
||||
if id == "" then
|
||||
resp.status_code(400)
|
||||
resp.data("missing id")
|
||||
else
|
||||
body = http.post(
|
||||
data="", timeout=10.0, "#{ingest_dequeue_url}?id=#{url.encode(id)}"
|
||||
)
|
||||
resp.status_code(body.status_code)
|
||||
resp.content_type("application/json; charset=utf-8")
|
||||
resp.data(string.trim(body) ^ "\n")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
# Partage Subsonic à la demande. Un morceau de la bibliothèque Subsonic n'a pas
|
||||
# d'URL publique : son lien « source » pointe ici avec l'id du morceau. On
|
||||
# demande alors à l'ingest (qui détient les identifiants Subsonic) de créer un
|
||||
# partage public via createShare, puis on redirige l'auditeur vers l'URL
|
||||
# renvoyée. Le partage n'est donc créé que si quelqu'un clique réellement sur le
|
||||
# lien — jamais à chaque morceau joué. 404 si l'id manque, 502 si l'ingest ne
|
||||
# peut pas partager (partage désactivé côté serveur, injoignable…).
|
||||
ingest_share_url = "http://ingest:8080/share"
|
||||
harbor.http.register(
|
||||
port=8000, method="GET", "/share",
|
||||
fun(req, resp) -> begin
|
||||
song = list.assoc(default="", "song", req.query)
|
||||
if song == "" then
|
||||
resp.status_code(404)
|
||||
resp.data("missing song id")
|
||||
else
|
||||
body = http.post(data="", timeout=10.0, "#{ingest_share_url}?id=#{url.encode(song)}")
|
||||
share = json.parse(default={url=""}, string.trim(body))
|
||||
if body.status_code == 200 and share.url != "" then
|
||||
resp.status_code(302)
|
||||
resp.header("Location", share.url)
|
||||
resp.data("")
|
||||
else
|
||||
resp.status_code(502)
|
||||
resp.data("share unavailable")
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue