'use strict'; // ── Architektur ─────────────────────────────────────────────────────────────── // Der Server (Node) besitzt die Kameras und liefert den Live-Stream als // MJPEG multipart/x-mixed-replace unter /api/stream/. Der Browser rendert // das nativ in einem . KEIN WebRTC, KEIN go2rtc, kein Transcode. // // HD-Snapshot: GET /api/snapshot//hires. Der Server-Schalter pausiert dafür // den Live-FFmpeg kurz (~1–2 s), greift 1280×960, schaltet zurück. Der - // Stream friert in dieser Zeit ein und läuft danach weiter – kein Client-Handling // nötig (das war früher die Fehlerquelle). const P = '[WebcamViewer]'; const log = (c, m) => console.log(`${P}[${c}] ${m}`); const warn = (c, m) => console.warn(`${P}[${c}] ⚠ ${m}`); const logErr = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? ''); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const cameras = []; // { id, box, img, infoEl, toggleBtn, hdBtn, active, busy } // ── Live-Stream an/aus ──────────────────────────────────────────────────────── function startStream(cam) { cam.active = true; // Cache-Buster erzwingt eine frische Verbindung (sonst hängt Reconnect manchmal) cam.img.src = `/api/stream/${encodeURIComponent(cam.id)}?t=${Date.now()}`; cam.toggleBtn.textContent = '⏸'; cam.toggleBtn.title = 'Stream ausschalten'; setInfo(cam, 'verbindet…', ''); log(cam.id, 'Live an'); } function stopStream(cam) { cam.active = false; cam.img.removeAttribute('src'); // schließt die multipart-Verbindung cam.toggleBtn.textContent = '▶'; cam.toggleBtn.title = 'Stream einschalten'; setInfo(cam, 'aus', ''); log(cam.id, 'Live aus'); } // ── H.264-Live über MSE ───────────────────────────────────────────────────── // Der Server liefert unter /api/stream/ ein fortlaufendes fragmentiertes MP4 // (Init-Segment zuerst, dann Fragmente). Wir lesen den Body als ReadableStream // und speisen die Bytes der Reihe nach in einen SourceBuffer →