init commit

This commit is contained in:
2025-10-09 09:57:24 +09:00
commit 4d551bd74f
6636 changed files with 1218703 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
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(`
<!DOCTYPE html>
<html>
<head>
<title>VideoReader Global Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.status { background: #f0f0f0; padding: 20px; border-radius: 5px; }
.channel { margin: 10px 0; padding: 10px; background: #e8f4fd; border-radius: 3px; }
</style>
</head>
<body>
<h1>🌐 VideoReader Global Server</h1>
<div class="status">
<h2>Server Status</h2>
<p><strong>Status:</strong> Running</p>
<p><strong>Signaling Port:</strong> ${PORT}</p>
<p><strong>Media Port:</strong> ${MEDIA_PORT}</p>
<p><strong>Active Channels:</strong> <span id="channelCount">Loading...</span></p>
</div>
<h2>📡 Configuration</h2>
<pre>
{
"SignalingServer": "${req.get('host')}",
"DataPort": ${MEDIA_PORT},
"DefaultChannel": 10,
"FallbackIP": "127.0.0.1",
"UseSSL": false,
"ProfileName": "local"
}
</pre>
<h2>📊 Active Channels</h2>
<div id="channels">Loading...</div>
<script>
async function updateStatus() {
try {
const response = await fetch('/api/channels');
const channels = await response.json();
document.getElementById('channelCount').textContent = channels.length;
const channelsDiv = document.getElementById('channels');
channelsDiv.innerHTML = channels.length === 0
? '<p>No active channels</p>'
: channels.map(ch =>
\`<div class="channel">
<strong>Channel \${ch.channel}:</strong>
\${ch.connections} connections
<small>(created: \${new Date(ch.created).toLocaleString()})</small>
</div>\`
).join('');
} catch (e) {
console.error('Failed to update status:', e);
}
}
updateStatus();
setInterval(updateStatus, 5000);
</script>
</body>
</html>
`);
});
// 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 };