'use strict'; const express = require('express'); const http = require('http'); const path = require('path'); const { createProxyMiddleware } = require('http-proxy-middleware'); const { createSnapshotRouter } = require('./src/snapshotService'); const PORT = parseInt(process.env.PORT ?? '8444', 10); const GO2RTC_URL = process.env.GO2RTC_URL ?? 'http://localhost:1984'; const GO2RTC_PORT = parseInt(process.env.GO2RTC_PORT ?? '1984', 10); const app = express(); // ── 1. Eigene Endpunkte (vor dem Proxy registrieren) ───────────────────────── app.use('/api/snapshot', createSnapshotRouter(GO2RTC_URL)); app.get('/health', async (_req, res) => { try { const r = await fetch(`${GO2RTC_URL}/api/streams`); const streams = r.ok ? await r.json() : {}; res.json({ status: r.ok ? 'ok' : 'degraded', cameras: Object.keys(streams) }); } catch (err) { res.status(503).json({ status: 'down', error: err.message }); } }); app.get('/config.json', (_req, res) => { res.json({ go2rtcPort: GO2RTC_PORT }); }); // ── 2. HTTP-Proxy zu go2rtc ─────────────────────────────────────────────────── const go2rtcProxy = createProxyMiddleware({ target: GO2RTC_URL, changeOrigin: true, pathFilter: ['/api', '/video-rtc.js', '/video-stream.js'], logger: console, on: { error: (err, _req, res) => { console.error('[HPM] proxy error:', err.message); if (!res.headersSent) res.status(502).json({ error: 'go2rtc nicht erreichbar' }); }, }, }); app.use(go2rtcProxy); // ── 3. Statische Dateien ────────────────────────────────────────────────────── app.use(express.static(path.join(__dirname, 'public'))); // ── 4. go2rtc Stream-Monitor (server-seitiges Logging) ─────────────────────── // Pollt alle 5 s go2rtc /api/streams und loggt Änderungen. // Sichtbar im Portainer-Log von AppRobotWebcam. // Logt: Producer-Starts/-Stops, Consumer-Anzahl, Timeouts/Restarts. // // go2rtc /api/streams liefert z.B.: // { "cam0": { "producers": [{"url":"...","state":"running"}], "consumers": [...] } } // const STREAM_POLL_MS = 5000; let prevStreamState = {}; async function pollGo2rtcStreams() { try { const r = await fetch(`${GO2RTC_URL}/api/streams`); if (!r.ok) { console.warn(`[monitor] /api/streams → HTTP ${r.status}`); return; } const streams = await r.json(); for (const [name, data] of Object.entries(streams)) { const producers = data.producers ?? []; const consumers = data.consumers ?? []; const nConsumers = consumers.length; const prev = prevStreamState[name] ?? {}; // Producer-Status for (let i = 0; i < producers.length; i++) { const p = producers[i]; const state = p.state ?? 'unknown'; const key = `${name}.p${i}`; const pPrev = prevStreamState[key]; if (pPrev !== state) { if (state === 'running') console.log(`[monitor][${name}] producer #${i} LÄUFT (${p.url ?? ''})`); if (state === 'error') console.error(`[monitor][${name}] producer #${i} FEHLER (${p.url ?? ''})`); if (state === 'stop') console.warn(`[monitor][${name}] producer #${i} GESTOPPT`); if (!['running','error','stop'].includes(state)) console.log(`[monitor][${name}] producer #${i} state="${state}"`); prevStreamState[key] = state; } } // Consumer-Anzahl — nur loggen wenn sie sich ändert if (prev.nConsumers !== nConsumers) { console.log(`[monitor][${name}] consumers: ${prev.nConsumers ?? '?'} → ${nConsumers}`); prevStreamState[name] = { ...prev, nConsumers }; } } // Streams die verschwunden sind (Timeout/Restart) for (const name of Object.keys(prevStreamState)) { if (name.includes('.')) continue; // skip producer-state keys if (!streams[name]) { console.warn(`[monitor][${name}] Stream verschwunden aus go2rtc`); delete prevStreamState[name]; } } } catch (err) { console.error('[monitor] go2rtc nicht erreichbar:', err.message); } } // ── Start ───────────────────────────────────────────────────────────────────── const server = http.createServer(app); server.listen(PORT, '0.0.0.0', () => { console.log(`AppRobotWebcam http://0.0.0.0:${PORT}`); console.log(` go2rtc HTTP: ${GO2RTC_URL}`); console.log(` go2rtc WS: ws://[host]:${GO2RTC_PORT} (Browser verbindet direkt)`); console.log(` Viewer: http://0.0.0.0:${PORT}/`); console.log(` Snapshot API: http://0.0.0.0:${PORT}/api/snapshot/cam0`); console.log(` Stream-Monitor: alle ${STREAM_POLL_MS / 1000}s → Portainer-Log`); // Ersten Poll nach 3 s (go2rtc braucht einen Moment zum Starten) setTimeout(() => { pollGo2rtcStreams(); setInterval(pollGo2rtcStreams, STREAM_POLL_MS); }, 3000); }); const shutdown = (sig) => { console.log(`\n${sig} – shutting down`); server.close(() => process.exit(0)); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM'));