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

116 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 (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 logErr = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? '');
// ── Snapshot aller Kameras gleichzeitig ──────────────────────────────────────
// Auflösung = was go2rtc im MJPEG-Stream hält (aktuell 640×480 gemäss Config).
// Für höhere Auflösung: in go2rtc.yaml einen separaten Hi-Res-Stream definieren
// und hier auf dessen /api/frame.jpeg?src=cam0_hires zeigen.
function snapshotAll(camIds) {
const ts = Date.now();
log('snap', `Snapshot alle Kameras: ${camIds.join(', ')}`);
camIds.forEach(id => {
const a = document.createElement('a');
a.href = `/api/snapshot/${id}`;
a.download = `${id}_${ts}.jpg`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
}
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
function buildCamera(camId, go2rtcPort, container) {
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;
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) => logErr(camId, 'Video-Fehler', e), true);
log(camId, `Verbinde WebSocket → ${wsUrl}`);
stream.src = wsUrl;
box.appendChild(stream);
const label = document.createElement('div');
label.className = 'cam-label';
label.textContent = camId;
box.appendChild(label);
container.appendChild(box);
}
// ── Init ─────────────────────────────────────────────────────────────────────
async function init() {
log('init', 'Starte...');
let go2rtcPort = 1984;
try {
const r = await fetch('/config.json');
const d = await r.json();
go2rtcPort = d.go2rtcPort ?? 1984;
log('init', `go2rtc WS-Port: ${go2rtcPort}`);
} catch (e) {
warn('init', `Konnte /config.json nicht laden, nehme Port ${go2rtcPort}`);
}
try {
await customElements.whenDefined('video-stream');
log('init', '<video-stream> definiert');
} catch (e) {
logErr('init', '<video-stream> nicht geladen /video-stream.js erreichbar?', e);
return;
}
const container = document.getElementById('cameras');
const statusText = document.getElementById('statusText');
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) {
logErr('init', '/api/snapshot Fehler Fallback', e);
}
if (cams.length === 0) {
warn('init', 'Fallback auf cam0, cam1');
cams = ['cam0', 'cam1'];
}
// Globaler Snapshot-Button in der Header-Bar verdrahten
const snapAllBtn = document.getElementById('snapAllBtn');
if (snapAllBtn) {
snapAllBtn.onclick = () => snapshotAll(cams);
snapAllBtn.disabled = false;
}
cams.forEach(id => buildCamera(id, go2rtcPort, container));
statusText.textContent = `${cams.length} Kamera${cams.length !== 1 ? 's' : ''} · WebRTC`;
log('init', 'Fertig');
}
init();