/** * RTSP to WebSocket Proxy Server * Convert RTSP stream ke WebSocket untuk jsmpeg player * * Usage: * node rtsp-websocket-proxy.js * * Example: * node rtsp-websocket-proxy.js rtsp://10.60.0.10:8554/cam1 8082 */ const WebSocket = require('ws'); const { spawn } = require('child_process'); // Parse arguments const rtspUrl = process.argv[2] || 'rtsp://10.60.0.10:8554/cam1'; const wsPort = parseInt(process.argv[3]) || 8082; console.log('šŸš€ Starting RTSP to WebSocket Proxy'); console.log('šŸ“¹ RTSP URL:', rtspUrl); console.log('šŸ”Œ WebSocket Port:', wsPort); console.log(''); // Create WebSocket server const wss = new WebSocket.Server({ port: wsPort, perMessageDeflate: false }); let ffmpegProcess = null; let clients = new Set(); wss.on('connection', (ws) => { console.log('āœ… New WebSocket client connected'); clients.add(ws); // Start FFmpeg process jika belum ada if (!ffmpegProcess) { startFFmpeg(); } ws.on('close', () => { console.log('āŒ WebSocket client disconnected'); clients.delete(ws); // Stop FFmpeg jika tidak ada client lagi if (clients.size === 0 && ffmpegProcess) { console.log('ā¹ļø No clients, stopping FFmpeg'); stopFFmpeg(); } }); ws.on('error', (error) => { console.error('āŒ WebSocket error:', error.message); }); }); function startFFmpeg() { console.log('šŸŽ¬ Starting FFmpeg process...'); // FFmpeg command untuk convert RTSP ke MPEG1 video stream // Format: MPEG1 video (untuk jsmpeg) dengan resolusi 800x600, bitrate 1000k const ffmpegArgs = [ '-i', rtspUrl, // Input RTSP URL '-f', 'mpegts', // Output format: MPEG Transport Stream '-codec:v', 'mpeg1video', // Video codec: MPEG1 (untuk jsmpeg) '-s', '800x600', // Resolution '-b:v', '1000k', // Video bitrate '-bf', '0', // No B-frames '-codec:a', 'mp2', // Audio codec: MP2 '-b:a', '128k', // Audio bitrate '-r', '25', // Frame rate: 25 fps 'pipe:1' // Output to stdout ]; ffmpegProcess = spawn('ffmpeg', ffmpegArgs, { stdio: ['ignore', 'pipe', 'pipe'] }); ffmpegProcess.stdout.on('data', (data) => { // Broadcast ke semua connected clients clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { try { client.send(data); } catch (error) { console.error('Error sending data to client:', error.message); } } }); }); ffmpegProcess.stderr.on('data', (data) => { // FFmpeg logs ke stderr, bisa di-ignore atau di-log untuk debugging const message = data.toString(); if (message.includes('error') || message.includes('Error')) { console.error('FFmpeg error:', message); } }); ffmpegProcess.on('exit', (code, signal) => { console.log(`āš ļø FFmpeg process exited with code ${code}, signal ${signal}`); ffmpegProcess = null; // Restart jika masih ada clients if (clients.size > 0) { console.log('šŸ”„ Restarting FFmpeg...'); setTimeout(() => startFFmpeg(), 2000); } }); ffmpegProcess.on('error', (error) => { console.error('āŒ FFmpeg spawn error:', error.message); console.error('šŸ’” Make sure FFmpeg is installed and available in PATH'); ffmpegProcess = null; }); console.log('āœ… FFmpeg process started'); } function stopFFmpeg() { if (ffmpegProcess) { console.log('ā¹ļø Stopping FFmpeg process...'); ffmpegProcess.kill('SIGTERM'); ffmpegProcess = null; } } // Graceful shutdown process.on('SIGINT', () => { console.log('\nšŸ›‘ Shutting down...'); stopFFmpeg(); wss.close(() => { console.log('āœ… Server closed'); process.exit(0); }); }); process.on('SIGTERM', () => { console.log('\nšŸ›‘ Shutting down...'); stopFFmpeg(); wss.close(() => { console.log('āœ… Server closed'); process.exit(0); }); }); console.log(`āœ… WebSocket server listening on ws://0.0.0.0:${wsPort}`); console.log('šŸ’” Connect from browser: ws://localhost:' + wsPort); console.log('šŸ’” Press Ctrl+C to stop\n');