Files
appRobotWebcam/public/viewer.js
2026-06-03 21:07:46 +02:00

112 lines
4.3 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
const MODE = 'webrtc,mse,mjpeg';
// ── Logging (sichtbar in Browser DevTools → Console → F12) ──────────────────
const P = '[WebcamViewer]';
const log = (c, m) => console.log(`${P}[${c}] ${m}`);
const warn = (c, m) => console.warn(`${P}[${c}] ⚠ ${m}`);
const err = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? '');
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
function buildCamera(camId, go2rtcPort, container) {
// WebSocket direkt zu go2rtc kein Proxy-Zwischenschritt, garantiert stabil.
// Protokoll: ws:// auf LAN (http). Für Internet mit TLS wird aus ws: wss: (Caddy).
const wsUrl = `ws://${location.hostname}:${go2rtcPort}/api/ws?src=${encodeURIComponent(camId)}`;
log(camId, `View erstellt mode="${MODE}" ws=${wsUrl}`);
const box = document.createElement('div');
box.className = 'cam-box';
const stream = document.createElement('video-stream');
stream.mode = MODE;
// Events vom inneren <video>-Element (capture-Phase)
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 (keine Daten)'), true);
stream.addEventListener('waiting', () => warn(camId, 'waiting (Buffer leer)'), true);
stream.addEventListener('error', (e) => err(camId, 'Video-Fehler', e), true);
log(camId, `Verbinde WebSocket → ${wsUrl}`);
stream.src = wsUrl; // setzt den VideoRTC-src und startet die Verbindung
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 → /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);
}
// ── Init ─────────────────────────────────────────────────────────────────────
async function init() {
log('init', 'Starte...');
// go2rtc-Port vom Server holen (damit er nicht im Client hart kodiert ist)
let go2rtcPort = 1984;
try {
const r = await fetch('/config.json');
const d = await r.json();
go2rtcPort = d.go2rtcPort ?? 1984;
log('init', `go2rtc WebSocket-Port: ${go2rtcPort}`);
} catch (e) {
warn('init', `Konnte /config.json nicht laden, nehme Port ${go2rtcPort}`);
}
// go2rtc Web-Component muss geladen sein bevor .src gesetzt wird
try {
await customElements.whenDefined('video-stream');
log('init', '<video-stream> definiert');
} catch (e) {
err('init', '<video-stream> nicht geladen /video-stream.js erreichbar?', e);
return;
}
const container = document.getElementById('cameras');
const statusText = document.getElementById('statusText');
// Kamera-Liste von go2rtc via Node-Proxy
let cams = [];
try {
const r = await fetch('/api/snapshot');
log('init', `/api/snapshot → HTTP ${r.status}`);
if (r.ok) {
const d = await r.json();
cams = (d.cameras ?? []).map(c => c.id);
log('init', `Kameras: ${cams.join(', ') || '(keine)'}`);
}
} catch (e) {
err('init', '/api/snapshot Fehler Fallback', e);
}
if (cams.length === 0) {
warn('init', 'Fallback auf cam0, cam1');
cams = ['cam0', 'cam1'];
}
cams.forEach(id => buildCamera(id, go2rtcPort, container));
statusText.textContent = `${cams.length} Kamera${cams.length !== 1 ? 's' : ''} · WebRTC`;
log('init', 'Fertig');
}
init();