Files
MoonTVPlus/src/components/WatchRoomProvider.tsx

266 lines
8.6 KiB
TypeScript
Raw Normal View History

2025-12-06 21:39:37 +08:00
// WatchRoom 全局状态管理 Provider
'use client';
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
import { useWatchRoom } from '@/hooks/useWatchRoom';
import type { Room, Member, ChatMessage, WatchRoomConfig } from '@/types/watch-room';
2025-12-07 00:16:21 +08:00
import Toast, { ToastProps } from '@/components/Toast';
2025-12-06 21:39:37 +08:00
2025-12-10 00:46:57 +08:00
// Import type from watch-room-socket
type WatchRoomSocket = import('@/lib/watch-room-socket').WatchRoomSocket;
2025-12-06 21:39:37 +08:00
interface WatchRoomContextType {
socket: WatchRoomSocket | null;
isConnected: boolean;
2025-12-09 21:16:15 +08:00
reconnectFailed: boolean;
2025-12-06 21:39:37 +08:00
currentRoom: Room | null;
members: Member[];
chatMessages: ChatMessage[];
isOwner: boolean;
isEnabled: boolean;
config: WatchRoomConfig | null;
// 房间操作
createRoom: (data: {
name: string;
description: string;
password?: string;
isPublic: boolean;
userName: string;
}) => Promise<Room>;
joinRoom: (data: {
roomId: string;
password?: string;
userName: string;
}) => Promise<{ room: Room; members: Member[] }>;
leaveRoom: () => void;
getRoomList: () => Promise<Room[]>;
// 聊天
sendChatMessage: (content: string, type?: 'text' | 'emoji') => void;
// 播放控制(供 play/live 页面使用)
updatePlayState: (state: any) => void;
seekPlayback: (currentTime: number) => void;
play: () => void;
pause: () => void;
changeVideo: (state: any) => void;
changeLiveChannel: (state: any) => void;
2025-12-07 23:48:29 +08:00
clearRoomState: () => void;
2025-12-09 21:16:15 +08:00
// 重连
manualReconnect: () => Promise<void>;
2025-12-06 21:39:37 +08:00
}
const WatchRoomContext = createContext<WatchRoomContextType | null>(null);
export const useWatchRoomContext = () => {
const context = useContext(WatchRoomContext);
if (!context) {
throw new Error('useWatchRoomContext must be used within WatchRoomProvider');
}
return context;
};
// 安全版本,可以在非 Provider 内使用
export const useWatchRoomContextSafe = () => {
return useContext(WatchRoomContext);
};
interface WatchRoomProviderProps {
children: React.ReactNode;
}
export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
const [config, setConfig] = useState<WatchRoomConfig | null>(null);
const [isEnabled, setIsEnabled] = useState(false);
2025-12-07 00:16:21 +08:00
const [toast, setToast] = useState<ToastProps | null>(null);
2025-12-09 21:16:15 +08:00
const [reconnectFailed, setReconnectFailed] = useState(false);
2025-12-07 00:16:21 +08:00
// 处理房间删除的回调
const handleRoomDeleted = useCallback((data?: { reason?: string }) => {
console.log('[WatchRoomProvider] Room deleted:', data);
// 显示Toast提示
if (data?.reason === 'owner_left') {
setToast({
message: '房主已解散房间',
type: 'error',
duration: 4000,
onClose: () => setToast(null),
});
} else {
setToast({
message: '房间已被删除',
type: 'info',
duration: 3000,
onClose: () => setToast(null),
});
}
}, []);
2025-12-06 21:39:37 +08:00
2025-12-07 23:48:29 +08:00
// 处理房间状态清除的回调房主离开超过30秒
const handleStateCleared = useCallback(() => {
console.log('[WatchRoomProvider] Room state cleared');
setToast({
message: '房主已离开,播放状态已清除',
2025-12-08 01:00:10 +08:00
type: 'info',
2025-12-07 23:48:29 +08:00
duration: 4000,
onClose: () => setToast(null),
});
}, []);
const watchRoom = useWatchRoom(handleRoomDeleted, handleStateCleared);
2025-12-06 21:39:37 +08:00
2025-12-09 21:16:15 +08:00
// 手动重连
const manualReconnect = useCallback(async () => {
console.log('[WatchRoomProvider] Manual reconnect initiated');
setReconnectFailed(false);
const { watchRoomSocketManager } = await import('@/lib/watch-room-socket');
const success = await watchRoomSocketManager.reconnect();
if (success) {
console.log('[WatchRoomProvider] Manual reconnect succeeded');
// 尝试重新加入房间
const storedInfo = localStorage.getItem('watch_room_info');
if (storedInfo && watchRoom.socket) {
try {
const info = JSON.parse(storedInfo);
console.log('[WatchRoomProvider] Attempting to rejoin room after reconnect');
await watchRoom.joinRoom({
roomId: info.roomId,
password: info.password,
userName: info.userName,
});
} catch (error) {
console.error('[WatchRoomProvider] Failed to rejoin room after reconnect:', error);
}
}
} else {
console.error('[WatchRoomProvider] Manual reconnect failed');
setReconnectFailed(true);
}
}, [watchRoom]);
2025-12-06 21:39:37 +08:00
// 加载配置
useEffect(() => {
const loadConfig = async () => {
try {
2025-12-08 10:03:25 +08:00
// 使用公共 API 获取观影室配置(不需要管理员权限)
const response = await fetch('/api/server-config');
2025-12-06 21:39:37 +08:00
if (response.ok) {
const data = await response.json();
2025-12-08 10:03:25 +08:00
// API 返回格式: { SiteName, StorageType, Version, WatchRoom }
2025-12-06 21:39:37 +08:00
const watchRoomConfig: WatchRoomConfig = {
2025-12-08 10:03:25 +08:00
enabled: data.WatchRoom?.enabled ?? false, // 默认不启用
serverType: data.WatchRoom?.serverType ?? 'internal',
externalServerUrl: data.WatchRoom?.externalServerUrl,
2025-12-06 21:39:37 +08:00
};
// 如果使用外部服务器,需要获取认证信息(需要登录)
if (watchRoomConfig.serverType === 'external' && watchRoomConfig.enabled) {
try {
const authResponse = await fetch('/api/watch-room-auth');
if (authResponse.ok) {
const authData = await authResponse.json();
watchRoomConfig.externalServerAuth = authData.externalServerAuth;
} else {
console.error('[WatchRoom] Failed to load auth info:', authResponse.status);
// 如果无法获取认证信息,禁用观影室
watchRoomConfig.enabled = false;
}
} catch (error) {
console.error('[WatchRoom] Error loading auth info:', error);
// 如果无法获取认证信息,禁用观影室
watchRoomConfig.enabled = false;
}
}
2025-12-06 21:39:37 +08:00
setConfig(watchRoomConfig);
setIsEnabled(watchRoomConfig.enabled);
2025-12-08 10:03:25 +08:00
// 只在启用了观影室时才连接
2025-12-06 21:39:37 +08:00
if (watchRoomConfig.enabled) {
console.log('[WatchRoom] Connecting with config:', watchRoomConfig);
2025-12-09 21:16:15 +08:00
// 设置重连回调
const { watchRoomSocketManager } = await import('@/lib/watch-room-socket');
watchRoomSocketManager.setReconnectFailedCallback(() => {
console.log('[WatchRoomProvider] Reconnect failed callback triggered');
setReconnectFailed(true);
});
watchRoomSocketManager.setReconnectSuccessCallback(() => {
console.log('[WatchRoomProvider] Reconnect success callback triggered');
setReconnectFailed(false);
});
2025-12-06 21:39:37 +08:00
await watchRoom.connect(watchRoomConfig);
2025-12-08 10:03:25 +08:00
} else {
console.log('[WatchRoom] Watch room is disabled, skipping connection');
2025-12-06 21:39:37 +08:00
}
} else {
2025-12-08 10:03:25 +08:00
console.error('[WatchRoom] Failed to load config:', response.status);
// 加载配置失败时,不连接,保持禁用状态
const defaultConfig: WatchRoomConfig = {
enabled: false,
serverType: 'internal',
};
setConfig(defaultConfig);
setIsEnabled(false);
2025-12-06 21:39:37 +08:00
}
} catch (error) {
2025-12-08 10:03:25 +08:00
console.error('[WatchRoom] Error loading config:', error);
// 加载配置失败时,不连接,保持禁用状态
const defaultConfig: WatchRoomConfig = {
enabled: false,
serverType: 'internal',
};
2025-12-06 21:39:37 +08:00
setConfig(defaultConfig);
2025-12-08 10:03:25 +08:00
setIsEnabled(false);
2025-12-06 21:39:37 +08:00
}
};
loadConfig();
// 清理
return () => {
watchRoom.disconnect();
};
}, []);
const contextValue: WatchRoomContextType = {
socket: watchRoom.socket,
isConnected: watchRoom.isConnected,
2025-12-09 21:16:15 +08:00
reconnectFailed,
2025-12-06 21:39:37 +08:00
currentRoom: watchRoom.currentRoom,
members: watchRoom.members,
chatMessages: watchRoom.chatMessages,
isOwner: watchRoom.isOwner,
isEnabled,
config,
createRoom: watchRoom.createRoom,
joinRoom: watchRoom.joinRoom,
leaveRoom: watchRoom.leaveRoom,
getRoomList: watchRoom.getRoomList,
sendChatMessage: watchRoom.sendChatMessage,
updatePlayState: watchRoom.updatePlayState,
seekPlayback: watchRoom.seekPlayback,
play: watchRoom.play,
pause: watchRoom.pause,
changeVideo: watchRoom.changeVideo,
changeLiveChannel: watchRoom.changeLiveChannel,
2025-12-07 23:48:29 +08:00
clearRoomState: watchRoom.clearRoomState,
2025-12-09 21:16:15 +08:00
manualReconnect,
2025-12-06 21:39:37 +08:00
};
return (
<WatchRoomContext.Provider value={contextValue}>
{children}
2025-12-07 00:16:21 +08:00
{toast && <Toast {...toast} />}
2025-12-06 21:39:37 +08:00
</WatchRoomContext.Provider>
);
}