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
|
|
|
|
};
|
2025-12-26 22:13:13 +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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|