Files
appRobotWebcam/public/viewer.js
2026-06-03 20:56:11 +02:00

119 lines
4.5 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.
'use strict';
// go2rtc Player-Modi Fallback-Reihenfolge: WebRTC → MSE → MJPEG
// Schlägt WebRTC fehl, springt der go2rtc-Player automatisch weiter → kein schwarzes Bild.
const MODE = 'webrtc,mse,mjpeg';
// ── Logging-Hilfe (sichtbar in Browser DevTools → Console) ──────────────────
const LOG_PREFIX = '[AppRobotWebcam]';
function log(cam, msg) { console.log(`${LOG_PREFIX}[${cam}] ${msg}`); }
function warn(cam, msg) { console.warn(`${LOG_PREFIX}[${cam}] ⚠ ${msg}`); }
function logErr(cam, msg, e) { console.error(`${LOG_PREFIX}[${cam}] ✗ ${msg}`, e ?? ''); }
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
function buildCamera(camId, container) {
const wsUrl = `/api/ws?src=${encodeURIComponent(camId)}`;
log(camId, `View erstellt mode=${MODE} ws=${wsUrl}`);
const box = document.createElement('div');
box.className = 'cam-box';
// go2rtc Web-Component: verbindet sich via WebSocket zu /api/ws (→ Node-Proxy → go2rtc)
const stream = document.createElement('video-stream');
stream.mode = MODE;
// Events vom inneren <video>-Element abfangen (capture-Phase = vor dem Element selbst)
stream.addEventListener('play', () => log(camId, '▶ spielt'), true);
stream.addEventListener('playing', () => log(camId, '▶ Bild läuft'), true);
stream.addEventListener('pause', () => warn(camId, 'pausiert'), true);
stream.addEventListener('stalled', () => warn(camId, 'stalled (kein Daten)'), true);
stream.addEventListener('waiting', () => warn(camId, 'waiting (Buffer leer)'), true);
stream.addEventListener('error', (e) => logErr(camId, 'Video-Fehler', e), true);
// src setzen startet die Verbindung
log(camId, `Verbinde → ${wsUrl}`);
stream.src = wsUrl;
box.appendChild(stream);
const label = document.createElement('div');
label.className = 'cam-label';
label.textContent = camId;
box.appendChild(label);
const actions = document.createElement('div');
actions.className = 'cam-actions';
const snapBtn = document.createElement('button');
snapBtn.textContent = 'Snapshot';
snapBtn.onclick = () => {
log(camId, 'Snapshot download → /api/snapshot/' + camId);
const a = document.createElement('a');
a.href = `/api/snapshot/${camId}`;
a.download = `${camId}_${Date.now()}.jpg`;
a.click();
};
actions.appendChild(snapBtn);
box.appendChild(actions);
container.appendChild(box);
// Connectivity-Check nach 5 s: ist die WebSocket-Verbindung zu /api/ws nutzbar?
setTimeout(async () => {
try {
const r = await fetch('/api/streams');
if (r.ok) {
const d = await r.json();
log(camId, `go2rtc streams: ${Object.keys(d).join(', ')}`);
} else {
warn(camId, `/api/streams → HTTP ${r.status}`);
}
} catch (err) {
logErr(camId, '/api/streams nicht erreichbar (Proxy defekt?)', err);
}
}, 5000);
}
// ── Init ─────────────────────────────────────────────────────────────────────
async function init() {
log('init', 'Starte...');
// go2rtc Web-Component muss geladen sein bevor .src gesetzt wird
try {
await customElements.whenDefined('video-stream');
log('init', '<video-stream> definiert');
} catch (err) {
logErr('init', '<video-stream> nicht geladen /video-stream.js erreichbar?', err);
return;
}
const container = document.getElementById('cameras');
const statusText = document.getElementById('statusText');
// Kamera-Liste von go2rtc (via Node-Proxy /api/snapshot → /api/streams)
let cams = [];
try {
const r = await fetch('/api/snapshot');
log('init', `/api/snapshot → HTTP ${r.status}`);
const d = await r.json();
if (Array.isArray(d.cameras) && d.cameras.length) {
cams = d.cameras.map(c => c.id);
log('init', `Kameras: ${cams.join(', ')}`);
} else {
warn('init', 'Keine Kameras in go2rtc Config OK? go2rtc läuft?');
}
} catch (err) {
logErr('init', '/api/snapshot nicht erreichbar Fallback cam0/cam1', err);
}
if (cams.length === 0) {
warn('init', 'Fallback: nehme cam0 und cam1 an');
cams = ['cam0', 'cam1'];
}
cams.forEach(id => buildCamera(id, container));
statusText.textContent = `${cams.length} Kamera${cams.length !== 1 ? 's' : ''} · WebRTC`;
log('init', 'Fertig');
}
init();