优化socket.io重连机制

This commit is contained in:
mtvpls
2025-12-09 21:16:15 +08:00
parent a1dc1dc37b
commit 17c2338b02
5 changed files with 334 additions and 17 deletions
+51
View File
@@ -10,6 +10,7 @@ import Toast, { ToastProps } from '@/components/Toast';
interface WatchRoomContextType {
socket: WatchRoomSocket | null;
isConnected: boolean;
reconnectFailed: boolean;
currentRoom: Room | null;
members: Member[];
chatMessages: ChatMessage[];
@@ -44,6 +45,9 @@ interface WatchRoomContextType {
changeVideo: (state: any) => void;
changeLiveChannel: (state: any) => void;
clearRoomState: () => void;
// 重连
manualReconnect: () => Promise<void>;
}
const WatchRoomContext = createContext<WatchRoomContextType | null>(null);
@@ -69,6 +73,7 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
const [config, setConfig] = useState<WatchRoomConfig | null>(null);
const [isEnabled, setIsEnabled] = useState(false);
const [toast, setToast] = useState<ToastProps | null>(null);
const [reconnectFailed, setReconnectFailed] = useState(false);
// 处理房间删除的回调
const handleRoomDeleted = useCallback((data?: { reason?: string }) => {
@@ -106,6 +111,37 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
const watchRoom = useWatchRoom(handleRoomDeleted, handleStateCleared);
// 手动重连
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]);
// 加载配置
useEffect(() => {
const loadConfig = async () => {
@@ -127,6 +163,19 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
// 只在启用了观影室时才连接
if (watchRoomConfig.enabled) {
console.log('[WatchRoom] Connecting with config:', watchRoomConfig);
// 设置重连回调
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);
});
await watchRoom.connect(watchRoomConfig);
} else {
console.log('[WatchRoom] Watch room is disabled, skipping connection');
@@ -164,6 +213,7 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
const contextValue: WatchRoomContextType = {
socket: watchRoom.socket,
isConnected: watchRoom.isConnected,
reconnectFailed,
currentRoom: watchRoom.currentRoom,
members: watchRoom.members,
chatMessages: watchRoom.chatMessages,
@@ -182,6 +232,7 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
changeVideo: watchRoom.changeVideo,
changeLiveChannel: watchRoom.changeLiveChannel,
clearRoomState: watchRoom.clearRoomState,
manualReconnect,
};
return (
@@ -2,7 +2,7 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import { MessageCircle, X, Send, Smile, Minimize2, Maximize2, Info, Users, LogOut, XCircle, Mic, MicOff, Volume2, VolumeX } from 'lucide-react';
import { MessageCircle, X, Send, Smile, Minimize2, Maximize2, Info, Users, LogOut, XCircle, Mic, MicOff, Volume2, VolumeX, AlertCircle } from 'lucide-react';
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
import { useVoiceChat } from '@/hooks/useVoiceChat';
@@ -25,6 +25,7 @@ export default function ChatFloatingWindow() {
// 语音聊天状态
const [isMicEnabled, setIsMicEnabled] = useState(false);
const [isSpeakerEnabled, setIsSpeakerEnabled] = useState(true);
const [isReconnecting, setIsReconnecting] = useState(false);
// 使用语音聊天hook
const voiceChat = useVoiceChat({
@@ -95,8 +96,43 @@ export default function ChatFloatingWindow() {
}
}, [isOpen, isMinimized]);
// 如果没有加入房间,不显示聊天按钮
// 处理手动重连
const handleReconnect = async () => {
if (!watchRoom?.manualReconnect) return;
setIsReconnecting(true);
try {
await watchRoom.manualReconnect();
} catch (error) {
console.error('[ChatFloatingWindow] Reconnect failed:', error);
} finally {
setIsReconnecting(false);
}
};
// 如果没有加入房间,只显示重连按钮(如果需要)
if (!watchRoom?.currentRoom) {
// 重连失败时显示重连按钮
if (watchRoom?.reconnectFailed) {
return (
<div className="fixed bottom-20 right-4 z-[700] flex flex-col gap-3 md:bottom-4">
<button
onClick={handleReconnect}
disabled={isReconnecting}
className="group relative flex h-14 w-14 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-all hover:scale-110 hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed animate-pulse"
aria-label="连接失败,点击重连"
title="连接失败,点击重连"
>
<AlertCircle className="h-6 w-6" />
{isReconnecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-10 w-10 animate-spin rounded-full border-4 border-white border-t-transparent"></div>
</div>
)}
</button>
</div>
);
}
return null;
}
@@ -141,6 +177,24 @@ export default function ChatFloatingWindow() {
if (!isOpen && !showRoomInfo) {
return (
<div className="fixed bottom-20 right-4 z-[700] flex flex-col gap-3 md:bottom-4">
{/* 重连失败提示气泡 */}
{watchRoom?.reconnectFailed && (
<button
onClick={handleReconnect}
disabled={isReconnecting}
className="group relative flex h-14 w-14 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-all hover:scale-110 hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed animate-pulse"
aria-label="连接失败,点击重连"
title="连接失败,点击重连"
>
<AlertCircle className="h-6 w-6" />
{isReconnecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-10 w-10 animate-spin rounded-full border-4 border-white border-t-transparent"></div>
</div>
)}
</button>
)}
{/* 房间信息按钮 */}
<button
onClick={() => setShowRoomInfo(true)}
@@ -300,6 +354,24 @@ export default function ChatFloatingWindow() {
if (isMinimized) {
return (
<>
{/* 重连失败提示气泡 */}
{watchRoom?.reconnectFailed && (
<button
onClick={handleReconnect}
disabled={isReconnecting}
className="fixed bottom-[13.5rem] right-4 z-[700] group relative flex h-12 w-12 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-all hover:scale-110 hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed animate-pulse md:bottom-[11rem]"
aria-label="连接失败,点击重连"
title="连接失败,点击重连"
>
<AlertCircle className="h-5 w-5" />
{isReconnecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white border-t-transparent"></div>
</div>
)}
</button>
)}
{/* 房间信息按钮 */}
<button
onClick={() => setShowRoomInfo(true)}
@@ -335,6 +407,24 @@ export default function ChatFloatingWindow() {
// 完整聊天窗口
return (
<>
{/* 重连失败提示气泡 */}
{watchRoom?.reconnectFailed && (
<button
onClick={handleReconnect}
disabled={isReconnecting}
className="fixed bottom-[32.5rem] right-4 z-[700] group relative flex h-12 w-12 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-all hover:scale-110 hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed animate-pulse md:bottom-[30rem]"
aria-label="连接失败,点击重连"
title="连接失败,点击重连"
>
<AlertCircle className="h-5 w-5" />
{isReconnecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white border-t-transparent"></div>
</div>
)}
</button>
)}
{/* 房间信息按钮 */}
<button
onClick={() => setShowRoomInfo(true)}