Files
nginx-stream-server/web/static/js/stream.js
jbergner a39e2aaade
All checks were successful
release-tag / release-image (push) Successful in 2m3s
test mit webevents
2025-09-21 20:56:22 +02:00

119 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
const name = document.documentElement.dataset.stream;
const v = document.getElementById('v');
const liveEl = document.getElementById('live');
const viewersEl = document.getElementById('viewers');
const srcEl = document.getElementById('hlssrc');
let playerLive = false; // <- neue Quelle der Wahrheit
function setLive(on){
liveEl.className = 'pill ' + (on ? 'live' : 'off');
liveEl.textContent = on ? 'LIVE' : 'Offline';
}
async function chooseManifest() {
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;
}
async function initPlayer() {
const url = await chooseManifest();
if (!url) { playerLive = false; setLive(false); return false; }
srcEl.textContent = url;
try { v.muted = true; v.playsInline = true; } catch {}
if (window.Hls && Hls.isSupported()) {
if (!window._hls) {
window._hls = new Hls({ liveDurationInfinity: true });
window._hls.on(Hls.Events.MANIFEST_PARSED, () => {
// Manifest geladen → sehr gutes Live-Signal
playerLive = true;
setLive(true);
});
window._hls.on(Hls.Events.ERROR, (_e, 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.loadSource(url);
} else if (v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = url; // Safari/iOS
} else {
console.warn('HLS nicht unterstützt');
playerLive = false; setLive(false);
return false;
}
try { await v.play(); } catch(e){ /* Autoplay blockiert ist ok */ }
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(){
// API nur als Zusatz überschreibt NIE ein „echtes“ playerLive=true
try {
const r = await fetch('/api/streams', { cache:'no-store' });
if (!r.ok) return;
const d = await r.json();
const it = d.items.find(x => x.name === name);
if (!it) return;
const apiLive = !!it.live;
const combinedLive = playerLive || apiLive;
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(){
for (let i=0; i<10; i++){
const ok = await initPlayer();
if (ok) break;
await new Promise(r => setTimeout(r, 1200));
}
refreshMeta();
//setInterval(refreshMeta, 2500);
})();
const es = new EventSource('/api/streams/events');
es.addEventListener('update', (ev)=>{
try {
const data = JSON.parse(ev.data);
const it = data.items.find(x=>x.name===name);
if (!it) return;
const apiLive = !!it.live;
const combinedLive = playerLive || apiLive;
setLive(combinedLive);
let viewers = it.viewers ?? 0;
if (combinedLive && viewers === 0) viewers = '≥1';
viewersEl.textContent = 'Zuschauer: ' + viewers;
} catch(e){ /* ignore */ }
});
})();