75 lines
3.6 KiB
JavaScript
75 lines
3.6 KiB
JavaScript
'use strict';
|
||
|
||
const express = require('express');
|
||
const http = require('http');
|
||
const path = require('path');
|
||
const { CameraSwitch } = require('./src/cameraSwitch');
|
||
const { detectDevices } = require('./src/deviceDetect');
|
||
const { createSnapshotRouter, createStreamRouter } = require('./src/snapshotService');
|
||
|
||
const PORT = parseInt(process.env.PORT ?? '8444', 10);
|
||
const LIVE_SIZE = process.env.LIVE_SIZE ?? '640x480';
|
||
const LIVE_FPS = parseInt(process.env.LIVE_FPS ?? '30', 10);
|
||
const HIRES_SIZE = process.env.HIRES_SIZE ?? '1280x960';
|
||
const HIRES_FPS = parseInt(process.env.HIRES_FPS ?? '15', 10);
|
||
const ENCODE_MODE = process.env.ENCODE_MODE ?? 'copybsf'; // 'copybsf' (niedrige CPU) | 'mjpeg' (Re-Encode-Fallback)
|
||
const ON_DEMAND = (process.env.ON_DEMAND ?? 'true') !== 'false'; // Live nur bei Verbrauchern (spart idle-CPU)
|
||
const IDLE_GRACE_MS = parseInt(process.env.IDLE_GRACE_MS ?? '15000', 10);
|
||
|
||
// ── Kameras: cam0 = erstes Gerät, cam1 = zweites … (DEV0/DEV1-Env überschreibt) ─
|
||
const devices = detectDevices();
|
||
const switches = {};
|
||
devices.forEach((device, i) => {
|
||
const id = `cam${i}`;
|
||
switches[id] = new CameraSwitch({ id, device, liveSize: LIVE_SIZE, liveFps: LIVE_FPS, hiresSize: HIRES_SIZE, hiresFps: HIRES_FPS, encode: ENCODE_MODE, onDemand: ON_DEMAND, idleGraceMs: IDLE_GRACE_MS });
|
||
});
|
||
|
||
const app = express();
|
||
|
||
// ── 1. Eigene Endpunkte ───────────────────────────────────────────────────────
|
||
app.use('/api/snapshot', createSnapshotRouter(switches));
|
||
app.use('/api/stream', createStreamRouter(switches));
|
||
|
||
app.get('/health', (_req, res) => {
|
||
res.json({
|
||
status: 'ok',
|
||
cameras: Object.values(switches).map((sw) => ({
|
||
id: sw.id, device: sw.device, state: sw.state, hasFrame: !!sw.latest,
|
||
})),
|
||
});
|
||
});
|
||
|
||
app.get('/config.json', (_req, res) => {
|
||
res.json({ cameras: Object.keys(switches) });
|
||
});
|
||
|
||
// ── 2. Statische Dateien ──────────────────────────────────────────────────────
|
||
// no-cache: Browser MUSS index.html/viewer.js vor Nutzung revalidieren.
|
||
app.use(express.static(path.join(__dirname, 'public'), {
|
||
setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'),
|
||
}));
|
||
|
||
// ── 3. Start ──────────────────────────────────────────────────────────────────
|
||
const server = http.createServer(app);
|
||
|
||
server.listen(PORT, '0.0.0.0', () => {
|
||
console.log(`AppRobotWebcam http://0.0.0.0:${PORT}`);
|
||
console.log(` Kameras: ${Object.entries(switches).map(([id, sw]) => `${id}=${sw.device}`).join(', ')}`);
|
||
console.log(` Live: ${LIVE_SIZE}@${LIVE_FPS} · HD-Grab: ${HIRES_SIZE}@${HIRES_FPS} · Encode: ${ENCODE_MODE} · On-Demand: ${ON_DEMAND}`);
|
||
console.log(` Viewer: http://0.0.0.0:${PORT}/`);
|
||
console.log(` Live-Stream: http://0.0.0.0:${PORT}/api/stream/cam0`);
|
||
console.log(` Snapshot API: http://0.0.0.0:${PORT}/api/snapshot/cam0 (+ /hires)`);
|
||
|
||
// Live-Producer starten (Dauerbetrieb)
|
||
Object.values(switches).forEach((sw) => sw.start());
|
||
});
|
||
|
||
const shutdown = (sig) => {
|
||
console.log(`\n${sig} – shutting down`);
|
||
Object.values(switches).forEach((sw) => { sw.stopping = true; if (sw.proc) { try { sw.proc.kill('SIGKILL'); } catch (_e) {} } });
|
||
server.close(() => process.exit(0));
|
||
setTimeout(() => process.exit(0), 3000); // Sicherheitsnetz
|
||
};
|
||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|