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

This commit is contained in:
2025-09-21 19:56:14 +02:00
parent 9faa53e149
commit 3fd8408605

View File

@@ -5,6 +5,8 @@
const viewersEl = document.getElementById('viewers'); const viewersEl = document.getElementById('viewers');
const srcEl = document.getElementById('hlssrc'); const srcEl = document.getElementById('hlssrc');
let playerLive = false; // <- neue Quelle der Wahrheit
function setLive(on){ function setLive(on){
liveEl.className = 'pill ' + (on ? 'live' : 'off'); liveEl.className = 'pill ' + (on ? 'live' : 'off');
liveEl.textContent = on ? 'LIVE' : 'Offline'; liveEl.textContent = on ? 'LIVE' : 'Offline';
@@ -14,71 +16,84 @@
const enc = encodeURIComponent(name); const enc = encodeURIComponent(name);
const candidates = [ const candidates = [
`/hls/${enc}/index.m3u8`, `/hls/${enc}/index.m3u8`,
`/hls/${enc}/main_stream.m3u8`, // Fallback, wenn es keinen Master gibt `/hls/${enc}/main_stream.m3u8`,
]; ];
for (const url of candidates) { for (const url of candidates) {
try { try { const r = await fetch(url, { cache: 'no-store' }); if (r.ok) return url; } catch {}
const r = await fetch(url, { cache: 'no-store' });
if (r.ok) return url; // WICHTIG: GET statt HEAD
} catch (_) {}
} }
return null; return null;
} }
async function initPlayer() { async function initPlayer() {
const url = await chooseManifest(); const url = await chooseManifest();
if (!url) { setLive(false); return false; } if (!url) { playerLive = false; setLive(false); return false; }
srcEl.textContent = url; srcEl.textContent = url;
setLive(true);
try { v.muted = true; v.playsInline = true; } catch(_) {} try { v.muted = true; v.playsInline = true; } catch {}
if (window.Hls && Hls.isSupported()) { if (window.Hls && Hls.isSupported()) {
if (!window._hls) { if (!window._hls) {
window._hls = new Hls({ liveDurationInfinity: true }); window._hls = new Hls({ liveDurationInfinity: true });
window._hls.on(Hls.Events.MANIFEST_PARSED, (_e, data) => { window._hls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log('HLS manifest parsed. levels=', data.levels?.length); // Manifest geladen → sehr gutes Live-Signal
playerLive = true;
setLive(true);
}); });
window._hls.on(Hls.Events.ERROR, (_e, data) => { window._hls.on(Hls.Events.ERROR, (_e, data) => {
console.warn('hls.js error', data); console.warn('hls.js error', data);
// fatale Fehler → als offline markieren
if (data?.fatal) { playerLive = false; setLive(false); }
}); });
window._hls.attachMedia(v); window._hls.attachMedia(v);
} }
window._hls.loadSource(url); window._hls.loadSource(url);
} else if (v.canPlayType('application/vnd.apple.mpegurl')) { } else if (v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = url; // Safari / iOS v.src = url; // Safari/iOS
} else { } else {
console.warn('HLS nicht unterstützt'); console.warn('HLS nicht unterstützt');
playerLive = false; setLive(false);
return false; return false;
} }
try { await v.play(); } catch(e){ console.log('autoplay blockiert (ok):', e?.name||e); } try { await v.play(); } catch(e){ /* Autoplay blockiert ist ok */ }
return true; return true;
} }
// Zusätzliche Player-Signale
v.addEventListener('loadedmetadata', () => { playerLive = true; setLive(true); });
v.addEventListener('playing', () => { playerLive = true; setLive(true); });
v.addEventListener('error', () => { playerLive = false; setLive(false); });
async function refreshMeta(){ async function refreshMeta(){
// API nur als Zusatz überschreibt NIE ein „echtes“ playerLive=true
try { try {
const r = await fetch('/api/streams', { cache: 'no-store' }); const r = await fetch('/api/streams', { cache:'no-store' });
if (!r.ok) return; if (!r.ok) return;
const d = await r.json(); const d = await r.json();
const it = d.items.find(x => x.name === name); const it = d.items.find(x => x.name === name);
if (it) { if (!it) return;
setLive(!!it.live);
viewersEl.textContent = 'Zuschauer: ' + it.viewers;
}
} catch (_) {}
}
v.addEventListener('error', e => console.warn('video error', e)); const apiLive = !!it.live;
v.addEventListener('loadedmetadata', () => console.log('loadedmetadata')); const combinedLive = playerLive || apiLive;
v.addEventListener('playing', () => console.log('playing')); setLive(combinedLive);
let viewers = it.viewers ?? 0;
if (combinedLive && viewers === 0) {
// Wir sehen selbst Video → mindestens 1 Betrachter
viewers = '≥1';
}
viewersEl.textContent = 'Zuschauer: ' + viewers;
} catch (_) {
// Bei API-Fehler nichts überschreiben
}
}
(async function boot(){ (async function boot(){
for (let i=0; i<10; i++){ for (let i=0; i<10; i++){
const ok = await initPlayer(); const ok = await initPlayer();
if (ok) break; if (ok) break;
await new Promise(r => setTimeout(r, 1500)); await new Promise(r => setTimeout(r, 1200));
} }
refreshMeta(); refreshMeta();
setInterval(refreshMeta, 2500); setInterval(refreshMeta, 2500);