roadmap Arbeiten
This commit is contained in:
219
doc/05_screenShot_roadmap.md
Normal file
219
doc/05_screenShot_roadmap.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# AppRobotWebcam – Hi-Res-Snapshot via Consumer-Umhängen
|
||||||
|
|
||||||
|
> Status: **Konzept, phasenweise testbar.** Noch nicht umgesetzt.
|
||||||
|
> Vorgeschichte & gescheiterte Ansätze: siehe `04_Delay_roadmap.md` (Abschnitt
|
||||||
|
> „KONSOLIDIERT"). Diese Datei beschreibt den Ansatz, der die dort dokumentierten
|
||||||
|
> Fehler **strukturell** umgeht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Grundidee
|
||||||
|
|
||||||
|
Das Kernproblem aller bisherigen Versuche: **Eine USB-Kamera lässt sich nur einmal
|
||||||
|
öffnen**, und der Live-Viewer zwingt go2rtc, das Gerät zu halten (per on-demand-
|
||||||
|
Reconnect). Jeder Versuch, go2rtc das Gerät zu *entreißen* oder die Live-Quelle zur
|
||||||
|
Laufzeit *umzuschalten*, ist gescheitert (device-busy, bzw. `PATCH` hängt an → 107 %).
|
||||||
|
|
||||||
|
**Neuer Ansatz – das Problem umdrehen:** Nicht das Gerät dem Stream entreißen, sondern
|
||||||
|
**die Zuschauer vom Live-Stream wegziehen.** Hat `cam0` keine Zuschauer mehr, stoppt
|
||||||
|
go2rtc den Producer von selbst (on-demand) und gibt das Gerät frei. Dann kann ein
|
||||||
|
separater Hi-Res-Stream es kurz für sich haben.
|
||||||
|
|
||||||
|
### Warum das sicher ist (im Gegensatz zu allem vorher)
|
||||||
|
|
||||||
|
- **`cam0` wird nie verändert.** Kein `PATCH`/`PUT`/`DELETE` auf den Live-Stream.
|
||||||
|
Das Append-Problem (107 %) kann nicht auftreten.
|
||||||
|
- **Zur Laufzeit nur LESENDE go2rtc-Aufrufe**: `GET /api/streams`, `GET /api/frame.jpeg`.
|
||||||
|
Die einzige „schreibende" Änderung ist das Hinzufügen von `cam0_hires` in der Config
|
||||||
|
(per Redeploy, nicht zur Laufzeit).
|
||||||
|
- **Kleiner Schadensradius**: Geht etwas schief, ist die Erholung „Browser wieder auf
|
||||||
|
`cam0` hängen" → go2rtc startet `cam0` neu. Keine kaputte Stream-Definition, die bis
|
||||||
|
zum Neustart hängt.
|
||||||
|
|
||||||
|
Damit respektiert der Ansatz die eisernen Regeln aus `04_*` (Snapshot-Pfad read-only,
|
||||||
|
keine Laufzeit-Mutation von cam0/cam1).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
| Stream | Quelle | on-demand | Zweck |
|
||||||
|
|--------|--------|-----------|-------|
|
||||||
|
| `cam0` | Kamera @640 | ja | **unverändert** – Live-Stream |
|
||||||
|
| `cam1` | Kamera @640 | ja | **unverändert** – Live-Stream |
|
||||||
|
| `cam0_hires` | Kamera @1280×960 | ja | **nur** für den Hi-Res-Grab (Phase 2) |
|
||||||
|
| `cam1_hires` | Kamera @1280×960 | ja | dito für cam1 |
|
||||||
|
|
||||||
|
**Platzhalter = rein clientseitig**, kein go2rtc-Stream nötig:
|
||||||
|
Der Browser friert beim Umhängen den zuletzt gezeigten Live-Frame auf einem `<canvas>`
|
||||||
|
ein und blendet „HD Image Work" ein (ca. 30 % der Bildgröße, unten rechts). Das ist
|
||||||
|
das, was während des Grabs zu sehen ist. Vorteil: keine zusätzliche go2rtc-Last, kein
|
||||||
|
Nachschieben von Bildern in go2rtc.
|
||||||
|
|
||||||
|
> Alternative (nur falls *mehrere* Zuschauer gleichzeitig denselben Platzhalter sehen
|
||||||
|
> sollen): ein echter go2rtc-`standbild0`-Stream aus einer statischen Bilddatei. Mehr
|
||||||
|
> Aufwand, hier zunächst nicht nötig.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ziel-Ablauf (vollständig, Phase 1 + 2)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Browser hängt um: cam0 → Canvas-Standbild ("HD Image Work") [clientseitig]
|
||||||
|
2. cam0 hat 0 Zuschauer → go2rtc stoppt cam0-Producer → Gerät frei
|
||||||
|
3. [Pause ~4s] ← Wert ist FREI WÄHLBAR, wird aus Phase-1-Messung gesetzt
|
||||||
|
4. Node holt 1 Frame von cam0_hires → go2rtc öffnet Gerät @1280 → frame.jpeg
|
||||||
|
(Breite ≥1000px prüfen, sonst retry; Warmup beachten – s.u.)
|
||||||
|
5. cam0_hires-Consumer endet → Gerät frei [Pause ~4s]
|
||||||
|
6. Browser hängt zurück: Canvas → cam0 → Live @640 wieder da
|
||||||
|
```
|
||||||
|
|
||||||
|
Blackout auf **cam0** ~8–10 s (mit 4s-Pausen), cam1 unberührt. Für „alle 30 s,
|
||||||
|
Button-getriggert, Blackout ok" passt das. Die Pausen sind großzügig gewählt; sobald
|
||||||
|
Phase 1 die echte Freigabe-Zeit liefert, können sie gekürzt werden.
|
||||||
|
|
||||||
|
> **Die 4s sind ein Startwert, keine feste Größe.** Phase 1 misst, wie schnell go2rtc
|
||||||
|
> das Gerät wirklich freigibt → daraus wird der reale Pausenwert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Der eine Dreh- und Angelpunkt (= warum Phase 1 zuerst kommt)
|
||||||
|
|
||||||
|
Der ganze Ansatz steht und fällt mit **einer** Annahme:
|
||||||
|
|
||||||
|
> **Gibt go2rtc das Gerät frei, wenn `cam0` den letzten Zuschauer verliert — und wie
|
||||||
|
> schnell?**
|
||||||
|
|
||||||
|
go2rtc *kann* einen Producer nach dem letzten Consumer „warm" halten statt ihn sofort
|
||||||
|
zu stoppen. Tut es das, bleibt das Gerät belegt und `cam0_hires` läuft auf „device
|
||||||
|
busy". **Diese Zahl wird in Phase 1 gemessen, bevor irgendein Hi-Res-Grab gebaut wird.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PHASE 1 — Freigabe verifizieren (kein Grab, voll reversibel)
|
||||||
|
|
||||||
|
**Ziel:** Beweisen, dass Schritt 1 → 2 → 6 funktioniert und das Gerät tatsächlich
|
||||||
|
(und wie schnell) frei wird. Kein `cam0_hires`, kein 1280-Zugriff. Risiko ~null:
|
||||||
|
im schlimmsten Fall ist es ein Reconnect von cam0.
|
||||||
|
|
||||||
|
### Umzusetzen
|
||||||
|
|
||||||
|
**A. Viewer (`public/viewer.js`)** – Button „Hi-Res-Test (Phase 1)":
|
||||||
|
1. Aktuellen `cam0`-Frame auf ein `<canvas>` zeichnen, „HD Image Work" einblenden,
|
||||||
|
Canvas anstelle des `<video-stream>` zeigen.
|
||||||
|
2. `<video-stream>` für cam0 **entfernen/stoppen** (das ist das „Umhängen" – cam0
|
||||||
|
verliert seinen Consumer).
|
||||||
|
3. `GET /api/snapshot/cam0/release-test` aufrufen (neuer Node-Endpunkt, s.u.).
|
||||||
|
4. Auf Antwort: `<video-stream>` für cam0 wieder einsetzen (Live zurück), Canvas weg.
|
||||||
|
|
||||||
|
**B. Node (`src/snapshotService.js`)** – neuer read-only Endpunkt
|
||||||
|
`GET /api/snapshot/:id/release-test`:
|
||||||
|
1. Startzeit loggen.
|
||||||
|
2. `GET ${go2rtc}/api/streams` alle 200 ms pollen (max. ~10 s).
|
||||||
|
3. Loggen:
|
||||||
|
- Wann erreicht `cam0` **0 Consumer**?
|
||||||
|
- Wann ist der `cam0`-**Producer gestoppt** (Feld `producers` leer bzw. `state` ≠
|
||||||
|
`running`) → das ist der Proxy für „Gerät frei".
|
||||||
|
- Dauer von „0 Consumer" → „Producer gestoppt" in ms.
|
||||||
|
4. Ergebnis ins Log schreiben **und** als JSON zurückgeben, z. B.:
|
||||||
|
```json
|
||||||
|
{ "freed": true, "msUntilFree": 1700, "samples": [...] }
|
||||||
|
```
|
||||||
|
5. Kein Schreibzugriff auf go2rtc. Nur Lesen.
|
||||||
|
|
||||||
|
### Erfolgskriterium Phase 1
|
||||||
|
- Log/JSON zeigt `freed: true` und eine **konkrete** `msUntilFree`.
|
||||||
|
- Nach dem Test (Schritt 6) zeigt cam0 wieder normal Live (~50 % CPU, stabil).
|
||||||
|
|
||||||
|
### Was wir daraus lernen
|
||||||
|
- `msUntilFree` → der reale Pausenwert für Schritt 3/5 (statt der geratenen 4 s).
|
||||||
|
- Wird der Producer **nicht** gestoppt (`freed: false`): go2rtc hält das Gerät warm →
|
||||||
|
Ansatz so nicht tragfähig → prüfen, ob ein go2rtc-Setting das Verhalten ändert,
|
||||||
|
sonst zurück zu Weg A (separate Kamera, siehe `04_*`).
|
||||||
|
|
||||||
|
> ⚠ Die genaue JSON-Form von `/api/streams` (Felder `producers`/`consumers`/`state`)
|
||||||
|
> vor dem Bauen kurz an der echten Instanz ansehen (`curl -s localhost:1984/api/streams`)
|
||||||
|
> und den Parser danach ausrichten — nicht annehmen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PHASE 2 — Den Hi-Res-Grab ergänzen (Schritt 4 + 5)
|
||||||
|
|
||||||
|
Nur starten, **wenn Phase 1 `freed: true` geliefert hat.**
|
||||||
|
|
||||||
|
### Vorbereitung (Config, per Redeploy – nicht zur Laufzeit)
|
||||||
|
`docker-compose.yaml`, go2rtc-`streams` ergänzen:
|
||||||
|
```yaml
|
||||||
|
cam0_hires: "ffmpeg:device?video=/dev/video0&input_format=mjpeg&video_size=1280x960&framerate=15#video=mjpeg"
|
||||||
|
cam1_hires: "ffmpeg:device?video=/dev/video2&input_format=mjpeg&video_size=1280x960&framerate=15#video=mjpeg"
|
||||||
|
```
|
||||||
|
- `#video=mjpeg` (Re-Encode) ist ok – läuft nur ~1–2 s pro Grab. (`#video=copy` ist
|
||||||
|
laut `04_*` auf dieser Kamera tot.)
|
||||||
|
- **Präzondition prüfen:** Nach Redeploy via `/api/streams` bestätigen, dass
|
||||||
|
`cam0_hires` **dormant** ist (kein laufender Producer, solange niemand es anfragt).
|
||||||
|
Sonst würde es beim Start das Gerät greifen und mit `cam0` kollidieren.
|
||||||
|
|
||||||
|
### Node-Endpunkt `GET /api/snapshot/:id/hires` (Phase-2-Variante)
|
||||||
|
Voraussetzung: der **Client hat cam0 bereits losgelassen** (Browser-Dance wie Phase 1).
|
||||||
|
Ablauf im Endpunkt:
|
||||||
|
1. Mutex setzen (kein paralleler Grab).
|
||||||
|
2. (optional) via `/api/streams` verifizieren: `cam0` hat 0 Consumer → sonst abbrechen
|
||||||
|
(Gerät noch belegt).
|
||||||
|
3. `sleep(msUntilFree)` – Gerät freigeben lassen.
|
||||||
|
4. **Grab mit Warmup** (robuste Variante):
|
||||||
|
- Kurz `cam0_hires` als Stream konsumieren (z. B. `GET /api/stream.mjpeg?src=cam0_hires`)
|
||||||
|
für ~1,5 s, damit die Kamera-Belichtung einschwingt und der Producer warm bleibt.
|
||||||
|
- Den **letzten** Frame behalten, der `Breite ≥ 1000 px` **und** nicht „zu klein/
|
||||||
|
schwarz" ist (Warmup-Schutz, vgl. das frühere 1-KB-Schwarzbild).
|
||||||
|
- Einfachere Alternative: `GET /api/frame.jpeg?src=cam0_hires` mit Retry (mehrfach,
|
||||||
|
bis Breite ≥1000 px und plausible Größe).
|
||||||
|
5. Consumer von `cam0_hires` beenden → Gerät frei.
|
||||||
|
6. Mutex lösen. JPEG (1280×960) zurückgeben.
|
||||||
|
|
||||||
|
**Client** nach Antwort: Canvas weg, `<video-stream>` cam0 wieder einsetzen (Schritt 6).
|
||||||
|
|
||||||
|
### Robustheit (Pflicht)
|
||||||
|
- **`finally`/Recovery:** Egal was schiefgeht – der Client MUSS am Ende wieder auf `cam0`
|
||||||
|
hängen. Da `cam0` nie verändert wurde, reicht „wieder anhängen" zur vollen Erholung.
|
||||||
|
- **Timeout** auf den Grab (z. B. 8 s) → sonst Fehler + Recovery.
|
||||||
|
- **Mutex**: nie zwei Grabs gleichzeitig (würde zwei 1280-Producer = Gerätekonflikt
|
||||||
|
provozieren).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Platzhalter-Detail (Canvas „HD Image Work")
|
||||||
|
|
||||||
|
- Beim Umhängen den aktuellen Frame des `<video>`-Elements per
|
||||||
|
`canvasCtx.drawImage(video, …)` einfrieren.
|
||||||
|
- Text „HD Image Work" unten rechts, ca. 30 % der Bildbreite, mit halbtransparentem
|
||||||
|
Hintergrund (Lesbarkeit).
|
||||||
|
- Canvas über/anstelle des gestoppten `<video-stream>` zeigen.
|
||||||
|
- Nach Schritt 6 Canvas entfernen.
|
||||||
|
- Rein clientseitig – go2rtc sieht davon nichts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offene Punkte / Risiken (ehrlich)
|
||||||
|
|
||||||
|
| Punkt | Status | Umgang |
|
||||||
|
|-------|--------|--------|
|
||||||
|
| **Gibt go2rtc das Gerät frei + wie schnell?** | **ungeklärt – Linchpin** | **Phase 1 misst es.** Erst danach Phase 2. |
|
||||||
|
| Warmup-Schwarzbild bei 1280 | bekannt | kurz konsumieren + Breiten/Größen-Check + Retry |
|
||||||
|
| Mehrere gleichzeitige Zuschauer | Einschränkung | Gerät wird nur frei, wenn **alle** cam0 loslassen. Für 1 Operator + Button ok; Multi-Client bräuchte ein Broadcast-Signal „alle auf Platzhalter". |
|
||||||
|
| `cam0_hires` greift Gerät schon beim Start? | zu prüfen | nach Redeploy via `/api/streams` bestätigen, dass es dormant ist |
|
||||||
|
| Fehler mitten in der Sequenz | beherrschbar | `finally` → Client immer zurück auf cam0; Worst case go2rtc-Restart, cam0-Definition bleibt heil |
|
||||||
|
| Orchestrierung Client↔Server | Komplexität | klare Reihenfolge: Client löst cam0 → ruft Endpunkt → Endpunkt wartet+grabt → Client hängt zurück |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Käme das so hin? — kurz
|
||||||
|
|
||||||
|
**Ja.** Der Ablauf 1–6 mit anpassbaren Pausen ist tragfähig, **wenn** der Linchpin
|
||||||
|
(Geräte-Freigabe nach Consumer-Verlust) hält — und genau das beweist Phase 1, ohne
|
||||||
|
cam0 anzufassen und ohne einen einzigen schreibenden go2rtc-Aufruf. Zwei Vereinfachungen
|
||||||
|
gegenüber der ersten Skizze: der Platzhalter ist clientseitig (kein eigener Stream),
|
||||||
|
und zur Laufzeit wird go2rtc nur **gelesen**, nie verändert.
|
||||||
|
|
||||||
|
**Reihenfolge:** Phase 1 (messen, ~null Risiko) → Pausen aus der Messung setzen →
|
||||||
|
Phase 2 (Grab). Fällt Phase 1, bleibt Weg A (separate Kamera) aus `04_*` der sichere
|
||||||
|
Fallback.
|
||||||
179
doc/06_portForwarding_roadmap.md
Normal file
179
doc/06_portForwarding_roadmap.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# AppRobotWebcam – Port-Forwarding / HTTPS-Proxy Roadmap
|
||||||
|
|
||||||
|
> Ziel: Die Viewer-Webseite über **einen** HTTPS-Reverse-Proxy nach aussen geben,
|
||||||
|
> ohne go2rtc oder interne Ports ins Internet zu hängen.
|
||||||
|
> Stand: 2026-06-04 · noch **nicht umgesetzt** (Plan zum Abarbeiten).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR — welche Ports nach aussen?
|
||||||
|
|
||||||
|
| Port | Dienst | Internet? | Begründung |
|
||||||
|
|------|--------|-----------|------------|
|
||||||
|
| **443** | HTTPS-Reverse-Proxy | ✅ **ja – einziger Internet-Port** | TLS-Terminierung + einziger Einstieg |
|
||||||
|
| **8444** | Node.js (Webseite, `/api`, Snapshots, Stream-WS) | nur Proxy→Backend (LAN/localhost) | Proxy leitet hierhin weiter; **nicht** ins Internet |
|
||||||
|
| 1984 | go2rtc API / WebSocket / Debug-UI | ❌ nein | bleibt intern (localhost) |
|
||||||
|
| 8555/udp | go2rtc WebRTC-Media | ❌ nein | im aktuellen **MJPEG-Modus ungenutzt** (siehe Caveat unten) |
|
||||||
|
|
||||||
|
**Merksatz:** Nach Umsetzung dieser Roadmap ist der einzige offene Port `443` am Proxy.
|
||||||
|
Alles andere läuft über genau eine Origin (`https://<host>`) → Proxy → `8444`.
|
||||||
|
|
||||||
|
> `network_mode: host`: Die Container binden direkt an Host-Ports — es gibt **kein**
|
||||||
|
> Docker-`ports:`-Mapping. „Offen/zu" steuerst du allein über die Host-Firewall.
|
||||||
|
> Läuft der Proxy auf demselben Host, muss `8444` gar nicht in der Firewall geöffnet
|
||||||
|
> werden (Proxy erreicht `127.0.0.1:8444`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aktueller Stand (was läuft)
|
||||||
|
|
||||||
|
- Live-Bild kommt als **MJPEG über WebSocket** (`MODE = 'mjpeg'`, `public/viewer.js:9`).
|
||||||
|
Die WebRTC-Kommentare im `docker-compose.yaml` beschreiben einen **anderen** Aufbau,
|
||||||
|
der nicht aktiv ist → UDP 8555 wird derzeit nicht gebraucht.
|
||||||
|
- Node.js (`8444`) liefert: Webseite, `/config.json`, `/health`, `/api/snapshot/*`
|
||||||
|
und proxied `/api`, `/video-rtc.js`, `/video-stream.js` per HTTP an go2rtc (`server.js:34`).
|
||||||
|
- Snapshots und Skripte laufen bereits **relativ/same-origin** über `8444` → proxy-tauglich.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Das Problem (warum es so noch nicht hinter HTTPS läuft)
|
||||||
|
|
||||||
|
Der Viewer baut die Stream-Verbindung aktuell **direkt** zu go2rtc auf — `public/viewer.js:36`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const wsUrl = `ws://${location.hostname}:${GO2RTC_PORT}/api/ws?src=...`; // GO2RTC_PORT = 1984
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinter einem HTTPS-Proxy scheitert das doppelt:
|
||||||
|
|
||||||
|
1. **Mixed Content** — eine `https://`-Seite darf kein unverschlüsseltes `ws://` öffnen
|
||||||
|
→ Browser blockt die Verbindung hart.
|
||||||
|
2. **Proxy-Umgehung** — die URL zeigt direkt auf Port `1984`, den wir bewusst nicht
|
||||||
|
exponieren (dort hängt auch die offene go2rtc-Debug-UI).
|
||||||
|
|
||||||
|
→ Folge: Seite lädt, aber **kein Live-Bild**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lösung — 3 Schritte
|
||||||
|
|
||||||
|
Danach geht der Stream über dieselbe Origin wie die Seite (`wss://<host>/api/ws`),
|
||||||
|
durch den Proxy, auf `8444`, intern weiter zu go2rtc `1984`.
|
||||||
|
|
||||||
|
### Schritt 1 — `public/viewer.js`: same-origin & protokoll-bewusste WS-URL
|
||||||
|
|
||||||
|
`startStream()`, aktuell `viewer.js:36`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ALT:
|
||||||
|
const wsUrl = `ws://${location.hostname}:${GO2RTC_PORT}/api/ws?src=${encodeURIComponent(cam.id)}`;
|
||||||
|
|
||||||
|
// NEU:
|
||||||
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const wsUrl = `${proto}//${location.host}/api/ws?src=${encodeURIComponent(cam.id)}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `location.host` enthält ggf. den Port → funktioniert **sowohl** direkt im LAN
|
||||||
|
(`http://host:8444` → `ws://host:8444/...`) **als auch** hinter HTTPS
|
||||||
|
(`https://cam.example.com` → `wss://cam.example.com/...`).
|
||||||
|
- `/api/ws` ist im Proxy-`pathFilter` bereits enthalten (`server.js:37`).
|
||||||
|
- `GO2RTC_PORT` / der `/config.json`-Fetch in `init()` werden für den Stream damit
|
||||||
|
**nicht mehr gebraucht** (dürfen als harmloser Toter Code bleiben oder raus).
|
||||||
|
|
||||||
|
### Schritt 2 — `server.js`: Proxy WebSockets durchreichen lassen
|
||||||
|
|
||||||
|
Im `createProxyMiddleware`-Block (`server.js:34`) `ws: true` ergänzen:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const go2rtcProxy = createProxyMiddleware({
|
||||||
|
target: GO2RTC_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true, // ← NEU
|
||||||
|
pathFilter: ['/api', '/video-rtc.js', '/video-stream.js'],
|
||||||
|
logger: console,
|
||||||
|
on: { /* … unverändert … */ },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Und nach dem Erstellen des HTTP-Servers (`server.js:113`) den Upgrade-Handler binden:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const server = http.createServer(app);
|
||||||
|
server.on('upgrade', go2rtcProxy.upgrade); // ← NEU (WebSocket-Upgrades an go2rtc)
|
||||||
|
```
|
||||||
|
|
||||||
|
> `http-proxy-middleware@^3` (vorhanden, `package.json`): `ws: true` **plus** der
|
||||||
|
> explizite `server.on('upgrade', …)` ist die dokumentierte, zuverlässige Kombination.
|
||||||
|
|
||||||
|
### Schritt 3 — Reverse-Proxy: WebSocket-Upgrade durchreichen
|
||||||
|
|
||||||
|
**nginx:**
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name cam.example.com;
|
||||||
|
# ssl_certificate … / ssl_certificate_key …;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8444;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade; # ← Pflicht für WS
|
||||||
|
proxy_set_header Connection "upgrade"; # ← Pflicht für WS
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 3600s; # WS-Stream offen halten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Synology DSM** (Systemsteuerung → Anmeldeportal → Erweitert → Reverse Proxy):
|
||||||
|
1. Regel anlegen: Quelle `HTTPS` / `cam.example.com` / `443` → Ziel `HTTP` / `localhost` / `8444`.
|
||||||
|
2. In der Regel → Reiter **Eigene Kopfzeile (Custom Header)** → **Erstellen → WebSocket**
|
||||||
|
(fügt `Upgrade`/`Connection` automatisch hinzu). **Ohne diesen Schritt kommt kein Bild.**
|
||||||
|
3. Optional Timeout erhöhen, damit der Stream nicht nach kurzer Zeit getrennt wird.
|
||||||
|
|
||||||
|
**Caddy** (zur Referenz — WS geht automatisch):
|
||||||
|
```caddy
|
||||||
|
cam.example.com {
|
||||||
|
reverse_proxy 127.0.0.1:8444
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifikation (nach Deploy abhaken)
|
||||||
|
|
||||||
|
- [ ] `https://cam.example.com/` lädt, Header + Kamera-Kacheln sichtbar.
|
||||||
|
- [ ] DevTools → Console: `[WebcamViewer][init] … <video-stream> definiert`, **kein** Mixed-Content-Fehler.
|
||||||
|
- [ ] DevTools → Network → Filter „WS": Eintrag `wss://cam.example.com/api/ws?src=cam0`
|
||||||
|
mit Status **101 Switching Protocols** (nicht 4xx/blocked).
|
||||||
|
- [ ] Live-Bild für cam0 **und** cam1 läuft; Status zeigt „MJPEG · live".
|
||||||
|
- [ ] „⬇ Snapshot alle" lädt JPG(s) herunter (`/api/snapshot/...` über Proxy).
|
||||||
|
- [ ] Von **aussen**: nur `443` erreichbar. `1984`, `8444`, `8555` aus dem Internet
|
||||||
|
**nicht** erreichbar (z. B. `curl https://<public>:1984` → Timeout/refused).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
Kleine, isolierte Änderung in genau zwei Dateien — risikoarm:
|
||||||
|
1. `public/viewer.js` und `server.js` auf den alten Stand zurück (git).
|
||||||
|
2. `docker restart AppRobotWebcam` (lädt Code neu).
|
||||||
|
|
||||||
|
Der Live-Stream im LAN funktioniert mit der neuen `viewer.js` weiterhin
|
||||||
|
(`ws://host:8444/...`), d. h. die Änderung ist auch ohne Proxy gefahrlos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caveat — falls je wieder WebRTC (MODE mit `webrtc`)
|
||||||
|
|
||||||
|
Wird `public/viewer.js` jemals zurück auf `MODE = 'webrtc,mse,mjpeg'` gestellt
|
||||||
|
(siehe `doc/04_Delay_roadmap.md`), ändert sich die Port-Lage:
|
||||||
|
|
||||||
|
- WebRTC-**Signaling** läuft weiter über die WebSocket (`/api/ws`) → durch den Proxy ok.
|
||||||
|
- WebRTC-**Media** läuft über **UDP 8555** und geht **nicht** durch einen HTTP(S)-Proxy.
|
||||||
|
Dann bräuchte man entweder UDP 8555 direkt erreichbar **oder** einen TURN-Server.
|
||||||
|
|
||||||
|
Für den aktuellen MJPEG-Betrieb ist das **irrelevant** — alles läuft über TCP/WS auf `8444`.
|
||||||
Reference in New Issue
Block a user