(function () { const list = document.getElementById('list'); const filter = document.getElementById('filter'); // --- wie in stream.js: mögliche Manifest-Dateien ausprobieren --- async function chooseManifest(name) { const enc = encodeURIComponent(name); const candidates = [ `/hls/${enc}/index.m3u8`, `/hls/${enc}/main_stream.m3u8`, ]; for (const url of candidates) { try { const r = await fetch(url, { cache: 'no-store' }); if (r.ok) return url; } catch (_) {} } return null; } // Kleinster gemeinsamer Nenner: API-Live ODER Manifest erreichbar const probeCache = new Map(); // vermeidet Doppel-Requests pro Render async function probeLive(name) { if (probeCache.has(name)) return probeCache.get(name); const p = (async () => { const url = await chooseManifest(name); return !!url; })(); probeCache.set(name, p); return p; } function render(data) { const q = (filter.value || '').toLowerCase(); list.innerHTML = ''; data.items .filter((it) => !q || it.name.toLowerCase().includes(q)) .forEach((it) => { const a = document.createElement('a'); a.href = '/' + encodeURIComponent(it.name); a.className = 'card'; a.dataset.stream = it.name; // Grundzustand: was die API sagt const apiLive = !!it.live; const pillClass = apiLive ? 'live' : 'off'; const pillText = apiLive ? 'LIVE' : 'Offline'; const viewers = (()=>{ const cands = [it.viewers, it.viewerCount, it.viewCount, it.watchers, it.clients, it.hls_viewers, it.stats && it.stats.viewers]; for (const v of cands){ const n = typeof v === 'string' ? parseInt(v,10) : v; if (Number.isFinite(n) && n >= 0) return n; } return 0; })(); a.innerHTML = `