Files
MoonTVPlus/src/hooks/useWatchRoom.ts

463 lines
13 KiB
TypeScript
Raw Normal View History

2025-12-06 21:39:37 +08:00
// 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';
2025-12-07 23:48:29 +08:00
export function useWatchRoom(
onRoomDeleted?: (data?: { reason?: string }) => void,
onStateCleared?: () => void
) {
2025-12-06 21:39:37 +08:00
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);
2025-12-07 00:16:21 +08:00
// 重新加入房间(自动重连)
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();
}
}, []);
2025-12-06 21:39:37 +08:00
// 连接到服务器
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);
}
2025-12-07 00:16:21 +08:00
}, [rejoinRoom]);
2025-12-06 21:39:37 +08:00
// 断开连接
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(),
}]);
2025-12-06 21:39:37 +08:00
storeRoomInfo({
roomId: response.room.id,
roomName: response.room.name,
isOwner: true,
userName: data.userName,
password: data.password,
2025-12-07 00:16:21 +08:00
ownerToken: response.room.ownerToken, // 保存房主令牌
2025-12-06 21:39:37 +08:00
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);
2025-12-07 00:16:21 +08:00
// 根据服务器返回的 room.ownerId 判断是否是房主
const isRoomOwner = response.room.ownerId === sock.id;
setIsOwner(isRoomOwner);
2025-12-06 21:39:37 +08:00
storeRoomInfo({
roomId: response.room.id,
roomName: response.room.name,
2025-12-07 00:16:21 +08:00
isOwner: isRoomOwner,
2025-12-06 21:39:37 +08:00
userName: data.userName,
password: data.password,
2025-12-07 00:16:21 +08:00
ownerToken: isRoomOwner ? response.room.ownerToken : undefined,
2025-12-06 21:39:37 +08:00
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();
2025-12-07 00:16:21 +08:00
if (!sock || !isOwner) {
console.log('[WatchRoom] Cannot update play state:', { hasSocket: !!sock, isOwner });
return;
}
2025-12-06 21:39:37 +08:00
2025-12-07 00:16:21 +08:00
console.log('[WatchRoom] Emitting play:update with state:', state);
2025-12-06 21:39:37 +08:00
sock.emit('play:update', state);
},
[isOwner]
);
// 跳转播放进度
const seekPlayback = useCallback(
(currentTime: number) => {
const sock = watchRoomSocketManager.getSocket();
2025-12-07 00:16:21 +08:00
if (!sock) {
console.log('[WatchRoom] Cannot seek - no socket');
return;
}
2025-12-06 21:39:37 +08:00
2025-12-07 00:16:21 +08:00
console.log('[WatchRoom] Emitting play:seek with time:', currentTime);
2025-12-06 21:39:37 +08:00
sock.emit('play:seek', currentTime);
},
[]
);
// 播放
const play = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
2025-12-07 00:16:21 +08:00
if (!sock) {
console.log('[WatchRoom] Cannot play - no socket');
return;
}
2025-12-06 21:39:37 +08:00
2025-12-07 00:16:21 +08:00
console.log('[WatchRoom] Emitting play:play');
2025-12-06 21:39:37 +08:00
sock.emit('play:play');
}, []);
// 暂停
const pause = useCallback(() => {
const sock = watchRoomSocketManager.getSocket();
2025-12-07 00:16:21 +08:00
if (!sock) {
console.log('[WatchRoom] Cannot pause - no socket');
return;
}
2025-12-06 21:39:37 +08:00
2025-12-07 00:16:21 +08:00
console.log('[WatchRoom] Emitting play:pause');
2025-12-06 21:39:37 +08:00
sock.emit('play:pause');
}, []);
// 切换视频
const changeVideo = useCallback(
(state: PlayState) => {
const sock = watchRoomSocketManager.getSocket();
2025-12-07 23:48:29 +08:00
if (!sock) {
console.log('[WatchRoom] Cannot change video - no socket');
return;
}
if (!isOwner) {
console.log('[WatchRoom] Cannot change video - not owner');
return;
}
2025-12-06 21:39:37 +08:00
2025-12-07 23:48:29 +08:00
console.log('[WatchRoom] Emitting play:change with state:', state);
2025-12-06 21:39:37 +08:00
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]
);
2025-12-07 23:48:29 +08:00
// 清除房间播放状态(房主离开播放/直播页面时调用)
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]);
2025-12-06 21:39:37 +08:00
// 设置事件监听
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));
});
2025-12-07 00:16:21 +08:00
socket.on('room:deleted', (data?: { reason?: string }) => {
console.log('[WatchRoom] Room deleted:', data);
// 调用回调显示Toast
onRoomDeleted?.(data);
2025-12-06 21:39:37 +08:00
setCurrentRoom(null);
setMembers([]);
setChatMessages([]);
clearStoredRoomInfo();
});
// 播放事件
socket.on('play:update', (state) => {
if (currentRoom) {
setCurrentRoom((prev) => (prev ? { ...prev, currentState: state } : null));
}
});
2025-12-07 00:16:21 +08:00
// 视频切换事件(换集、换源)
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));
}
});
2025-12-06 21:39:37 +08:00
// 聊天事件
socket.on('chat:message', (message) => {
setChatMessages((prev) => [...prev, message]);
});
2025-12-07 23:48:29 +08:00
// 状态清除事件(房主心跳超时)
socket.on('state:cleared', () => {
console.log('[WatchRoom] Room state cleared by server (owner inactive)');
// 清除当前房间的播放/直播状态
setCurrentRoom((prev) => (prev ? { ...prev, currentState: null } : null));
// 调用回调显示Toast
onStateCleared?.();
});
2025-12-06 21:39:37 +08:00
// 连接状态
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');
2025-12-07 00:16:21 +08:00
socket.off('play:change');
socket.off('live:change');
2025-12-06 21:39:37 +08:00
socket.off('chat:message');
2025-12-07 23:48:29 +08:00
socket.off('state:cleared');
2025-12-06 21:39:37 +08:00
socket.off('connect');
socket.off('disconnect');
};
2025-12-07 23:48:29 +08:00
}, [socket, currentRoom, onRoomDeleted, onStateCleared]);
2025-12-06 21:39:37 +08:00
// 清理
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,
2025-12-07 23:48:29 +08:00
clearRoomState,
2025-12-06 21:39:37 +08:00
};
}
// 存储房间信息到 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);
}