bugfix-8
All checks were successful
release-tag / release-image (push) Successful in 1m58s

This commit is contained in:
2025-09-21 21:08:50 +02:00
parent a39e2aaade
commit 73286479cb

View File

@@ -1,42 +1,105 @@
(function(){ (function () {
const list = document.getElementById('list'); const list = document.getElementById('list');
const filter = document.getElementById('filter'); const filter = document.getElementById('filter');
function render(data){ // --- wie in stream.js: mögliche Manifest-Dateien ausprobieren ---
const q = (filter.value||'').toLowerCase(); 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 = ''; list.innerHTML = '';
data.items data.items
.filter(it => !q || it.name.toLowerCase().includes(q)) .filter((it) => !q || it.name.toLowerCase().includes(q))
.forEach(it => { .forEach((it) => {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = '/' + encodeURIComponent(it.name); a.href = '/' + encodeURIComponent(it.name);
a.className = 'card'; 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';
a.innerHTML = ` a.innerHTML = `
<div class="row space-between"> <div class="row space-between">
<div> <div>
<div class="title-strong">${it.name}</div> <div class="title-strong">${it.name}</div>
<div class="muted">Zuschauer: ${it.viewers ?? 0}</div> <div class="muted">Zuschauer: ${it.viewers ?? 0}</div>
</div> </div>
<div class="pill ${it.live ? 'live':'off'}">${it.live ? 'LIVE' : 'Offline'}</div> <div class="pill ${pillClass}" data-role="live-pill">${pillText}</div>
</div>`; </div>`;
list.appendChild(a); list.appendChild(a);
// Sofort im Hintergrund „echtes“ Live prüfen und ggf. überschreiben
probeLive(it.name).then((isLive) => {
if (isLive || apiLive) {
const pill = a.querySelector('[data-role="live-pill"]');
if (pill) {
pill.className = 'pill live';
pill.textContent = 'LIVE';
}
}
});
}); });
} }
filter.addEventListener('input', ()=>{/* re-render mit letztem snapshot */} // Filter rendert jetzt wirklich neu (vorher: leerer Handler)
); filter.addEventListener('input', () => render(last));
let last = {items:[]}; let last = { items: [] };
// Erst-Load (falls Server SSE erst später schickt)
(async function initialFetch() {
try {
const r = await fetch('/api/streams', { cache: 'no-store' });
if (r.ok) {
last = await r.json();
render(last);
}
} catch (_) {}
})();
// Live-Updates via SSE (wie gehabt)
const es = new EventSource('/api/streams/events', { withCredentials: false }); const es = new EventSource('/api/streams/events', { withCredentials: false });
es.addEventListener('update', (ev)=>{ es.addEventListener('update', (ev) => {
try { try {
last = JSON.parse(ev.data); last = JSON.parse(ev.data);
render(last); render(last);
} catch(e) { console.warn('sse parse', e); } } catch (e) {
console.warn('sse parse', e);
}
}); });
es.onerror = (e)=>console.warn('sse error', e); es.onerror = (e) => console.warn('sse error', e);
// Optionaler Reload-Button: // Optionaler Reload-Button:
const btn = document.getElementById('reload'); const btn = document.getElementById('reload');
if (btn) btn.addEventListener('click', ()=>render(last)); if (btn) btn.addEventListener('click', () => render(last));
})(); })();