Files
2026-06-01 02:05:05 +08:00

884 lines
28 KiB
JavaScript

// Next.js 自定义服务器 + Socket.IO
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { Server } = require('socket.io');
const {
attachTVRemoteIO,
cleanupTVRemoteDevices,
clearTVRemoteHub,
registerTVRemoteDevice,
removeTVRemoteSocket,
updateTVRemoteDevice,
} = require('./src/lib/tv-remote-hub.js');
function shouldInitSQLite() {
const isCloudflare = process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
return process.env.NEXT_PUBLIC_STORAGE_TYPE === 'd1' && !isCloudflare && process.env.MOONTV_LITE !== 'true';
}
function isTVModeEnabled() {
return process.env.ENABLE_TV_MODE !== 'false';
}
function ensureSQLiteReady() {
if (!shouldInitSQLite()) {
return;
}
try {
const { initSQLiteDatabase } = require('./scripts/init-sqlite.js');
initSQLiteDatabase();
} catch (error) {
console.error('❌ Error initializing SQLite database:', error);
throw error;
}
}
ensureSQLiteReady();
const dev = process.env.NODE_ENV !== 'production';
const hostname = process.env.HOSTNAME || '0.0.0.0';
const port = parseInt(process.env.PORT || '3000', 10);
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
// 读取观影室配置的辅助函数
async function getWatchRoomConfig() {
// 观影室配置现在统一从环境变量读取
const config = {
enabled: process.env.WATCH_ROOM_ENABLED === 'true',
serverType: (process.env.WATCH_ROOM_SERVER_TYPE || 'internal'),
externalServerUrl: process.env.WATCH_ROOM_EXTERNAL_SERVER_URL,
externalServerAuth: process.env.WATCH_ROOM_EXTERNAL_SERVER_AUTH,
};
console.log(`[WatchRoom] Watch room ${config.enabled ? 'enabled' : 'disabled'} via environment variable.`);
return config;
}
// 观影室服务器类
class WatchRoomServer {
constructor(io) {
this.io = io;
this.rooms = new Map();
this.members = new Map();
this.socketToRoom = new Map();
this.screenHelpers = new Map();
this.helperToRoom = new Map();
this.roomDeletionTimers = new Map(); // 房间延迟删除定时器
this.cleanupInterval = null;
this.setupEventHandlers();
this.startCleanupTimer();
}
setupEventHandlers() {
this.io.on('connection', (socket) => {
console.log(`[WatchRoom] Client connected: ${socket.id}`);
// 创建房间
socket.on('room:create', (data, callback) => {
try {
const roomId = this.generateRoomId();
const userId = socket.id;
const ownerToken = this.generateRoomId(); // 生成房主令牌
const room = {
id: roomId,
name: data.name,
description: data.description,
password: data.password,
isPublic: data.isPublic,
roomType: data.roomType || 'sync',
ownerId: userId,
ownerName: data.userName,
ownerToken: ownerToken, // 保存房主令牌
memberCount: 1,
currentState: null,
createdAt: Date.now(),
lastOwnerHeartbeat: Date.now(),
};
const member = {
id: userId,
name: data.userName,
isOwner: true,
lastHeartbeat: Date.now(),
};
this.rooms.set(roomId, room);
this.members.set(roomId, new Map([[userId, member]]));
this.socketToRoom.set(socket.id, {
roomId,
userId,
userName: data.userName,
isOwner: true,
});
socket.join(roomId);
console.log(`[WatchRoom] Room created: ${roomId} by ${data.userName}`);
callback({ success: true, room });
} catch (error) {
console.error('[WatchRoom] Error creating room:', error);
callback({ success: false, error: '创建房间失败' });
}
});
// 加入房间
socket.on('room:join', (data, callback) => {
try {
const room = this.rooms.get(data.roomId);
if (!room) {
return callback({ success: false, error: '房间不存在' });
}
if (room.password && room.password !== data.password) {
return callback({ success: false, error: '密码错误' });
}
const userId = socket.id;
let isOwner = false;
// 检查是否是房主重连(通过 ownerToken 验证)
if (data.ownerToken && data.ownerToken === room.ownerToken) {
isOwner = true;
// 更新房主的 socket.id
room.ownerId = userId;
room.lastOwnerHeartbeat = Date.now();
this.rooms.set(data.roomId, room);
console.log(`[WatchRoom] Owner ${data.userName} reconnected to room ${data.roomId}`);
}
// 取消房间的删除定时器(如果有人重连)
if (this.roomDeletionTimers.has(data.roomId)) {
console.log(`[WatchRoom] Cancelling deletion timer for room ${data.roomId}`);
clearTimeout(this.roomDeletionTimers.get(data.roomId));
this.roomDeletionTimers.delete(data.roomId);
}
const member = {
id: userId,
name: data.userName,
isOwner: isOwner,
lastHeartbeat: Date.now(),
};
const roomMembers = this.members.get(data.roomId);
if (roomMembers) {
if (isOwner) {
Array.from(roomMembers.entries()).forEach(([memberId, existingMember]) => {
if (existingMember.isOwner && memberId !== userId) {
roomMembers.delete(memberId);
}
});
}
roomMembers.set(userId, member);
room.memberCount = roomMembers.size;
this.rooms.set(data.roomId, room);
}
this.socketToRoom.set(socket.id, {
roomId: data.roomId,
userId,
userName: data.userName,
isOwner: isOwner,
});
socket.join(data.roomId);
socket.to(data.roomId).emit('room:member-joined', member);
console.log(`[WatchRoom] User ${data.userName} joined room ${data.roomId}${isOwner ? ' (as owner)' : ''}`);
const members = Array.from(roomMembers?.values() || []);
callback({ success: true, room, members });
} catch (error) {
console.error('[WatchRoom] Error joining room:', error);
callback({ success: false, error: '加入房间失败' });
}
});
// 离开房间
socket.on('room:leave', () => {
this.handleLeaveRoom(socket);
});
// 获取房间列表
socket.on('room:list', (callback) => {
const publicRooms = Array.from(this.rooms.values()).filter((room) => room.isPublic);
callback(publicRooms);
});
// 播放状态更新(任何成员都可以触发同步)
socket.on('play:update', (state) => {
console.log(`[WatchRoom] Received play:update from ${socket.id}:`, state);
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) {
console.log('[WatchRoom] No room info for socket, ignoring play:update');
return;
}
const room = this.rooms.get(roomInfo.roomId);
if (room) {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
console.log(`[WatchRoom] Broadcasting play:update to room ${roomInfo.roomId} from ${roomInfo.userName}`);
socket.to(roomInfo.roomId).emit('play:update', state);
} else {
console.log('[WatchRoom] Room not found for play:update');
}
});
// 播放进度跳转
socket.on('play:seek', (currentTime) => {
console.log(`[WatchRoom] Received play:seek from ${socket.id}:`, currentTime);
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) {
console.log('[WatchRoom] No room info for socket, ignoring play:seek');
return;
}
console.log(`[WatchRoom] Broadcasting play:seek to room ${roomInfo.roomId}`);
socket.to(roomInfo.roomId).emit('play:seek', currentTime);
});
// 播放
socket.on('play:play', () => {
console.log(`[WatchRoom] Received play:play from ${socket.id}`);
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) {
console.log('[WatchRoom] No room info for socket, ignoring play:play');
return;
}
console.log(`[WatchRoom] Broadcasting play:play to room ${roomInfo.roomId}`);
socket.to(roomInfo.roomId).emit('play:play');
});
// 暂停
socket.on('play:pause', () => {
console.log(`[WatchRoom] Received play:pause from ${socket.id}`);
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) {
console.log('[WatchRoom] No room info for socket, ignoring play:pause');
return;
}
console.log(`[WatchRoom] Broadcasting play:pause to room ${roomInfo.roomId}`);
socket.to(roomInfo.roomId).emit('play:pause');
});
// 切换视频/集数
socket.on('play:change', (state) => {
console.log(`[WatchRoom] Received play:change from ${socket.id}:`, state);
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) {
console.log('[WatchRoom] No room info for socket, ignoring play:change');
return;
}
if (!roomInfo.isOwner) {
console.log('[WatchRoom] User is not owner, ignoring play:change');
return;
}
const room = this.rooms.get(roomInfo.roomId);
if (room) {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
console.log(`[WatchRoom] Broadcasting play:change to room ${roomInfo.roomId}`);
socket.to(roomInfo.roomId).emit('play:change', state);
} else {
console.log('[WatchRoom] Room not found for play:change');
}
});
// 切换直播频道
socket.on('live:change', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room) {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('live:change', state);
}
});
socket.on('music:change', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:change', state);
}
});
socket.on('music:update', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:update', state);
}
});
socket.on('music:queue', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = state;
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:queue', state);
}
});
socket.on('music:play', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = { ...state, isPlaying: true };
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:play', state);
}
});
socket.on('music:pause', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = { ...state, isPlaying: false };
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:pause', state);
}
});
socket.on('music:seek', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo || !roomInfo.isOwner) return;
const room = this.rooms.get(roomInfo.roomId);
if (room?.roomType === 'music') {
room.currentState = { ...state };
this.rooms.set(roomInfo.roomId, room);
socket.to(roomInfo.roomId).emit('music:seek', state);
}
});
socket.on('screen:helper-register', (data, callback) => {
try {
const room = this.rooms.get(data.roomId);
if (!room) {
callback({ success: false, error: '房间不存在' });
return;
}
if (room.ownerToken !== data.ownerToken) {
callback({ success: false, error: '房主身份验证失败' });
return;
}
const oldHelperSocketId = this.screenHelpers.get(data.roomId);
if (oldHelperSocketId && oldHelperSocketId !== socket.id) {
this.helperToRoom.delete(oldHelperSocketId);
}
this.screenHelpers.set(data.roomId, socket.id);
this.helperToRoom.set(socket.id, data.roomId);
callback({ success: true });
} catch (error) {
console.error('[WatchRoom] Error registering screen helper:', error);
callback({ success: false, error: '注册共享控制窗口失败' });
}
});
// 开始屏幕共享
socket.on('screen:start', (state) => {
const roomInfo = this.socketToRoom.get(socket.id);
const helperRoomId = this.helperToRoom.get(socket.id);
const roomId = roomInfo?.roomId || helperRoomId;
if (!roomId) return;
if (helperRoomId && this.screenHelpers.get(helperRoomId) !== socket.id) return;
if (roomInfo && !roomInfo.isOwner) return;
const room = this.rooms.get(roomId);
if (room) {
room.currentState = state;
this.rooms.set(roomId, room);
this.io.to(roomId).emit('screen:start', state);
}
});
// 停止屏幕共享
socket.on('screen:stop', () => {
const roomInfo = this.socketToRoom.get(socket.id);
const helperRoomId = this.helperToRoom.get(socket.id);
const roomId = roomInfo?.roomId || helperRoomId;
if (!roomId) return;
if (helperRoomId && this.screenHelpers.get(helperRoomId) !== socket.id) return;
if (roomInfo && !roomInfo.isOwner) return;
const room = this.rooms.get(roomId);
if (room) {
room.currentState = null;
this.rooms.set(roomId, room);
this.io.to(roomId).emit('screen:stop');
}
});
socket.on('screen:viewer-ready', () => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
const room = this.rooms.get(roomInfo.roomId);
if (!room || roomInfo.isOwner || room.currentState?.type !== 'screen') return;
const targetSocketId = this.screenHelpers.get(roomInfo.roomId) || room.ownerId;
this.io.to(targetSocketId).emit('screen:viewer-ready', {
userId: socket.id,
});
});
// 屏幕共享 WebRTC 信令
socket.on('screen:offer', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
const helperRoomId = this.helperToRoom.get(socket.id);
if (!roomInfo && !helperRoomId) return;
this.io.to(data.targetUserId).emit('screen:offer', {
userId: socket.id,
offer: data.offer,
});
});
socket.on('screen:answer', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
const helperRoomId = this.helperToRoom.get(socket.id);
if (!roomInfo && !helperRoomId) return;
this.io.to(data.targetUserId).emit('screen:answer', {
userId: socket.id,
answer: data.answer,
});
});
socket.on('screen:ice', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
const helperRoomId = this.helperToRoom.get(socket.id);
if (!roomInfo && !helperRoomId) return;
this.io.to(data.targetUserId).emit('screen:ice', {
userId: socket.id,
candidate: data.candidate,
});
});
// 聊天消息
socket.on('chat:message', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
const message = {
id: this.generateMessageId(),
userId: roomInfo.userId,
userName: roomInfo.userName,
content: data.content,
type: data.type,
timestamp: Date.now(),
};
this.io.to(roomInfo.roomId).emit('chat:message', message);
});
// WebRTC 信令
socket.on('voice:offer', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
this.io.to(data.targetUserId).emit('voice:offer', {
userId: socket.id,
offer: data.offer,
});
});
socket.on('voice:answer', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
this.io.to(data.targetUserId).emit('voice:answer', {
userId: socket.id,
answer: data.answer,
});
});
socket.on('voice:ice', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
this.io.to(data.targetUserId).emit('voice:ice', {
userId: socket.id,
candidate: data.candidate,
});
});
// 语音聊天 - 服务器中转音频数据
socket.on('voice:audio-chunk', (data) => {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
// 将音频数据转发给房间内的其他成员
socket.to(roomInfo.roomId).emit('voice:audio-chunk', {
userId: socket.id,
audioData: data.audioData,
sampleRate: data.sampleRate || 16000,
});
});
// 心跳
socket.on('heartbeat', () => {
const roomInfo = this.socketToRoom.get(socket.id);
// 如果用户在房间中,更新心跳时间
if (roomInfo) {
const roomMembers = this.members.get(roomInfo.roomId);
const member = roomMembers?.get(roomInfo.userId);
if (member) {
member.lastHeartbeat = Date.now();
roomMembers?.set(roomInfo.userId, member);
}
if (roomInfo.isOwner) {
const room = this.rooms.get(roomInfo.roomId);
if (room) {
room.lastOwnerHeartbeat = Date.now();
this.rooms.set(roomInfo.roomId, room);
}
}
}
// 无论是否在房间中,都响应心跳包(pong)
socket.emit('heartbeat:pong', { timestamp: Date.now() });
});
// 断开连接
socket.on('disconnect', () => {
console.log(`[WatchRoom] Client disconnected: ${socket.id}`);
const helperRoomId = this.helperToRoom.get(socket.id);
if (helperRoomId) {
this.helperToRoom.delete(socket.id);
if (this.screenHelpers.get(helperRoomId) === socket.id) {
this.screenHelpers.delete(helperRoomId);
const room = this.rooms.get(helperRoomId);
if (room && room.currentState?.type === 'screen') {
room.currentState = null;
this.rooms.set(helperRoomId, room);
this.io.to(helperRoomId).emit('screen:stop');
}
}
}
this.handleLeaveRoom(socket);
});
});
}
handleLeaveRoom(socket) {
const roomInfo = this.socketToRoom.get(socket.id);
if (!roomInfo) return;
const { roomId, userId, isOwner } = roomInfo;
const room = this.rooms.get(roomId);
const roomMembers = this.members.get(roomId);
if (roomMembers) {
roomMembers.delete(userId);
if (room) {
room.memberCount = roomMembers.size;
this.rooms.set(roomId, room);
}
socket.to(roomId).emit('room:member-left', userId);
// 如果是房主主动离开,解散房间并踢出所有成员
if (isOwner) {
console.log(`[WatchRoom] Owner actively left room ${roomId}, disbanding room`);
// 通知所有成员房间被解散
socket.to(roomId).emit('room:deleted', { reason: 'owner_left' });
// 强制所有成员离开房间
const members = Array.from(roomMembers.keys());
members.forEach(memberId => {
this.socketToRoom.delete(memberId);
});
// 立即删除房间(跳过通知,因为上面已经发送了)
this.deleteRoom(roomId, true);
// 清除可能存在的删除定时器
if (this.roomDeletionTimers.has(roomId)) {
clearTimeout(this.roomDeletionTimers.get(roomId));
this.roomDeletionTimers.delete(roomId);
}
} else {
// 普通成员离开,房间为空时延迟删除
if (roomMembers.size === 0) {
console.log(`[WatchRoom] Room ${roomId} is now empty, will delete in 30 seconds if no one rejoins`);
const deletionTimer = setTimeout(() => {
// 再次检查房间是否仍然为空
const currentRoomMembers = this.members.get(roomId);
if (currentRoomMembers && currentRoomMembers.size === 0) {
console.log(`[WatchRoom] Room ${roomId} deletion timer expired, deleting room`);
this.deleteRoom(roomId);
this.roomDeletionTimers.delete(roomId);
}
}, 30000); // 30秒后删除
this.roomDeletionTimers.set(roomId, deletionTimer);
}
}
}
socket.leave(roomId);
this.socketToRoom.delete(socket.id);
}
deleteRoom(roomId, skipNotify = false) {
console.log(`[WatchRoom] Deleting room ${roomId}`);
// 如果不跳过通知,则发送 room:deleted 事件
if (!skipNotify) {
this.io.to(roomId).emit('room:deleted');
}
this.rooms.delete(roomId);
this.members.delete(roomId);
const helperSocketId = this.screenHelpers.get(roomId);
if (helperSocketId) {
this.helperToRoom.delete(helperSocketId);
this.screenHelpers.delete(roomId);
}
}
startCleanupTimer() {
this.cleanupInterval = setInterval(() => {
const now = Date.now();
const deleteTimeout = 5 * 60 * 1000; // 5分钟 - 删除房间
const clearStateTimeout = 30 * 1000; // 30秒 - 清除播放状态
for (const [roomId, room] of this.rooms.entries()) {
const timeSinceHeartbeat = now - room.lastOwnerHeartbeat;
// 如果房主心跳超过30秒,清除播放状态
if (timeSinceHeartbeat > clearStateTimeout && room.currentState !== null) {
console.log(`[WatchRoom] Room ${roomId} owner inactive for 30s, clearing play state`);
room.currentState = null;
this.rooms.set(roomId, room);
// 通知房间内所有成员状态已清除
this.io.to(roomId).emit('state:cleared');
}
// 检查房主是否超时5分钟 - 删除房间
if (timeSinceHeartbeat > deleteTimeout) {
console.log(`[WatchRoom] Room ${roomId} owner timeout, deleting...`);
this.deleteRoom(roomId);
}
}
}, 10000); // 每10秒检查一次,确保更及时的清理
}
generateRoomId() {
return Math.random().toString(36).substring(2, 8).toUpperCase();
}
generateMessageId() {
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
// 清理所有房间删除定时器
for (const timer of this.roomDeletionTimers.values()) {
clearTimeout(timer);
}
this.roomDeletionTimers.clear();
}
}
function parseCookieHeader(cookieHeader) {
if (!cookieHeader) return {};
return cookieHeader.split(';').reduce((acc, part) => {
const index = part.indexOf('=');
if (index <= 0) return acc;
const key = part.slice(0, index).trim();
const value = part.slice(index + 1).trim();
if (key) acc[key] = value;
return acc;
}, {});
}
function parseSocketAuth(socket) {
const cookies = parseCookieHeader(socket.handshake.headers.cookie || '');
const raw = cookies.auth || socket.handshake.auth?.token || '';
if (!raw) return null;
let decoded = raw;
try {
decoded = decodeURIComponent(decoded);
} catch {}
if (decoded.includes('%')) {
try {
decoded = decodeURIComponent(decoded);
} catch {}
}
try {
return JSON.parse(decoded);
} catch {
return null;
}
}
class TVRemoteServer {
constructor(io) {
this.io = io;
this.cleanupInterval = null;
attachTVRemoteIO(io);
this.setupEventHandlers();
this.startCleanupTimer();
}
setupEventHandlers() {
this.io.on('connection', (socket) => {
socket.on('tv-remote:register-tv', (data, callback) => {
const auth = parseSocketAuth(socket);
if (!auth?.username) {
callback?.({ success: false, error: '未登录' });
return;
}
const deviceId = String(data?.deviceId || '').slice(0, 128);
if (!deviceId) {
callback?.({ success: false, error: '缺少设备 ID' });
return;
}
callback?.(registerTVRemoteDevice(socket.id, auth.username, data));
});
socket.on('tv-remote:tv-state', (data) => {
const auth = parseSocketAuth(socket);
if (!auth?.username) return;
updateTVRemoteDevice(socket.id, auth.username, data);
});
socket.on('disconnect', () => {
removeTVRemoteSocket(socket.id);
});
});
}
startCleanupTimer() {
this.cleanupInterval = setInterval(() => {
cleanupTVRemoteDevices();
}, 30_000);
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
clearTVRemoteHub();
}
}
app.prepare().then(async () => {
const httpServer = createServer(async (req, res) => {
try {
const parsedUrl = parse(req.url, true);
await handle(req, res, parsedUrl);
} catch (err) {
console.error('Error occurred handling', req.url, err);
res.statusCode = 500;
res.end('Internal server error');
}
});
// 读取观影室配置
const watchRoomConfig = await getWatchRoomConfig();
console.log('[WatchRoom] Config:', watchRoomConfig);
let watchRoomServer = null;
let tvRemoteServer = null;
let io = null;
const tvModeEnabled = isTVModeEnabled();
const shouldStartInternalWatchRoom =
watchRoomConfig.enabled && watchRoomConfig.serverType === 'internal';
if (tvModeEnabled || shouldStartInternalWatchRoom) {
io = new Server(httpServer, {
path: '/socket.io',
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
});
}
if (tvModeEnabled && io) {
tvRemoteServer = new TVRemoteServer(io);
console.log('[TVRemote] Socket.IO remote server initialized');
} else {
console.log('[TVRemote] TV mode disabled, remote server not initialized');
}
if (shouldStartInternalWatchRoom && io) {
// 初始化观影室服务器
watchRoomServer = new WatchRoomServer(io);
console.log('[WatchRoom] Socket.IO server initialized');
} else {
if (!watchRoomConfig.enabled) {
console.log('[WatchRoom] Watch room is disabled');
} else if (watchRoomConfig.serverType === 'external') {
console.log('[WatchRoom] Using external watch room server');
}
}
httpServer
.once('error', (err) => {
console.error(err);
process.exit(1);
})
.listen(port, () => {
console.log(`> Ready on http://${hostname}:${port}`);
if (io) {
console.log(`> Socket.IO ready on ws://${hostname}:${port}`);
} else {
console.log('> Socket.IO disabled');
}
});
const forceExit = (signal) => {
console.log(`\n[Server] Received ${signal}, force exiting...`);
process.exit(0);
};
process.on('SIGINT', () => forceExit('SIGINT'));
process.on('SIGTERM', () => forceExit('SIGTERM'));
});