/* * app.js - Glue between the DOM and StreamPlayer. * * - Polls the MediaMTX API (/v3/paths/get/game) to determine if the stream * is live, and updates the status bar accordingly. * - Starts/stops StreamPlayer based on that status. * - Wires up the "Click to unmute" button (browsers autoplay muted). * - Authentik forward auth is handled at the NPM layer, so by the time * this JS runs the user is already authenticated. No auth logic here. */ (function () { const PATH_NAME = 'game'; const API_URL = `/v3/paths/get/${PATH_NAME}`; const POLL_INTERVAL_MS = 5000; const els = { video: document.getElementById('video'), overlay: document.getElementById('overlay'), overlayTitle: document.getElementById('overlay-title'), overlayMessage: document.getElementById('overlay-message'), overlayCard: document.querySelector('.overlay-card'), statusIndicator: document.getElementById('status-indicator'), viewerCount: document.getElementById('viewer-count'), latency: document.getElementById('latency'), transport: document.getElementById('transport'), unmuteBtn: document.getElementById('unmute-btn'), }; const state = { pathReady: false, playerState: 'offline', }; // ---- overlay helpers ------------------------------------------------- function showOverlay(title, message, isError) { els.overlayTitle.textContent = title; els.overlayMessage.textContent = message; els.overlayCard.classList.toggle('error', !!isError); els.overlay.hidden = false; } function hideOverlay() { els.overlay.hidden = true; } // ---- status bar helpers --------------------------------------------- function setStatusIndicator(label, cls) { els.statusIndicator.textContent = label; els.statusIndicator.className = 'status ' + cls; } function setViewerCount(n) { els.viewerCount.textContent = String(n); } function setLatency(ms) { els.latency.textContent = ms == null ? '--' : `${ms} ms`; } function setTransport(label) { els.transport.textContent = label || '--'; } // ---- MediaMTX API polling ------------------------------------------- async function pollPathStatus() { try { const res = await fetch(API_URL, { credentials: 'include' }); if (res.status === 404) { applyPathStatus(false, 0); return; } if (!res.ok) { // 401/403 means session expired - force reload so NPM can redirect to Authentik. if (res.status === 401 || res.status === 403) { window.location.reload(); } return; } const data = await res.json(); applyPathStatus(Boolean(data.ready), (data.readers || []).length); } catch (_) { applyPathStatus(false, 0); } } function applyPathStatus(ready, viewers) { setViewerCount(viewers); if (ready && !state.pathReady) { state.pathReady = true; setStatusIndicator('LIVE', 'live'); showOverlay('Connecting to stream…', 'Negotiating WebRTC', false); window.StreamPlayer.start(els.video); } else if (!ready && state.pathReady) { state.pathReady = false; setStatusIndicator('OFFLINE', 'offline'); setTransport('--'); setLatency(null); showOverlay('Stream offline', 'Waiting for the streamer to start…', false); window.StreamPlayer.stop(); } } // ---- Unmute button -------------------------------------------------- // // Browsers require a user gesture to play audio. The