const express = require('express'); const net = require('net'); const fs = require('fs'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3000; const MEDIA_PORT = process.env.MEDIA_PORT || 5000; // Логирование const log = (message) => { console.log(`[${new Date().toISOString()}] ${message}`); }; // Хранилище активных каналов const activeChannels = new Map(); // Middleware app.use(express.json()); app.use((req, res, next) => { log(`${req.method} ${req.url} from ${req.ip}`); next(); }); // CORS для всех доменов (в продакшене настроить конкретные домены) app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); next(); }); // Маршрут для получения IP медиа-сервера (совместимость с оригинальным API) app.get('/get-ip-kr.php', (req, res) => { const port = req.query.port; log(`IP request for channel: ${port}`); // В простой реализации возвращаем IP этого же сервера // В продакшене здесь может быть логика балансировки const mediaServerIP = process.env.MEDIA_SERVER_IP || '127.0.0.1'; // Возвращаем IP в бинарном формате (4 байта) как в оригинале const ipParts = mediaServerIP.split('.').map(n => parseInt(n)); const buffer = Buffer.from(ipParts); res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': buffer.length }); res.end(buffer); log(`Returned IP: ${mediaServerIP} for channel ${port}`); }); // REST API для управления app.get('/api/status', (req, res) => { res.json({ status: 'running', activeChannels: activeChannels.size, uptime: process.uptime(), channels: Array.from(activeChannels.keys()) }); }); app.get('/api/channels', (req, res) => { const channelList = Array.from(activeChannels.entries()).map(([channel, data]) => ({ channel, connections: data.connections, created: data.created })); res.json(channelList); }); // Веб-интерфейс app.get('/', (req, res) => { res.send(` VideoReader Global Server

🌐 VideoReader Global Server

Server Status

Status: Running

Signaling Port: ${PORT}

Media Port: ${MEDIA_PORT}

Active Channels: Loading...

📡 Configuration

{
  "SignalingServer": "${req.get('host')}",
  "DataPort": ${MEDIA_PORT},
  "DefaultChannel": 10,
  "FallbackIP": "127.0.0.1",
  "UseSSL": false,
  "ProfileName": "local"
}
            

📊 Active Channels

Loading...
`); }); // TCP медиа-сервер const mediaServer = net.createServer((socket) => { let channel = null; let deviceType = null; log(`New connection from ${socket.remoteAddress}:${socket.remotePort}`); socket.on('data', (data) => { if (channel === null && data.length >= 2) { // Первые 2 байта: тип устройства (0=receiver, 1=sender) и канал deviceType = data[0]; channel = data[1]; log(`Device connected - Type: ${deviceType === 0 ? 'receiver' : 'sender'}, Channel: ${channel}`); // Регистрируем канал if (!activeChannels.has(channel)) { activeChannels.set(channel, { connections: 0, created: new Date().toISOString(), receivers: [], senders: [] }); } const channelData = activeChannels.get(channel); channelData.connections++; if (deviceType === 0) { channelData.receivers.push(socket); } else { channelData.senders.push(socket); } // Обработка данных после установки канала if (data.length > 2) { handleMediaData(socket, data.slice(2), channel, deviceType); } } else if (channel !== null) { // Передача медиа-данных handleMediaData(socket, data, channel, deviceType); } }); socket.on('close', () => { if (channel !== null) { const channelData = activeChannels.get(channel); if (channelData) { channelData.connections--; // Удаляем сокет из соответствующего массива if (deviceType === 0) { channelData.receivers = channelData.receivers.filter(s => s !== socket); } else { channelData.senders = channelData.senders.filter(s => s !== socket); } // Удаляем канал если нет подключений if (channelData.connections <= 0) { activeChannels.delete(channel); log(`Channel ${channel} removed - no active connections`); } } } log(`Connection closed from ${socket.remoteAddress}:${socket.remotePort}`); }); socket.on('error', (err) => { log(`Socket error: ${err.message}`); }); }); function handleMediaData(fromSocket, data, channel, fromType) { const channelData = activeChannels.get(channel); if (!channelData) return; // Пересылаем данные от отправителей к получателям и наоборот const targetSockets = fromType === 0 ? channelData.senders : channelData.receivers; targetSockets.forEach(socket => { if (socket !== fromSocket && socket.writable) { try { socket.write(data); } catch (err) { log(`Error forwarding data: ${err.message}`); } } }); } // Запуск серверов app.listen(PORT, () => { log(`🌐 Signaling server running on port ${PORT}`); log(`📡 Web interface: http://localhost:${PORT}`); }); mediaServer.listen(MEDIA_PORT, () => { log(`📺 Media server running on port ${MEDIA_PORT}`); }); // Graceful shutdown process.on('SIGINT', () => { log('Shutting down servers...'); mediaServer.close(); process.exit(0); }); // Экспорт для возможности тестирования module.exports = { app, mediaServer };