'use strict'; const express = require('express'); const http = require('http'); const { WebSocketServer } = require('ws'); const path = require('path'); const { detectDevices } = require('./src/deviceDetect'); const { VideoStream } = require('./src/videoStream'); const { createSnapshotRouter } = require('./src/snapshotService'); const PORT = parseInt(process.env.PORT ?? '8080', 10); function buildStreams() { const devices = detectDevices(); console.log(`Detected ${devices.length} camera(s):`, devices); return devices.map((device, idx) => new VideoStream(device, { name: `cam${idx}`, width: parseInt(process.env[`CAM${idx}_WIDTH`] ?? '640', 10), height: parseInt(process.env[`CAM${idx}_HEIGHT`] ?? '480', 10), fps: parseInt(process.env[`CAM${idx}_FPS`] ?? '30', 10), quality: parseInt(process.env[`CAM${idx}_QUALITY`] ?? '5', 10), })); } function main() { const streams = buildStreams(); // Start all streams eagerly – first client gets immediate frame streams.forEach(s => s.start()); const app = express(); app.use(express.static(path.join(__dirname, 'public'))); app.use('/api/snapshot', createSnapshotRouter(streams)); app.get('/health', (_req, res) => { res.json({ status: 'ok', cameras: streams.map((s, i) => ({ id: `cam${i}`, device: s.device, running: s.isRunning, clients: s.clientCount, hasFrame: s.latestFrame !== null, })), }); }); const server = http.createServer(app); const wss = new WebSocketServer({ noServer: true }); server.on('upgrade', (req, socket, head) => { const match = req.url?.match(/^\/ws\/cam(\d+)$/); if (!match) { socket.destroy(); return; } const idx = parseInt(match[1], 10); if (!streams[idx]) { socket.destroy(); return; } wss.handleUpgrade(req, socket, head, (ws) => { const stream = streams[idx]; stream.addClient(ws); ws.on('close', () => stream.removeClient(ws)); ws.on('error', () => stream.removeClient(ws)); }); }); server.listen(PORT, '0.0.0.0', () => { console.log(`AppRobotWebcam on http://0.0.0.0:${PORT}`); streams.forEach((s, i) => { console.log(` ws://0.0.0.0:${PORT}/ws/cam${i} → ${s.device}`); console.log(` http://0.0.0.0:${PORT}/api/snapshot/cam${i}`); }); }); const shutdown = (signal) => { console.log(`\n${signal} received – shutting down`); streams.forEach(s => s.stop()); server.close(() => process.exit(0)); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } main();