Files
MoonTVPlus/src/hooks/useWatchRoom.ts
2025-12-07 23:53:00 +08:00

463 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// React Hook for Watch Room
'use client';
import { useEffect, useState, useCallback, useRef } from 'react';
import { watchRoomSocketManager, type WatchRoomSocket } from '@/lib/watch-room-socket';
import type {
Room,
Member,
PlayState,
LiveState,
ChatMessage,
WatchRoomConfig,
StoredRoomInfo,
} from '@/types/watch-room';
const STORAGE_KEY = 'watch_room_info';
export function useWatchRoom(
onRoomDeleted?: (data?: { reason?: string }) => void,
onStateCleared?: () => void
) {
const [socket, setSocket] = useState<WatchRoomSocket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [currentRoom, setCurrentRoom] = useState<Room | null>(null);
const [members, setMembers] = useState<Member[]>([]);
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
const [isOwner, setIsOwner] = useState(false);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 重新加入房间(自动重连)
const rejoinRoom = useCallback(async (info: StoredRoomInfo) => {
console.log('[WatchRoom] Auto-rejoining room:', info);
try {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !watchRoomSocketManager.isConnected()) {
console.error('[WatchRoom] Not connected, cannot rejoin');
return;
}
const result = await new Promise<{ room: Room; members: Member[] }>((resolve, reject) => {
sock.emit('room:join', {
roomId: info.roomId,
password: info.password,
userName: info.userName,
ownerToken: info.ownerToken, // 发送房主令牌
}, (response) => {
if (response.success && response.room && response.members) {
resolve({ room: response.room, members: response.members });
} else {
reject(new Error(response.error || '重新加入房间失败'));
}
});
});
setCurrentRoom(result.room);
setMembers(result.members);
// 根据服务器返回的 room.ownerId 判断是否是房主
setIsOwner(result.room.ownerId === sock.id);
console.log('[WatchRoom] Successfully rejoined room:', result.room.name);
} catch (error) {
console.error('[WatchRoom] Failed to rejoin room:', error);
clearStoredRoomInfo();
}
}, []);
// 连接到服务器
const connect = useCallback(async (config: WatchRoomConfig) => {
try {
const sock = await watchRoomSocketManager.connect(config);
setSocket(sock);
setIsConnected(true);
// 尝试自动重连房间
const storedInfo = getStoredRoomInfo();
if (storedInfo) {
console.log('[WatchRoom] Attempting to reconnect to room:', storedInfo.roomId);
reconnectTimeoutRef.current = setTimeout(() => {
rejoinRoom(storedInfo);
}, 1000);
}
} catch (error) {
console.error('[WatchRoom] Failed to connect:', error);
setIsConnected(false);
}
}, [rejoinRoom]);
// 断开连接
const disconnect = useCallback(() => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
watchRoomSocketManager.disconnect();
setSocket(null);
setIsConnected(false);
setCurrentRoom(null);
setMembers([]);
setChatMessages([]);
}, []);
// 创建房间
const createRoom = useCallback(
async (data: { name: string; description: string; password?: string; isPublic: boolean; userName: string }) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !watchRoomSocketManager.isConnected()) {
throw new Error('Not connected');
}
return new Promise<Room>((resolve, reject) => {
sock.emit('room:create', data, (response) => {
if (response.success && response.room) {
setCurrentRoom(response.room);
setIsOwner(true);
// 创建房间时,手动设置房主的成员信息
setMembers([{
id: sock.id!,
name: data.userName,
isOwner: true,
lastHeartbeat: Date.now(),
}]);
storeRoomInfo({
roomId: response.room.id,
roomName: response.room.name,
isOwner: true,
userName: data.userName,
password: data.password,
ownerToken: response.room.ownerToken, // 保存房主令牌
timestamp: Date.now(),
});
resolve(response.room);
} else {
reject(new Error(response.error || '创建房间失败'));
}
});
});
},
[]
);
// 加入房间
const joinRoom = useCallback(
async (data: { roomId: string; password?: string; userName: string }) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !watchRoomSocketManager.isConnected()) {
throw new Error('Not connected');
}
return new Promise<{ room: Room; members: Member[] }>((resolve, reject) => {
sock.emit('room:join', data, (response) => {
if (response.success && response.room && response.members) {
setCurrentRoom(response.room);
setMembers(response.members);
// 根据服务器返回的 room.ownerId 判断是否是房主
const isRoomOwner = response.room.ownerId === sock.id;
setIsOwner(isRoomOwner);
storeRoomInfo({
roomId: response.room.id,
roomName: response.room.name,
isOwner: isRoomOwner,
userName: data.userName,
password: data.password,
ownerToken: isRoomOwner ? response.room.ownerToken : undefined,
timestamp: Date.now(),
});
resolve({ room: response.room, members: response.members });
} else {
reject(new Error(response.error || '加入房间失败'));
}
});
});
},
[]
);
// 离开房间
const leaveRoom = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) return;
sock.emit('room:leave');
setCurrentRoom(null);
setMembers([]);
setChatMessages([]);
setIsOwner(false);
clearStoredRoomInfo();
}, []);
// 获取房间列表
const getRoomList = useCallback(async (): Promise<Room[]> => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !watchRoomSocketManager.isConnected()) {
throw new Error('Not connected');
}
return new Promise((resolve) => {
sock.emit('room:list', (rooms) => {
resolve(rooms);
});
});
}, []);
// 发送聊天消息
const sendChatMessage = useCallback(
(content: string, type: 'text' | 'emoji' = 'text') => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !currentRoom) return;
sock.emit('chat:message', { content, type });
},
[currentRoom]
);
// 更新播放状态
const updatePlayState = useCallback(
(state: PlayState) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !isOwner) {
console.log('[WatchRoom] Cannot update play state:', { hasSocket: !!sock, isOwner });
return;
}
console.log('[WatchRoom] Emitting play:update with state:', state);
sock.emit('play:update', state);
},
[isOwner]
);
// 跳转播放进度
const seekPlayback = useCallback(
(currentTime: number) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) {
console.log('[WatchRoom] Cannot seek - no socket');
return;
}
console.log('[WatchRoom] Emitting play:seek with time:', currentTime);
sock.emit('play:seek', currentTime);
},
[]
);
// 播放
const play = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) {
console.log('[WatchRoom] Cannot play - no socket');
return;
}
console.log('[WatchRoom] Emitting play:play');
sock.emit('play:play');
}, []);
// 暂停
const pause = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) {
console.log('[WatchRoom] Cannot pause - no socket');
return;
}
console.log('[WatchRoom] Emitting play:pause');
sock.emit('play:pause');
}, []);
// 切换视频
const changeVideo = useCallback(
(state: PlayState) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) {
console.log('[WatchRoom] Cannot change video - no socket');
return;
}
if (!isOwner) {
console.log('[WatchRoom] Cannot change video - not owner');
return;
}
console.log('[WatchRoom] Emitting play:change with state:', state);
sock.emit('play:change', state);
},
[isOwner]
);
// 切换直播频道
const changeLiveChannel = useCallback(
(state: LiveState) => {
const sock = watchRoomSocketManager.getSocket();
if (!sock || !isOwner) return;
sock.emit('live:change', state);
},
[isOwner]
);
// 清除房间播放状态(房主离开播放/直播页面时调用)
const clearRoomState = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
if (!sock) {
console.log('[WatchRoom] Cannot clear state - no socket');
return;
}
if (!isOwner) {
console.log('[WatchRoom] Cannot clear state - not owner');
return;
}
console.log('[WatchRoom] Emitting state:clear');
sock.emit('state:clear');
}, [isOwner]);
// 设置事件监听
useEffect(() => {
if (!socket) return;
// 房间事件
socket.on('room:joined', (data) => {
setCurrentRoom(data.room);
setMembers(data.members);
});
socket.on('room:member-joined', (member) => {
setMembers((prev) => [...prev, member]);
});
socket.on('room:member-left', (userId) => {
setMembers((prev) => prev.filter((m) => m.id !== userId));
});
socket.on('room:deleted', (data?: { reason?: string }) => {
console.log('[WatchRoom] Room deleted:', data);
// 调用回调显示Toast
onRoomDeleted?.(data);
setCurrentRoom(null);
setMembers([]);
setChatMessages([]);
clearStoredRoomInfo();
});
// 播放事件
socket.on('play:update', (state) => {
if (currentRoom) {
setCurrentRoom((prev) => (prev ? { ...prev, currentState: state } : null));
}
});
// 视频切换事件(换集、换源)
socket.on('play:change', (state) => {
if (currentRoom) {
setCurrentRoom((prev) => (prev ? { ...prev, currentState: state } : null));
}
});
// 直播频道切换事件
socket.on('live:change', (state) => {
if (currentRoom) {
setCurrentRoom((prev) => (prev ? { ...prev, currentState: state } : null));
}
});
// 聊天事件
socket.on('chat:message', (message) => {
setChatMessages((prev) => [...prev, message]);
});
// 状态清除事件(房主心跳超时)
socket.on('state:cleared', () => {
console.log('[WatchRoom] Room state cleared by server (owner inactive)');
// 清除当前房间的播放/直播状态
setCurrentRoom((prev) => (prev ? { ...prev, currentState: null } : null));
// 调用回调显示Toast
onStateCleared?.();
});
// 连接状态
socket.on('connect', () => {
setIsConnected(true);
});
socket.on('disconnect', () => {
setIsConnected(false);
});
return () => {
socket.off('room:joined');
socket.off('room:member-joined');
socket.off('room:member-left');
socket.off('room:deleted');
socket.off('play:update');
socket.off('play:change');
socket.off('live:change');
socket.off('chat:message');
socket.off('state:cleared');
socket.off('connect');
socket.off('disconnect');
};
}, [socket, currentRoom, onRoomDeleted, onStateCleared]);
// 清理
useEffect(() => {
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
};
}, []);
return {
socket,
isConnected,
currentRoom,
members,
chatMessages,
isOwner,
connect,
disconnect,
createRoom,
joinRoom,
leaveRoom,
getRoomList,
sendChatMessage,
updatePlayState,
seekPlayback,
play,
pause,
changeVideo,
changeLiveChannel,
clearRoomState,
};
}
// 存储房间信息到 localStorage
function storeRoomInfo(info: StoredRoomInfo) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(info));
}
// 获取存储的房间信息
function getStoredRoomInfo(): StoredRoomInfo | null {
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return null;
try {
const info: StoredRoomInfo = JSON.parse(stored);
// 检查是否过期24小时
if (Date.now() - info.timestamp > 24 * 60 * 60 * 1000) {
clearStoredRoomInfo();
return null;
}
return info;
} catch {
return null;
}
}
// 清除存储的房间信息
function clearStoredRoomInfo() {
localStorage.removeItem(STORAGE_KEY);
}