stream: make the web player installable as a PWA
Wire up a web manifest, service worker and icon routes so the player can be installed on mobile. The static manifest stays generic; the page regenerates it at runtime from STATION_NAME so an instance keeps its name. The service worker only caches the app shell, never the live stream or the playback APIs. Icons (192/512, maskable and apple-touch) are rasterized from the favicon. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
976f009297
commit
c73b71d32f
10 changed files with 239 additions and 0 deletions
|
|
@ -5,6 +5,17 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title id="pageTitle"></title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<!-- Application installable (PWA). Le manifeste décrit nom, icônes et mode
|
||||
d'affichage ; les balises Apple font l'équivalent sur iOS, qui n'exploite
|
||||
pas le manifeste pour l'ajout à l'écran d'accueil. La couleur de thème
|
||||
teinte la barre système en mode autonome. -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" id="manifestLink">
|
||||
<meta name="theme-color" content="#0d0b14">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="" id="appleTitle">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
|
|
@ -235,6 +246,39 @@
|
|||
document.getElementById("stationName").textContent = "◈ " + STATION_NAME;
|
||||
document.getElementById("pageTitle").textContent = STATION_NAME;
|
||||
|
||||
// Le manifeste statique reste générique (« Radieo ») pour être réutilisable ;
|
||||
// on le régénère ici à partir de STATION_NAME, l'unique réglage de la station,
|
||||
// afin que l'app installée porte le nom de l'instance. Le manifeste est servi
|
||||
// en data URL, donc toutes les URLs qu'il contient doivent être absolues.
|
||||
(async () => {
|
||||
try {
|
||||
const link = document.getElementById("manifestLink");
|
||||
const base = await fetch(link.href, { cache: "no-store" }).then((r) => r.json());
|
||||
const abs = (p) => new URL(p, location.origin).href;
|
||||
const manifest = {
|
||||
...base,
|
||||
name: STATION_NAME,
|
||||
short_name: STATION_NAME,
|
||||
start_url: abs(base.start_url || "/"),
|
||||
scope: abs(base.scope || "/"),
|
||||
icons: (base.icons || []).map((ic) => ({ ...ic, src: abs(ic.src) })),
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(manifest)], { type: "application/manifest+json" });
|
||||
link.href = URL.createObjectURL(blob);
|
||||
} catch (e) { /* on garde le manifeste statique générique */ }
|
||||
// iOS n'exploite pas le manifeste : on lui donne le nom via sa balise dédiée.
|
||||
document.getElementById("appleTitle").setAttribute("content", STATION_NAME);
|
||||
})();
|
||||
|
||||
// Enregistrement du service worker : condition pour que la webapp soit
|
||||
// « installable » sur mobile (Android/Chrome) et pour ouvrir la fenêtre
|
||||
// autonome depuis le cache. Échec silencieux si non supporté.
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
// Fond aléatoire repris de l'écran de connexion Navidrome : on récupère la
|
||||
// liste de leur galerie (index.yml, CORS ouvert), on tire un nom au hasard
|
||||
// et on sert la version .webp depuis leur CDN — même logique que Navidrome,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue