stream: add a whole-station restart of the current track
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Wire the Media Session previous-track control to a new POST /restart-track route that requeues the current song from the start for every listener, and keep next-track as skip. Exposing both handlers also makes Android (Chrome) show the skip button, which it hides when only nexttrack is set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
622210197f
commit
96a1ba89e6
2 changed files with 45 additions and 5 deletions
|
|
@ -245,9 +245,17 @@
|
||||||
safe(() => media.setActionHandler("play", () => player.play().catch(() => {})));
|
safe(() => media.setActionHandler("play", () => player.play().catch(() => {})));
|
||||||
safe(() => media.setActionHandler("pause", () => player.pause()));
|
safe(() => media.setActionHandler("pause", () => player.pause()));
|
||||||
safe(() => media.setActionHandler("stop", () => player.pause()));
|
safe(() => media.setActionHandler("stop", () => player.pause()));
|
||||||
// Pas de reprise possible sur un direct : « précédent » comme « suivant »
|
// « Suivant » saute le morceau courant. « Précédent » le rejoue depuis le
|
||||||
// passent au morceau suivant côté serveur.
|
// début, pour toute l'antenne (flux partagé, pas de position par
|
||||||
|
// auditeur). Brancher les deux est aussi nécessaire sous Android (Chrome),
|
||||||
|
// qui traite précédent/suivant comme une paire et masque le bouton suivant
|
||||||
|
// si seul « nexttrack » est défini.
|
||||||
safe(() => media.setActionHandler("nexttrack", () => { skipBtn.click(); }));
|
safe(() => media.setActionHandler("nexttrack", () => { skipBtn.click(); }));
|
||||||
|
safe(() => media.setActionHandler("previoustrack", () => {
|
||||||
|
fetch("/restart-track", { method: "POST" })
|
||||||
|
.then(() => setTimeout(() => { poll(); pollHistory(); }, 900))
|
||||||
|
.catch(() => {});
|
||||||
|
}));
|
||||||
// Le direct n'est pas déplaçable : on neutralise les actions de seek pour
|
// Le direct n'est pas déplaçable : on neutralise les actions de seek pour
|
||||||
// éviter que le système n'affiche des boutons d'avance/recul inopérants.
|
// éviter que le système n'affiche des boutons d'avance/recul inopérants.
|
||||||
safe(() => media.setActionHandler("seekbackward", null));
|
safe(() => media.setActionHandler("seekbackward", null));
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,17 @@ backup = playlist(
|
||||||
check_next=audio_only, fallback_file
|
check_next=audio_only, fallback_file
|
||||||
)
|
)
|
||||||
|
|
||||||
# fallback préfère la source principale et bascule sur le cache si elle n'a
|
# File de rejeu ponctuel : normalement vide (donc non prête, transparente). Le
|
||||||
# rien de prêt. track_sensitive=true : on ne coupe pas un morceau en cours.
|
# endpoint /restart-track y pousse le morceau courant pour le rejouer depuis le
|
||||||
music = fallback(track_sensitive=true, [main, backup])
|
# début à l'antenne. Placée en tête du fallback, elle préempte la source
|
||||||
|
# principale dès qu'un morceau y est poussé.
|
||||||
|
requeue = request.queue(id="requeue")
|
||||||
|
|
||||||
|
# fallback préfère la file de rejeu, puis la source principale, et bascule sur le
|
||||||
|
# cache si rien n'est prêt. track_sensitive=true : on ne coupe pas un morceau en
|
||||||
|
# cours (le rejeu ne prend donc effet qu'à la prochaine frontière, provoquée
|
||||||
|
# explicitement par source.skip dans /restart-track).
|
||||||
|
music = fallback(track_sensitive=true, [requeue, main, backup])
|
||||||
|
|
||||||
# --- Jingles : intercalés toutes les 2 chansons -----------------------------
|
# --- Jingles : intercalés toutes les 2 chansons -----------------------------
|
||||||
# Le dossier /jingles (monté depuis ./jingles) est parcouru et lu au hasard.
|
# Le dossier /jingles (monté depuis ./jingles) est parcouru et lu au hasard.
|
||||||
|
|
@ -270,6 +278,30 @@ harbor.http.register(
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Rejouer le morceau courant depuis le début, pour TOUTE l'antenne (le flux est
|
||||||
|
# partagé : il n'existe pas de position par auditeur). On repousse le fichier
|
||||||
|
# courant — pris dans le cache, donc forcément local et présent — en tête via la
|
||||||
|
# file de rejeu, puis on saute le morceau en cours pour l'y enchaîner aussitôt.
|
||||||
|
# On remet le compteur de chansons à zéro pour qu'un jingle ne vole pas la place
|
||||||
|
# du rejeu à cette frontière. Un morceau introuvable (jingle en cours, cache
|
||||||
|
# évincé) renvoie 409.
|
||||||
|
harbor.http.register(
|
||||||
|
port=8000, method="POST", "/restart-track",
|
||||||
|
fun(_, resp) -> begin
|
||||||
|
base = path.basename(list.assoc(default="", "filename", now_playing()))
|
||||||
|
full = "/cache/#{base}"
|
||||||
|
if base != "" and file.exists(full) then
|
||||||
|
song_count := 0
|
||||||
|
requeue.push.uri(full)
|
||||||
|
source.skip(radio)
|
||||||
|
resp.json({restarted=true})
|
||||||
|
else
|
||||||
|
resp.status_code(409)
|
||||||
|
resp.json({restarted=false})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
# Télécharger un morceau. Sans paramètre : le titre en cours. Avec `?file=<nom>` :
|
# Télécharger un morceau. Sans paramètre : le titre en cours. Avec `?file=<nom>` :
|
||||||
# n'importe quel fichier encore présent dans le cache (les titres passés listés
|
# n'importe quel fichier encore présent dans le cache (les titres passés listés
|
||||||
# par /history exposent ce jeton). Le cache étant borné par le LRU, un morceau
|
# par /history exposent ce jeton). Le cache étant borné par le LRU, un morceau
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue