(function () { const root = document.getElementById("regia-app"); if (!root) return; const cfg = { token: root.dataset.token, statusUrl: root.dataset.statusUrl, scoreUrl: root.dataset.scoreUrl, pauseUrl: root.dataset.pauseUrl, stopUrl: root.dataset.stopUrl, cableUrl: root.dataset.cableUrl, hlsUrl: root.dataset.hlsUrl, homeName: root.dataset.homeName, awayName: root.dataset.awayName, shareUrl: root.dataset.shareUrl, shareTitle: root.dataset.shareTitle, streamClosed: root.dataset.streamClosed === "true" }; const els = { badge: document.getElementById("regia-badge"), setsLine: document.getElementById("sets-line"), scoreLine: document.getElementById("score-line"), partialsLine: document.getElementById("partials-line"), homePoints: document.getElementById("home-points"), toast: document.getElementById("regia-toast"), modal: document.getElementById("regia-modal"), modalTitle: document.getElementById("modal-title"), modalBody: document.getElementById("modal-body"), modalConfirm: document.getElementById("modal-confirm"), modalCancel: document.getElementById("modal-cancel"), preview: document.getElementById("regia-preview"), previewPlaceholder: document.getElementById("regia-preview-placeholder") }; let pendingAction = null; let hls = null; let previewStarted = false; function toast(msg) { els.toast.textContent = msg; els.toast.classList.add("visible"); setTimeout(() => els.toast.classList.remove("visible"), 2800); } function formatPartials(partials) { if (!partials || !partials.length) return ""; return partials .map((p) => `Set ${p.set || p["set"]} ${p.home || p["home"]}-${p.away || p["away"]}`) .join(" · "); } function applyScorePayload(data) { const s = data.score; if (!s) return; const homePts = s.home_points ?? s.homePoints; const awayPts = s.away_points ?? s.awayPoints; const homeSets = s.home_sets ?? s.homeSets; const awaySets = s.away_sets ?? s.awaySets; const currentSet = s.current_set ?? s.currentSet; const awayEl = document.getElementById("away-points"); els.homePoints.textContent = homePts; if (awayEl) awayEl.textContent = awayPts; els.setsLine.textContent = `Set ${currentSet} · Set vinti ${homeSets}-${awaySets}`; els.scoreLine.textContent = `${cfg.homeName} ${homePts} - ${awayPts} ${cfg.awayName}`; const partialsText = formatPartials(s.set_partials || s.setPartials); if (partialsText) { els.partialsLine.textContent = `Parziali: ${partialsText}`; els.partialsLine.hidden = false; } } function setBadge(data) { if (data.stream_closed) { els.badge.textContent = "Terminata"; els.badge.className = "regia-badge regia-badge--ended"; return; } if (data.on_air) { els.badge.textContent = "In onda"; els.badge.className = "regia-badge regia-badge--live"; } else { els.badge.textContent = "In attesa"; els.badge.className = "regia-badge regia-badge--wait"; } } async function postScore(action) { const res = await fetch(cfg.scoreUrl, { method: "PATCH", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ cmd: action }) }); if (!res.ok) throw new Error("Errore aggiornamento punteggio"); return res.json(); } async function handleAction(action) { try { const data = await postScore(action); applyScorePayload(data); setBadge(data); if (data.set_won && action !== "close_set") { const winner = data.winner === "home" ? cfg.homeName : cfg.awayName; openModal("Set vinto", `${winner} ha vinto il set. Chiudere il set?`, () => postScore("close_set")); } else if (data.match_won) { const winner = data.winner === "home" ? cfg.homeName : cfg.awayName; openModal("Partita vinta", `${winner} ha vinto la partita. Chiudere la diretta?`, () => stopStream()); } } catch (e) { toast(e.message || "Errore"); } } function openModal(title, body, onConfirm) { els.modalTitle.textContent = title; els.modalBody.textContent = body; els.modal.classList.add("open"); pendingAction = onConfirm; } function closeModal() { els.modal.classList.remove("open"); pendingAction = null; } els.modalConfirm.addEventListener("click", async () => { const fn = pendingAction; closeModal(); if (fn) { try { const data = await fn(); if (data) { applyScorePayload(data); setBadge(data); } } catch (e) { toast(e.message || "Errore"); } } }); els.modalCancel.addEventListener("click", closeModal); root.querySelectorAll("[data-action]").forEach((btn) => { btn.addEventListener("click", () => { const action = btn.dataset.action; if (action === "close_set") { openModal("Chiudi set", "Confermi la chiusura del set corrente?", () => postScore("close_set")); } else { handleAction(action); } }); }); function syncPreviewFromStatus(data) { if (data.stream_closed) { cfg.streamClosed = true; stopPreview(); return; } if (data.on_air) { if (!previewStarted) startPreview(); else if (els.preview && els.preview.readyState >= 2) hidePreviewPlaceholder(); } else { destroyPreview(); showPreviewPlaceholder("Anteprima in attesa del segnale…"); } } async function pollStatus() { try { const res = await fetch(cfg.statusUrl, { headers: { Accept: "application/json" } }); if (!res.ok) return; const data = await res.json(); applyScorePayload(data); setBadge(data); syncPreviewFromStatus(data); } catch (_) {} } function bindPreviewEvents() { if (!els.preview) return; ["playing", "loadeddata", "canplay"].forEach((ev) => { els.preview.addEventListener(ev, hidePreviewPlaceholder); }); } function startPreview() { if (cfg.streamClosed || !els.preview || !cfg.hlsUrl || previewStarted) return; previewStarted = true; const url = hlsUrlFresh(); if (window.Hls && Hls.isSupported()) { hls = new Hls({ lowLatencyMode: true, maxBufferLength: 8 }); hls.loadSource(url); hls.attachMedia(els.preview); hls.on(Hls.Events.MANIFEST_PARSED, () => { els.preview.play().catch(() => {}); }); hls.on(Hls.Events.ERROR, (_, err) => { if (err.fatal) { destroyPreview(); setTimeout(() => { if (!cfg.streamClosed) startPreview(); }, 2500); } }); } else if (els.preview.canPlayType("application/vnd.apple.mpegurl")) { els.preview.src = url; els.preview.addEventListener( "loadedmetadata", () => els.preview.play().catch(() => {}), { once: true } ); } } function stopPreview() { destroyPreview(); if (els.preview) els.preview.style.display = "none"; showPreviewPlaceholder("Diretta terminata"); } async function pauseStream() { await fetch(cfg.pauseUrl, { method: "POST", headers: { Accept: "application/json" } }); toast("Trasmissione in pausa"); } async function stopStream() { const ok = confirm("Chiudere definitivamente la diretta?"); if (!ok) return null; const res = await fetch(cfg.stopUrl, { method: "POST", headers: { Accept: "application/json" } }); if (!res.ok) throw new Error("Errore chiusura"); const data = await res.json(); cfg.streamClosed = true; stopPreview(); toast("Diretta chiusa"); return data; } document.getElementById("btn-pause")?.addEventListener("click", () => pauseStream().catch((e) => toast(e.message))); document.getElementById("btn-stop")?.addEventListener("click", () => stopStream().catch((e) => toast(e.message))); async function shareLink() { const payload = { title: cfg.shareTitle, text: "Apri questo link per gestire il punteggio della diretta:", url: cfg.shareUrl }; if (navigator.share) { try { await navigator.share(payload); return; } catch (err) { if (err.name === "AbortError") return; } } await copyLink(); } async function copyLink() { try { await navigator.clipboard.writeText(cfg.shareUrl); toast("Link copiato"); } catch (_) { prompt("Copia il link:", cfg.shareUrl); } } document.getElementById("btn-share")?.addEventListener("click", () => shareLink()); document.getElementById("btn-copy")?.addEventListener("click", () => copyLink()); bindPreviewEvents(); if (!cfg.streamClosed && cfg.hlsUrl) { pollStatus(); } else if (cfg.streamClosed) { stopPreview(); } setInterval(pollStatus, 4000); })();