diff --git a/doc/01_WebcamRoadmap.md b/doc/01_WebcamRoadmap.md index a61d288..0da420b 100644 --- a/doc/01_WebcamRoadmap.md +++ b/doc/01_WebcamRoadmap.md @@ -65,11 +65,11 @@ Encoding, ICE-Negotiation, robuster Client mit Auto-Fallback (WebRTC→MSE→MJP - [x] Stabile Snapshot-API `/api/snapshot/cam{n}` - [x] Auflösung fest 640×480 → Latenz „akzeptabel" (war vorher das Hauptproblem) -### Phase 3 – Latenz final tunen (offen) -- [ ] Messvergleich WebRTC ⟷ MJPEG durchführen → siehe `03_Protocoll_roadmap.md` -- [ ] Falls nötig: Auflösung 320×240 testen (kleiner = weniger Browser-Last) -- [ ] Falls nötig: Keyframe-Intervall senken (`-g 15`), zerolatency-Tuning -- [ ] Prüfen ob Kamera natives H.264 liefert (`v4l2-ctl --list-formats`) → kein Re-Encode +### Phase 3 – Latenz final tunen ✅ +- [x] Messvergleich WebRTC ⟷ MJPEG: **WebRTC ~130 ms, MJPEG ~200 ms** → WebRTC gewinnt +- [x] Entscheid: bei WebRTC bleiben (niedrigere Latenz + besser für Internet) +- [ ] Optional: Prüfen ob Kamera natives H.264 liefert (`v4l2-ctl --list-formats`) → kein Re-Encode +- [ ] Optional: Keyframe-Intervall / Encoder-Preset tunen wenn <100 ms gefordert ### Phase 4 – Internet-Härtung (offen, vor Produktiv-Schaltung) - [ ] **TLS via Caddy** – empfohlen, weil Caddy WebSocket-Proxy nativ und zuverlässig kann. diff --git a/doc/02_Measurements.md b/doc/02_Measurements.md deleted file mode 100644 index e69de29..0000000 diff --git a/doc/03_Protocoll_roadmap.md b/doc/03_Protocoll_roadmap.md index dac9044..5a5c14b 100644 --- a/doc/03_Protocoll_roadmap.md +++ b/doc/03_Protocoll_roadmap.md @@ -56,12 +56,15 @@ direkt im Browser öffnen (für cam1: `cam0` → `cam1` ersetzen): gleichzeitig zeigt (Handy + Monitor zusammen abfotografieren ist am einfachsten). 4. Differenz „echte Zeit ↔ Bild im Stream" ablesen = Gesamt-Latenz pro Protokoll. -### Ergebnis-Tabelle (später ausfüllen) +### Ergebnis-Tabelle ✅ gemessen 2026-06-03 -| Kamera | MJPEG roh | WebRTC | MSE | Sieger | -|--------|-----------|--------|-----|--------| -| cam0 | ? ms | ? ms | ? ms | ? | -| cam1 | ? ms | ? ms | ? ms | ? | +Methode: Handy-Stoppuhr (ms) vor Kamera, Foto von Monitor + Stoppuhr. + +| Kamera | MJPEG | WebRTC | MSE | Sieger | +|--------|-------|--------|-----|--------| +| cam0+1 | ~200 ms | ~130 ms | — | **WebRTC** | + +MSE nicht gemessen (erwartet schlechter als beide, für Live nicht relevant). --- diff --git a/public/index.html b/public/index.html index 6c674b0..5afeb5d 100644 --- a/public/index.html +++ b/public/index.html @@ -13,13 +13,21 @@ padding: 10px 16px; background: #1a1a1a; border-bottom: 1px solid #333; } h1 { font-size: 1rem; font-weight: normal; letter-spacing: 0.05em; } - #statusText { font-size: 0.8rem; color: #888; margin-left: auto; } + #statusText { font-size: 0.8rem; color: #888; } + + #snapAllBtn { + margin-left: auto; + background: #2a4a2a; color: #8f8; border: 1px solid #4a8; + padding: 5px 14px; font-family: monospace; font-size: 0.82rem; + cursor: pointer; border-radius: 3px; letter-spacing: 0.03em; + } + #snapAllBtn:hover:not(:disabled) { background: #3a6a3a; } + #snapAllBtn:disabled { opacity: 0.4; cursor: default; } #cameras { display: flex; flex-wrap: wrap; gap: 12px; padding: 12px; } .cam-box { position: relative; background: #000; border: 1px solid #2a2a2a; } - /* go2rtc Web-Component */ video-stream { display: block; width: 640px; height: 480px; background: #111; } video-stream video { width: 100%; height: 100%; object-fit: contain; } @@ -28,23 +36,17 @@ background: rgba(0,0,0,.65); padding: 2px 7px; border-radius: 3px; font-size: 0.72rem; color: #ccc; } - .cam-actions { position: absolute; top: 5px; right: 8px; display: flex; gap: 4px; } - .cam-actions button { - background: rgba(0,0,0,.65); color: #ccc; border: 1px solid #444; - padding: 2px 8px; font-family: monospace; font-size: 0.7rem; - cursor: pointer; border-radius: 3px; - } - .cam-actions button:hover { background: rgba(60,60,60,.8); }

AppRobotWebcam

Verbinde... +
+
- diff --git a/public/viewer.js b/public/viewer.js index ddc3e73..3dd8541 100644 --- a/public/viewer.js +++ b/public/viewer.js @@ -3,16 +3,31 @@ // go2rtc Player-Modi – Fallback-Reihenfolge: WebRTC → MSE → MJPEG const MODE = 'webrtc,mse,mjpeg'; -// ── Logging (sichtbar in Browser DevTools → Console → F12) ────────────────── +// ── 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 err = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? ''); +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) { - // 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}`); @@ -22,16 +37,15 @@ function buildCamera(camId, go2rtcPort, container) { const stream = document.createElement('video-stream'); stream.mode = MODE; - // Events vom inneren