diff --git a/chuan-next/src/components/ConnectionStatus.tsx b/chuan-next/src/components/ConnectionStatus.tsx
index 87d8710..de87f7c 100644
--- a/chuan-next/src/components/ConnectionStatus.tsx
+++ b/chuan-next/src/components/ConnectionStatus.tsx
@@ -23,6 +23,7 @@ const getConnectionStatus = (currentRoom: { code: string; role: Role } | null) =
const isPeerConnected = connection?.isPeerConnected || false;
const isConnecting = connection?.isConnecting || false;
const error = connection?.error || null;
+ const currentConnectType = connection?.currentConnectType || 'webrtc';
if (error) {
return {
@@ -58,12 +59,15 @@ const getConnectionStatus = (currentRoom: { code: string; role: Role } | null) =
};
}
- if (isWebSocketConnected && !isPeerConnected) {
- return {
- type: 'room-ready' as const,
- message: '房间已创建',
- detail: '等待对方加入并建立P2P连接...',
- };
+ if (isWebSocketConnected ) {
+ // 根据连接类型显示不同信息
+ if (currentConnectType === 'websocket') {
+ return {
+ type: 'connected' as const,
+ message: 'P2P链接失败,WS降级中',
+ detail: 'WebSocket传输模式已就绪',
+ };
+ }
}
if (isWebSocketConnected && isPeerConnected) {
@@ -89,6 +93,8 @@ const getStatusColor = (type: string) => {
case 'connecting':
case 'room-ready':
return 'text-yellow-600';
+ case 'websocket-ready':
+ return 'text-orange-600';
case 'error':
return 'text-red-600';
case 'disconnected':
@@ -110,6 +116,8 @@ const StatusIcon = ({ type, className = 'w-3 h-3' }: { type: string; className?:
return (
);
+ case 'websocket-ready':
+ return ;
case 'error':
return ;
case 'disconnected':
@@ -120,11 +128,12 @@ const StatusIcon = ({ type, className = 'w-3 h-3' }: { type: string; className?:
};
// 获取连接状态文字描述
-const getConnectionStatusText = (connection: { isWebSocketConnected?: boolean; isPeerConnected?: boolean; isConnecting?: boolean; error?: string | null }) => {
+const getConnectionStatusText = (connection: { isWebSocketConnected?: boolean; isPeerConnected?: boolean; isConnecting?: boolean; error?: string | null; currentConnectType?: 'webrtc' | 'websocket' }) => {
const isWebSocketConnected = connection?.isWebSocketConnected || false;
const isPeerConnected = connection?.isPeerConnected || false;
const isConnecting = connection?.isConnecting || false;
const error = connection?.error || null;
+ const currentConnectType = connection?.currentConnectType || 'webrtc';
const wsStatus = isWebSocketConnected ? 'WS已连接' : 'WS未连接';
const rtcStatus = isPeerConnected ? 'RTC已连接' :
@@ -142,6 +151,11 @@ const getConnectionStatusText = (connection: { isWebSocketConnected?: boolean; i
return `${wsStatus} ${rtcStatus} - P2P连接成功`;
}
+ // 如果WebSocket已连接但P2P未连接,且当前连接类型是websocket
+ if (isWebSocketConnected && !isPeerConnected && currentConnectType === 'websocket') {
+ return `${wsStatus} ${rtcStatus} - P2P链接失败,将使用WS进行传输`;
+ }
+
return `${wsStatus} ${rtcStatus}`;
};
@@ -157,6 +171,7 @@ export function ConnectionStatus(props: ConnectionStatusProps) {
isPeerConnected: webrtcState.isPeerConnected,
isConnecting: webrtcState.isConnecting,
error: webrtcState.error,
+ currentConnectType: webrtcState.currentConnectType,
};
const isConnected = webrtcState.isWebSocketConnected && webrtcState.isPeerConnected;
diff --git a/chuan-next/src/components/WebRTCConnectionStatus.tsx b/chuan-next/src/components/WebRTCConnectionStatus.tsx
deleted file mode 100644
index 1578be4..0000000
--- a/chuan-next/src/components/WebRTCConnectionStatus.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-import React from 'react';
-import { AlertCircle, Wifi, WifiOff, Loader2, RotateCcw } from 'lucide-react';
-import { WebRTCConnection } from '@/hooks/connection/useSharedWebRTCManager';
-
-interface Props {
- webrtc: WebRTCConnection;
- className?: string;
-}
-
-/**
- * WebRTC连接状态显示组件
- * 显示详细的连接状态、错误信息和重试按钮
- */
-export function WebRTCConnectionStatus({ webrtc, className = '' }: Props) {
- const {
- isConnected,
- isConnecting,
- isWebSocketConnected,
- isPeerConnected,
- error,
- canRetry,
- retry
- } = webrtc;
-
- // 状态图标
- const getStatusIcon = () => {
- if (isConnecting) {
- return ;
- }
-
- if (error) {
- // 区分信息提示和错误
- if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
- return ;
- }
- return ;
- }
-
- if (isPeerConnected) {
- return ;
- }
-
- if (isWebSocketConnected) {
- return ;
- }
-
- return ;
- };
-
- // 状态文本
- const getStatusText = () => {
- if (error) {
- return error;
- }
-
- if (isConnecting) {
- return '正在连接...';
- }
-
- if (isPeerConnected) {
- return 'P2P连接已建立';
- }
-
- if (isWebSocketConnected) {
- return '信令服务器已连接';
- }
-
- return '未连接';
- };
-
- // 状态颜色
- const getStatusColor = () => {
- if (error) {
- // 区分信息提示和错误
- if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
- return 'text-yellow-600';
- }
- return 'text-red-600';
- }
- if (isConnecting) return 'text-blue-600';
- if (isPeerConnected) return 'text-green-600';
- if (isWebSocketConnected) return 'text-yellow-600';
- return 'text-gray-600';
- };
-
- const handleRetry = async () => {
- try {
- await retry();
- } catch (error) {
- console.error('重试连接失败:', error);
- }
- };
-
- return (
-
-
- {getStatusIcon()}
-
- {getStatusText()}
-
-
-
- {/* 连接详细状态指示器 */}
-
- {/* WebSocket状态 */}
-
-
- {/* P2P状态 */}
-
-
- {/* 重试按钮 */}
- {canRetry && (
-
- )}
-
-
- );
-}
-
-/**
- * 简单的连接状态指示器(用于空间受限的地方)
- */
-export function WebRTCStatusIndicator({ webrtc, className = '' }: Props) {
- const { isPeerConnected, isConnecting, error } = webrtc;
-
- if (error) {
- // 区分信息提示和错误
- if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
- return (
-
- );
- }
- return (
-
- );
- }
-
- if (isConnecting) {
- return (
-
- );
- }
-
- if (isPeerConnected) {
- return (
-
- );
- }
-
- return (
-
- );
-}
diff --git a/chuan-next/src/components/WebRTCFileTransfer.tsx b/chuan-next/src/components/WebRTCFileTransfer.tsx
index d77ad18..a183e83 100644
--- a/chuan-next/src/components/WebRTCFileTransfer.tsx
+++ b/chuan-next/src/components/WebRTCFileTransfer.tsx
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
import { useToast } from '@/components/ui/toast-simple';
import { WebRTCFileReceive } from '@/components/webrtc/WebRTCFileReceive';
import { WebRTCFileUpload } from '@/components/webrtc/WebRTCFileUpload';
-import { useConnectionState, useRoomConnection, useSharedWebRTCManager } from '@/hooks/connection';
+import { useConnectionState, useConnectManager, useRoomConnection } from '@/hooks/connection';
import { useFileListSync, useFileStateManager, useFileTransferBusiness } from '@/hooks/file-transfer';
import { useURLHandler } from '@/hooks/ui';
import { Download, Upload } from 'lucide-react';
@@ -34,7 +34,7 @@ export const WebRTCFileTransfer: React.FC = () => {
const fileInputRef = useRef(null);
// 创建共享连接
- const connection = useSharedWebRTCManager();
+ const connection = useConnectManager();
const stableConnection = useMemo(() => connection, [connection.getConnectState().isConnected, connection.getConnectState().isConnecting, connection.getConnectState().isWebSocketConnected, connection.getConnectState().error]);
// 使用共享连接创建业务层
@@ -229,10 +229,6 @@ export const WebRTCFileTransfer: React.FC = () => {
console.log('连接已断开,忽略进度更新:', progressInfo.fileName);
return;
}
-
- console.log('=== 文件进度更新 ===');
- console.log('文件:', progressInfo.fileName, 'ID:', progressInfo.fileId, '进度:', progressInfo.progress);
-
// 更新当前传输文件信息
setCurrentTransferFile({
fileId: progressInfo.fileId,
diff --git a/chuan-next/src/components/examples/WebSocketExample.tsx b/chuan-next/src/components/examples/WebSocketExample.tsx
deleted file mode 100644
index bf4ec74..0000000
--- a/chuan-next/src/components/examples/WebSocketExample.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import React, { useState } from 'react';
-import { IWebMessage } from '../../hooks/connection/types';
-import { useWebSocketConnection } from '../../hooks/connection/ws/useWebSocketConnection';
-
-/**
- * WebSocket 连接示例组件
- * 展示如何使用 WebSocket 实现的 IWebConnection 接口
- */
-export function WebSocketExample() {
- const [roomCode, setRoomCode] = useState('');
- const [role, setRole] = useState<'sender' | 'receiver'>('sender');
- const [message, setMessage] = useState('');
-
- // 使用 WebSocket 连接
- const connection = useWebSocketConnection();
- const state = connection.getConnectState();
-
- // 连接到房间
- const handleConnect = async () => {
- if (roomCode.trim()) {
- await connection.connect(roomCode.trim(), role);
- }
- };
-
- // 发送消息
- const handleSendMessage = () => {
- if (message.trim()) {
- connection.sendMessage({
- type: 'text',
- payload: { text: message.trim() }
- });
- setMessage('');
- }
- };
-
- // 注册消息处理器
- React.useEffect(() => {
- const unsubscribe = connection.registerMessageHandler('text', (msg: IWebMessage) => {
- console.log('收到文本消息:', msg.payload.text);
- });
-
- return unsubscribe;
- }, [connection]);
-
- return (
-
-
WebSocket 连接示例
-
- {/* 连接状态 */}
-
-
连接状态: {state.isConnected ? '已连接' : '未连接'}
-
传输类型: {connection.connectType}
- {state.error && (
-
错误: {state.error}
- )}
- {state.currentRoom && (
-
房间: {state.currentRoom.code} ({state.currentRoom.role})
- )}
-
-
- {/* 连接控制 */}
- {!state.isConnected ? (
-
-
-
- setRoomCode(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded"
- placeholder="输入房间代码"
- />
-
-
-
-
-
-
-
-
-
- {state.canRetry && (
-
- )}
-
- ) : (
-
- {/* 消息发送 */}
-
-
-
- setMessage(e.target.value)}
- className="flex-1 px-3 py-2 border border-gray-300 rounded"
- placeholder="输入消息内容"
- onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
- />
-
-
-
-
- {/* 断开连接 */}
-
-
- )}
-
- {/* 功能说明 */}
-
-
支持的功能:
-
- - ✅ WebSocket 连接管理
- - ✅ 消息发送和接收
- - ✅ 二进制数据传输
- - ✅ 多通道消息处理
- - ✅ 自动重连机制
- - ❌ 媒体轨道(WebSocket 不支持)
- - ❌ P2P 直连(WebSocket 不支持)
-
-
-
- );
-}
\ No newline at end of file
diff --git a/chuan-next/src/components/webrtc/WebRTCTextReceiver.tsx b/chuan-next/src/components/webrtc/WebRTCTextReceiver.tsx
index 4a225de..04edf82 100644
--- a/chuan-next/src/components/webrtc/WebRTCTextReceiver.tsx
+++ b/chuan-next/src/components/webrtc/WebRTCTextReceiver.tsx
@@ -4,7 +4,7 @@ import { ConnectionStatus } from '@/components/ConnectionStatus';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useToast } from '@/components/ui/toast-simple';
-import { useSharedWebRTCManager } from '@/hooks/connection';
+import { useConnectManager } from '@/hooks/connection';
import { useFileTransferBusiness } from '@/hooks/file-transfer';
import { useTextTransferBusiness } from '@/hooks/text-transfer';
import { Download, Image, MessageSquare } from 'lucide-react';
@@ -37,7 +37,7 @@ export const WebRTCTextReceiver: React.FC = ({
const hasTriedAutoConnect = useRef(false);
// 创建共享连接
- const connection = useSharedWebRTCManager();
+ const connection = useConnectManager();
const {getConnectState} = connection;
diff --git a/chuan-next/src/components/webrtc/WebRTCTextSender.tsx b/chuan-next/src/components/webrtc/WebRTCTextSender.tsx
index feb45f6..c1dcda8 100644
--- a/chuan-next/src/components/webrtc/WebRTCTextSender.tsx
+++ b/chuan-next/src/components/webrtc/WebRTCTextSender.tsx
@@ -4,7 +4,7 @@ import { ConnectionStatus } from '@/components/ConnectionStatus';
import RoomInfoDisplay from '@/components/RoomInfoDisplay';
import { Button } from '@/components/ui/button';
import { useToast } from '@/components/ui/toast-simple';
-import { useSharedWebRTCManager } from '@/hooks/connection';
+import { useConnectManager } from '@/hooks/connection';
import { useFileTransferBusiness } from '@/hooks/file-transfer';
import { useTextTransferBusiness } from '@/hooks/text-transfer';
import { Image, MessageSquare, Send } from 'lucide-react';
@@ -31,7 +31,7 @@ export const WebRTCTextSender: React.FC = ({ onRestart, o
const typingTimeoutRef = useRef(null);
// 创建共享连接
- const connection = useSharedWebRTCManager();
+ const connection = useConnectManager();
const { getConnectState } = connection;
diff --git a/chuan-next/src/hooks/connection/index.ts b/chuan-next/src/hooks/connection/index.ts
index 14fb6d1..e806e58 100644
--- a/chuan-next/src/hooks/connection/index.ts
+++ b/chuan-next/src/hooks/connection/index.ts
@@ -1,6 +1,6 @@
// 连接相关的hooks
export { useConnectionState } from './useConnectionState';
-export { useSharedWebRTCManager } from './useConnectManager';
+export { useConnectManager } from './useConnectManager';
export { useRoomConnection } from './useRoomConnection';
export { useWebRTCSupport } from './useWebRTCSupport';
diff --git a/chuan-next/src/hooks/connection/state/webConnectStore.ts b/chuan-next/src/hooks/connection/state/webConnectStore.ts
index 3cd207a..037c223 100644
--- a/chuan-next/src/hooks/connection/state/webConnectStore.ts
+++ b/chuan-next/src/hooks/connection/state/webConnectStore.ts
@@ -9,7 +9,9 @@ export interface WebConnectState {
isDataChannelConnected: boolean;
isMediaStreamConnected: boolean;
currentConnectType: 'webrtc' | 'websocket';
+ currentIsLocalNetWork: boolean; // 可选,表示当前是否在局域网内
state: RTCDataChannelState;
+ stateMsg: string | null;
error: string | null;
canRetry: boolean; // 新增:是否可以重试
currentRoom: { code: string; role: Role } | null;
@@ -25,11 +27,13 @@ interface WebRTCStore extends WebConnectState {
const initialState: WebConnectState = {
isConnected: false,
isConnecting: false,
+ currentIsLocalNetWork: false,
isWebSocketConnected: false,
isPeerConnected: false,
error: null,
canRetry: false, // 初始状态下不需要重试
currentRoom: null,
+ stateMsg: null,
isDataChannelConnected: false,
isMediaStreamConnected: false,
currentConnectType: 'webrtc',
diff --git a/chuan-next/src/hooks/connection/useConnectManager.ts b/chuan-next/src/hooks/connection/useConnectManager.ts
index 249ea85..0fe46bb 100644
--- a/chuan-next/src/hooks/connection/useConnectManager.ts
+++ b/chuan-next/src/hooks/connection/useConnectManager.ts
@@ -1,3 +1,4 @@
+import { getWsUrl } from '@/lib/config';
import { useCallback, useRef, useState } from 'react';
import { useReadConnectState } from './state/useWebConnectStateManager';
import { WebConnectState } from "./state/webConnectStore";
@@ -7,14 +8,21 @@ import { useWebSocketConnection } from './ws/useWebSocketConnection';
/**
- * 连接管理器 - 代理 WebSocket 和 WebRTC 连接
+ * 连接管理器 - 统一管理 WebSocket 和 WebRTC 连接
* 提供统一的连接接口,内部可以在不同传输方式之间切换
+ * 统一管理 WebSocket 连接,为 WebRTC 和 WebSocket 传输提供共享的 WebSocket 实例
*/
-export function useSharedWebRTCManager(): IWebConnection & IRegisterEventHandler & IGetConnectState {
+export function useConnectManager(): IWebConnection & IRegisterEventHandler & IGetConnectState {
// 当前连接类型
const [currentConnectType, setCurrentConnectType] = useState('webrtc');
- // 连接实例
+ // 统一的 WebSocket 连接引用
+ const wsRef = useRef(null);
+
+ // 当前房间信息
+ const currentRoomRef = useRef<{ code: string; role: Role } | null>(null);
+
+ // 连接实例 - 初始化时不传入 WebSocket
const wsConnection = useWebSocketConnection();
const webrtcConnection = useSharedWebRTCManagerImpl();
@@ -36,7 +44,9 @@ export function useSharedWebRTCManager(): IWebConnection & IRegisterEventHandler
state: 'closed',
error: null,
canRetry: false,
- currentRoom: null
+ currentRoom: null,
+ stateMsg: null,
+ currentIsLocalNetWork: false
});
// 更新连接状态
@@ -47,6 +57,71 @@ export function useSharedWebRTCManager(): IWebConnection & IRegisterEventHandler
};
}, []);
+ // 创建并管理 WebSocket 连接
+ const createWebSocketConnection = useCallback(async (roomCode: string, role: Role) => {
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
+ console.log('[ConnectManager] 已存在 WebSocket 连接,先断开');
+ wsRef.current.close();
+ }
+
+ try {
+ // 构建 WebSocket URL
+ const baseWsUrl = getWsUrl();
+ if (!baseWsUrl) {
+ throw new Error('WebSocket URL未配置');
+ }
+
+ // 构建完整的WebSocket URL
+ const wsUrl = `${baseWsUrl}/api/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`;
+ console.log('[ConnectManager] 创建 WebSocket 连接:', wsUrl);
+
+ const ws = new WebSocket(wsUrl);
+ // 设置二进制数据类型为 ArrayBuffer,避免默认的 Blob 类型
+ ws.binaryType = 'arraybuffer';
+ wsRef.current = ws;
+ currentRoomRef.current = { code: roomCode, role };
+
+ // WebSocket 事件处理
+ ws.onopen = () => {
+ console.log('[ConnectManager] WebSocket 连接成功');
+ updateConnectionState({
+ isWebSocketConnected: true,
+ error: null
+ });
+ };
+
+ ws.onerror = (error) => {
+ console.error('[ConnectManager] WebSocket 连接错误:', error);
+ updateConnectionState({
+ isWebSocketConnected: false,
+ error: 'WebSocket 连接失败'
+ });
+ };
+
+ ws.onclose = (event) => {
+ console.log('[ConnectManager] WebSocket 连接关闭:', event.code, event.reason);
+ updateConnectionState({
+ isWebSocketConnected: false,
+ error: event.wasClean ? null : 'WebSocket 连接意外断开'
+ });
+ };
+
+ return ws;
+ } catch (error) {
+ console.error('[ConnectManager] 创建 WebSocket 连接失败:', error);
+ updateConnectionState({
+ isWebSocketConnected: false,
+ error: '无法建立 WebSocket 连接'
+ });
+ throw error;
+ }
+ }, [updateConnectionState]);
+
+ // 获取 WebSocket 连接
+ const getWebSocketConnection = useCallback(() => {
+ return wsRef.current;
+ }, []);
+
// 切换连接类型
const switchConnectionType = useCallback((type: ConnectType) => {
console.log('[ConnectManager] 切换连接类型:', currentConnectType, '->', type);
@@ -62,8 +137,6 @@ export function useSharedWebRTCManager(): IWebConnection & IRegisterEventHandler
updateConnectionState({
currentConnectType: type,
- isConnected: false,
- isConnecting: false,
error: null
});
}, [currentConnectType, wsConnection, webrtcConnection, updateConnectionState]);
@@ -79,24 +152,42 @@ export function useSharedWebRTCManager(): IWebConnection & IRegisterEventHandler
});
try {
+ // 首先创建统一的 WebSocket 连接
+ const ws = await createWebSocketConnection(roomCode, role);
+
if (currentConnectType === 'webrtc') {
- // 使用当前选择的连接类型进行连接
+ // 将 WebSocket 注入到 WebRTC 连接中
+ webrtcConnection.injectWebSocket(ws);
currentConnectionRef.current = webrtcConnection;
await currentConnectionRef.current.connect(roomCode, role);
-
+ } else {
+ // WebSocket 连接也使用统一的 WebSocket 实例
+ wsConnection.injectWebSocket(ws);
+ currentConnectionRef.current = wsConnection;
+ await currentConnectionRef.current.connect(roomCode, role);
}
} catch (error) {
console.error('[ConnectManager] 连接失败:', error);
-
+ updateConnectionState({
+ isConnecting: false,
+ error: error instanceof Error ? error.message : '连接失败'
+ });
}
- }, [currentConnectType]);
+ }, [currentConnectType, createWebSocketConnection, webrtcConnection, wsConnection, updateConnectionState]);
// 断开连接
const disconnect = useCallback(() => {
console.log('[ConnectManager] 断开连接');
currentConnectionRef.current.disconnect();
+ // 断开 WebSocket 连接
+ if (wsRef.current) {
+ wsRef.current.close(1000, '用户主动断开');
+ wsRef.current = null;
+ }
+ currentRoomRef.current = null;
+
updateConnectionState({
isConnected: false,
isConnecting: false,
diff --git a/chuan-next/src/hooks/connection/webrtc/useSharedWebRTCManager.ts b/chuan-next/src/hooks/connection/webrtc/useSharedWebRTCManager.ts
index 60fac21..458b262 100644
--- a/chuan-next/src/hooks/connection/webrtc/useSharedWebRTCManager.ts
+++ b/chuan-next/src/hooks/connection/webrtc/useSharedWebRTCManager.ts
@@ -11,10 +11,12 @@ import { useWebRTCTrackManager } from './useWebRTCTrackManager';
* 创建单一的 WebRTC 连接实例,供多个业务模块共享使用
* 整合所有模块,提供统一的接口
*
- * webrtc 实现
+ * webrtc 实现 - 初始化时不需要 WebSocket,通过 injectWebSocket 动态注入
*
*/
-export function useSharedWebRTCManagerImpl(): IWebConnection & IRegisterEventHandler & IGetConnectState {
+export function useSharedWebRTCManagerImpl(): IWebConnection & IRegisterEventHandler & IGetConnectState & {
+ injectWebSocket: (ws: WebSocket) => void;
+} {
// 创建各个管理器实例
const stateManager = useWebConnectStateManager();
const dataChannelManager = useWebRTCDataChannelManager(stateManager);
@@ -77,5 +79,8 @@ export function useSharedWebRTCManagerImpl(): IWebConnection & IRegisterEventHan
// 当前房间信息
currentRoom: connectionCore.getCurrentRoom(),
+
+ // WebSocket 注入方法
+ injectWebSocket: connectionCore.injectWebSocket,
};
}
diff --git a/chuan-next/src/hooks/connection/webrtc/useWebRTCConnectionCore.ts b/chuan-next/src/hooks/connection/webrtc/useWebRTCConnectionCore.ts
index 53e9bd2..376d632 100644
--- a/chuan-next/src/hooks/connection/webrtc/useWebRTCConnectionCore.ts
+++ b/chuan-next/src/hooks/connection/webrtc/useWebRTCConnectionCore.ts
@@ -1,5 +1,4 @@
-import { getWsUrl } from '@/lib/config';
import { useCallback, useRef } from 'react';
import { getIceServersConfig } from '../../settings/useIceServersConfig';
import { IWebConnectStateManager } from '../state/useWebConnectStateManager';
@@ -30,18 +29,24 @@ export interface WebRTCConnectionCore {
// 设置断开连接回调
setOnDisconnectCallback: (callback: () => void) => void;
+
+ // 动态注入 WebSocket 连接
+ injectWebSocket: (ws: WebSocket) => void;
}
/**
* WebRTC 核心连接管理 Hook
* 负责基础的 WebRTC 连接管理,包括 WebSocket 连接、PeerConnection 创建和管理
+ * 初始化时不需要 WebSocket,可以通过 injectWebSocket 动态注入
*/
export function useWebRTCConnectionCore(
stateManager: IWebConnectStateManager,
dataChannelManager: WebRTCDataChannelManager,
trackManager: WebRTCTrackManager
): WebRTCConnectionCore {
+ // WebSocket 连接引用,初始为空
const wsRef = useRef(null);
+ const isExternalWebSocket = useRef(false);
const pcRef = useRef(null);
const timeoutRef = useRef(null);
@@ -81,7 +86,9 @@ export function useWebRTCConnectionCore(
}
}
- if (wsRef.current) {
+ // 如果是外部 WebSocket,不关闭连接,只是清理引用
+ // 外部 WebSocket 的生命周期由外部管理
+ if (!isExternalWebSocket.current && wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
@@ -260,275 +267,309 @@ export function useWebRTCConnectionCore(
isUserDisconnecting.current = false;
try {
- // 连接 WebSocket - 使用动态URL
- const baseWsUrl = getWsUrl();
- if (!baseWsUrl) {
- throw new Error('WebSocket URL未配置');
- }
-
- // 构建完整的WebSocket URL
- const wsUrl = `${baseWsUrl}/api/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`;
- console.log('[ConnectionCore] 🌐 连接WebSocket:', wsUrl);
- const ws = new WebSocket(wsUrl);
- wsRef.current = ws;
-
// 保存重新连接状态,供后续使用
const reconnectState = { isReconnect, role };
- // WebSocket 事件处理
- ws.onopen = () => {
- console.log('[ConnectionCore] ✅ WebSocket 连接已建立,房间准备就绪');
+ // 必须使用注入的 WebSocket 连接
+ if (!wsRef.current) {
+ throw new Error('WebSocket 连接未注入,请先调用 injectWebSocket 方法');
+ }
+
+ const ws = wsRef.current;
+ console.log('[ConnectionCore] 使用注入的 WebSocket 连接,状态:', ws.readyState);
+
+ // 检查 WebSocket 是否已经连接
+ if (ws.readyState === WebSocket.OPEN) {
+ console.log('[ConnectionCore] WebSocket 已连接,房间准备就绪');
stateManager.updateState({
isWebSocketConnected: true,
isConnecting: false, // WebSocket连接成功即表示初始连接完成
isConnected: true // 可以开始后续操作
});
+ } else if (ws.readyState === WebSocket.CONNECTING) {
+ // 如果 WebSocket 还在连接中,等待连接成功
+ console.log('[ConnectionCore] WebSocket 连接中,等待连接完成');
+ stateManager.updateState({ isConnecting: true, error: null });
- // 如果是重新连接且是发送方,检查是否有接收方在等待
- if (reconnectState.isReconnect && reconnectState.role === 'sender') {
- console.log('[ConnectionCore] 🔄 发送方重新连接,检查是否有接收方在等待');
- // 这里不需要立即创建PeerConnection,等待接收方加入的通知
- }
- };
+ // 设置 WebSocket 的事件处理
+ const originalOnOpen = ws.onopen;
+ ws.onopen = (event) => {
+ console.log('[ConnectionCore] ✅ WebSocket 连接已建立,房间准备就绪');
+ stateManager.updateState({
+ isWebSocketConnected: true,
+ isConnecting: false, // WebSocket连接成功即表示初始连接完成
+ isConnected: true // 可以开始后续操作
+ });
- ws.onmessage = async (event) => {
- try {
- const message = JSON.parse(event.data);
- console.log('[ConnectionCore] 📨 收到信令消息:', message.type);
+ // 调用原始处理器
+ if (originalOnOpen) {
+ originalOnOpen.call(ws, event);
+ }
+ };
+ } else {
+ throw new Error('WebSocket 连接状态异常: ' + ws.readyState);
+ }
- switch (message.type) {
- case 'peer-joined':
- // 对方加入房间的通知
- console.log('[ConnectionCore] 👥 对方已加入房间,角色:', message.payload?.role);
- if (role === 'sender' && message.payload?.role === 'receiver') {
- console.log('[ConnectionCore] 🚀 接收方已连接,发送方开始建立P2P连接');
- // 确保WebSocket连接状态正确更新
- stateManager.updateState({
- isWebSocketConnected: true,
- isConnected: true,
- isPeerConnected: true // 标记对方已加入,可以开始P2P
- });
+ // 设置 WebSocket 消息处理
+ if (ws) {
+ // 如果是外部 WebSocket,可能已经有事件处理器,我们需要保存它们
+ const originalOnMessage = ws.onmessage;
+ const originalOnError = ws.onerror;
+ const originalOnClose = ws.onclose;
- // 如果是重新连接,先清理旧的PeerConnection
- if (reconnectState.isReconnect && pcRef.current) {
- console.log('[ConnectionCore] 🔄 重新连接:清理旧的PeerConnection');
- pcRef.current.close();
- pcRef.current = null;
- }
+ ws.onmessage = async (event: MessageEvent) => {
+ try {
+ const message = JSON.parse(event.data);
+ console.log('[ConnectionCore] 📨 收到信令消息:', message.type);
- // 对方加入后,创建PeerConnection
- const pc = createPeerConnection(ws, role, reconnectState.isReconnect);
+ switch (message.type) {
+ case 'peer-joined':
+ // 对方加入房间的通知
+ console.log('[ConnectionCore] 👥 对方已加入房间,角色:', message.payload?.role);
+ if (role === 'sender' && message.payload?.role === 'receiver') {
+ console.log('[ConnectionCore] 🚀 接收方已连接,发送方开始建立P2P连接');
+ // 确保WebSocket连接状态正确更新
+ stateManager.updateState({
+ isWebSocketConnected: true,
+ isConnected: true,
+ isPeerConnected: true // 标记对方已加入,可以开始P2P
+ });
- // 设置轨道管理器的引用
- trackManager.setPeerConnection(pc);
- trackManager.setWebSocket(ws);
+ // 如果是重新连接,先清理旧的PeerConnection
+ if (reconnectState.isReconnect && pcRef.current) {
+ console.log('[ConnectionCore] 🔄 重新连接:清理旧的PeerConnection');
+ pcRef.current.close();
+ pcRef.current = null;
+ }
- // 发送方创建offer建立基础P2P连接
- try {
- console.log('[ConnectionCore] 📡 创建基础P2P连接offer');
- await trackManager.createOffer(pc, ws);
- } catch (error) {
- console.error('[ConnectionCore] 创建基础P2P连接失败:', error);
- }
- } else if (role === 'receiver' && message.payload?.role === 'sender') {
- console.log('[ConnectionCore] 🚀 发送方已连接,接收方准备接收P2P连接');
- // 确保WebSocket连接状态正确更新
- stateManager.updateState({
- isWebSocketConnected: true,
- isConnected: true,
- isPeerConnected: true // 标记对方已加入
- });
-
- // 如果是重新连接,先清理旧的PeerConnection
- if (reconnectState.isReconnect && pcRef.current) {
- console.log('[ConnectionCore] 🔄 重新连接:清理旧的PeerConnection');
- pcRef.current.close();
- pcRef.current = null;
- }
-
- // 对方加入后,立即创建PeerConnection,准备接收offer
- const pc = createPeerConnection(ws, role, reconnectState.isReconnect);
-
- // 设置轨道管理器的引用
- trackManager.setPeerConnection(pc);
- trackManager.setWebSocket(ws);
-
- // 等待一小段时间确保PeerConnection完全初始化
- setTimeout(() => {
- console.log('[ConnectionCore] ✅ 接收方PeerConnection已准备就绪');
- }, 100);
- }
- break;
-
- case 'offer':
- console.log('[ConnectionCore] 📬 处理offer...');
- // 如果PeerConnection不存在,先创建它
- let pcOffer = pcRef.current;
- if (!pcOffer) {
- console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
- pcOffer = createPeerConnection(ws, role, reconnectState.isReconnect);
-
- // 设置轨道管理器的引用
- trackManager.setPeerConnection(pcOffer);
- trackManager.setWebSocket(ws);
-
- // 等待一小段时间确保PeerConnection完全初始化
- await new Promise(resolve => setTimeout(resolve, 100));
- }
-
- if (pcOffer && pcOffer.signalingState === 'stable') {
- await pcOffer.setRemoteDescription(new RTCSessionDescription(message.payload));
- console.log('[ConnectionCore] ✅ 设置远程描述完成');
-
- const answer = await pcOffer.createAnswer();
- await pcOffer.setLocalDescription(answer);
- console.log('[ConnectionCore] ✅ 创建并设置answer完成');
-
- ws.send(JSON.stringify({ type: 'answer', payload: answer }));
- console.log('[ConnectionCore] 📤 发送 answer');
- } else {
- console.warn('[ConnectionCore] ⚠️ PeerConnection状态不是stable或不存在:', pcOffer?.signalingState);
- }
- break;
-
- case 'answer':
- console.log('[ConnectionCore] 📬 处理answer...');
- let pcAnswer = pcRef.current;
- try {
- // 如果PeerConnection不存在,先创建它
- if (!pcAnswer) {
- console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
- pcAnswer = createPeerConnection(ws, role, reconnectState.isReconnect);
+ // 对方加入后,创建PeerConnection
+ const pc = createPeerConnection(ws, role, reconnectState.isReconnect);
// 设置轨道管理器的引用
- trackManager.setPeerConnection(pcAnswer);
+ trackManager.setPeerConnection(pc);
+ trackManager.setWebSocket(ws);
+
+ // 发送方创建offer建立基础P2P连接
+ try {
+ console.log('[ConnectionCore] 📡 创建基础P2P连接offer');
+ await trackManager.createOffer(pc, ws);
+ } catch (error) {
+ console.error('[ConnectionCore] 创建基础P2P连接失败:', error);
+ }
+ } else if (role === 'receiver' && message.payload?.role === 'sender') {
+ console.log('[ConnectionCore] 🚀 发送方已连接,接收方准备接收P2P连接');
+ // 确保WebSocket连接状态正确更新
+ stateManager.updateState({
+ isWebSocketConnected: true,
+ isConnected: true,
+ isPeerConnected: true // 标记对方已加入
+ });
+
+ // 如果是重新连接,先清理旧的PeerConnection
+ if (reconnectState.isReconnect && pcRef.current) {
+ console.log('[ConnectionCore] 🔄 重新连接:清理旧的PeerConnection');
+ pcRef.current.close();
+ pcRef.current = null;
+ }
+
+ // 对方加入后,立即创建PeerConnection,准备接收offer
+ const pc = createPeerConnection(ws, role, reconnectState.isReconnect);
+
+ // 设置轨道管理器的引用
+ trackManager.setPeerConnection(pc);
+ trackManager.setWebSocket(ws);
+
+ // 等待一小段时间确保PeerConnection完全初始化
+ setTimeout(() => {
+ console.log('[ConnectionCore] ✅ 接收方PeerConnection已准备就绪');
+ }, 100);
+ }
+ break;
+
+ case 'offer':
+ console.log('[ConnectionCore] 📬 处理offer...');
+ // 如果PeerConnection不存在,先创建它
+ let pcOffer = pcRef.current;
+ if (!pcOffer) {
+ console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
+ pcOffer = createPeerConnection(ws, role, reconnectState.isReconnect);
+
+ // 设置轨道管理器的引用
+ trackManager.setPeerConnection(pcOffer);
trackManager.setWebSocket(ws);
// 等待一小段时间确保PeerConnection完全初始化
await new Promise(resolve => setTimeout(resolve, 100));
}
- if (pcAnswer) {
- const signalingState = pcAnswer.signalingState;
- // 如果状态是stable,可能是因为之前的offer已经完成,需要重新创建offer
- if (signalingState === 'stable') {
- console.log('[ConnectionCore] 🔄 PeerConnection状态为stable,重新创建offer');
- try {
- await trackManager.createOffer(pcAnswer, ws);
- // 等待一段时间让ICE候选收集完成
- await new Promise(resolve => setTimeout(resolve, 500));
+ if (pcOffer && pcOffer.signalingState === 'stable') {
+ await pcOffer.setRemoteDescription(new RTCSessionDescription(message.payload));
+ console.log('[ConnectionCore] ✅ 设置远程描述完成');
- // 现在状态应该是have-local-offer,可以处理answer
- if (pcAnswer.signalingState === 'have-local-offer') {
- await pcAnswer.setRemoteDescription(new RTCSessionDescription(message.payload));
- console.log('[ConnectionCore] ✅ answer 处理完成');
- } else {
- console.warn('[ConnectionCore] ⚠️ 重新创建offer后状态仍然不是have-local-offer:', pcAnswer.signalingState);
- }
- } catch (error) {
- console.error('[ConnectionCore] ❌ 重新创建offer失败:', error);
- }
- } else if (signalingState === 'have-local-offer') {
- await pcAnswer.setRemoteDescription(new RTCSessionDescription(message.payload));
- console.log('[ConnectionCore] ✅ answer 处理完成');
- } else {
- console.warn('[ConnectionCore] ⚠️ PeerConnection状态异常:', signalingState);
- }
+ const answer = await pcOffer.createAnswer();
+ await pcOffer.setLocalDescription(answer);
+ console.log('[ConnectionCore] ✅ 创建并设置answer完成');
+
+ ws.send(JSON.stringify({ type: 'answer', payload: answer }));
+ console.log('[ConnectionCore] 📤 发送 answer');
+ } else {
+ console.warn('[ConnectionCore] ⚠️ PeerConnection状态不是stable或不存在:', pcOffer?.signalingState);
}
- } catch (error) {
- console.error('[ConnectionCore] ❌ 处理answer失败:', error);
- if (error instanceof Error && error.message.includes('Failed to set local answer sdp')) {
- console.warn('[ConnectionCore] ⚠️ Answer处理失败,可能是连接状态变化导致的');
- // 清理连接状态,让客户端重新连接
- stateManager.updateState({ error: 'WebRTC连接状态异常,请重新连接', isPeerConnected: false });
- }
- }
- break;
+ break;
- case 'ice-candidate':
- let pcIce = pcRef.current;
- if (!pcIce) {
- console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
- pcIce = createPeerConnection(ws, role, reconnectState.isReconnect);
-
- // 等待一小段时间确保PeerConnection完全初始化
- await new Promise(resolve => setTimeout(resolve, 100));
- }
-
- if (pcIce && message.payload) {
+ case 'answer':
+ console.log('[ConnectionCore] 📬 处理answer...');
+ let pcAnswer = pcRef.current;
try {
- // 即使远程描述未设置,也可以先缓存ICE候选
- if (pcIce.remoteDescription) {
- await pcIce.addIceCandidate(new RTCIceCandidate(message.payload));
- console.log('[ConnectionCore] ✅ 添加 ICE 候选成功');
- } else {
- console.log('[ConnectionCore] 📝 远程描述未设置,缓存ICE候选');
- // 可以在这里实现ICE候选缓存机制,等远程描述设置后再添加
+ // 如果PeerConnection不存在,先创建它
+ if (!pcAnswer) {
+ console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
+ pcAnswer = createPeerConnection(ws, role, reconnectState.isReconnect);
+
+ // 设置轨道管理器的引用
+ trackManager.setPeerConnection(pcAnswer);
+ trackManager.setWebSocket(ws);
+
+ // 等待一小段时间确保PeerConnection完全初始化
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+
+ if (pcAnswer) {
+ const signalingState = pcAnswer.signalingState;
+ // 如果状态是stable,可能是因为之前的offer已经完成,需要重新创建offer
+ if (signalingState === 'stable') {
+ console.log('[ConnectionCore] 🔄 PeerConnection状态为stable,重新创建offer');
+ try {
+ await trackManager.createOffer(pcAnswer, ws);
+ // 等待一段时间让ICE候选收集完成
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // 现在状态应该是have-local-offer,可以处理answer
+ if (pcAnswer.signalingState === 'have-local-offer') {
+ await pcAnswer.setRemoteDescription(new RTCSessionDescription(message.payload));
+ console.log('[ConnectionCore] ✅ answer 处理完成');
+ } else {
+ console.warn('[ConnectionCore] ⚠️ 重新创建offer后状态仍然不是have-local-offer:', pcAnswer.signalingState);
+ }
+ } catch (error) {
+ console.error('[ConnectionCore] ❌ 重新创建offer失败:', error);
+ }
+ } else if (signalingState === 'have-local-offer') {
+ await pcAnswer.setRemoteDescription(new RTCSessionDescription(message.payload));
+ console.log('[ConnectionCore] ✅ answer 处理完成');
+ } else {
+ console.warn('[ConnectionCore] ⚠️ PeerConnection状态异常:', signalingState);
+ }
+ }
+ } catch (error) {
+ console.error('[ConnectionCore] ❌ 处理answer失败:', error);
+ if (error instanceof Error && error.message.includes('Failed to set local answer sdp')) {
+ console.warn('[ConnectionCore] ⚠️ Answer处理失败,可能是连接状态变化导致的');
+ // 清理连接状态,让客户端重新连接
+ stateManager.updateState({ error: 'WebRTC连接状态异常,请重新连接', isPeerConnected: false });
}
- } catch (err) {
- console.warn('[ConnectionCore] ⚠️ 添加 ICE 候选失败:', err);
}
- } else {
- console.warn('[ConnectionCore] ⚠️ ICE候选无效或PeerConnection不存在');
- }
- break;
+ break;
- case 'error':
- console.error('[ConnectionCore] ❌ 信令服务器错误:', message.error);
- stateManager.updateState({ error: message.error, isConnecting: false, canRetry: true });
- break;
+ case 'ice-candidate':
+ let pcIce = pcRef.current;
+ if (!pcIce) {
+ console.log('[ConnectionCore] 🔧 PeerConnection不存在,先创建它');
+ pcIce = createPeerConnection(ws, role, reconnectState.isReconnect);
- case 'disconnection':
- console.log('[ConnectionCore] 🔌 对方主动断开连接');
- // 对方断开连接的处理
- stateManager.updateState({
- isPeerConnected: false,
- isConnected: false, // 添加这个状态
- error: '对方已离开房间',
- canRetry: true
- });
- // 清理P2P连接但保持WebSocket连接,允许重新连接
- if (pcRef.current) {
- pcRef.current.close();
- pcRef.current = null;
- }
- // 调用断开连接回调,通知上层应用清除数据
- if (onDisconnectCallback.current) {
- console.log('[ConnectionCore] 📞 调用断开连接回调');
- onDisconnectCallback.current();
- }
- break;
+ // 等待一小段时间确保PeerConnection完全初始化
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
- default:
- console.warn('[ConnectionCore] ⚠️ 未知消息类型:', message.type);
+ if (pcIce && message.payload) {
+ try {
+ // 即使远程描述未设置,也可以先缓存ICE候选
+ if (pcIce.remoteDescription) {
+ await pcIce.addIceCandidate(new RTCIceCandidate(message.payload));
+ console.log('[ConnectionCore] ✅ 添加 ICE 候选成功');
+ } else {
+ console.log('[ConnectionCore] 📝 远程描述未设置,缓存ICE候选');
+ // 可以在这里实现ICE候选缓存机制,等远程描述设置后再添加
+ }
+ } catch (err) {
+ console.warn('[ConnectionCore] ⚠️ 添加 ICE 候选失败:', err);
+ }
+ } else {
+ console.warn('[ConnectionCore] ⚠️ ICE候选无效或PeerConnection不存在');
+ }
+ break;
+
+ case 'error':
+ const errorMessage = message.error || '信令服务器返回未知错误';
+ console.error('[ConnectionCore] ❌ 信令服务器错误:', errorMessage);
+ stateManager.updateState({ error: errorMessage, isConnecting: false, canRetry: true });
+ break;
+
+ case 'disconnection':
+ console.log('[ConnectionCore] 🔌 对方主动断开连接');
+ // 对方断开连接的处理
+ stateManager.updateState({
+ isPeerConnected: false,
+ isConnected: false, // 添加这个状态
+ error: '对方已离开房间',
+ canRetry: true
+ });
+ // 清理P2P连接但保持WebSocket连接,允许重新连接
+ if (pcRef.current) {
+ pcRef.current.close();
+ pcRef.current = null;
+ }
+ // 调用断开连接回调,通知上层应用清除数据
+ if (onDisconnectCallback.current) {
+ console.log('[ConnectionCore] 📞 调用断开连接回调');
+ onDisconnectCallback.current();
+ }
+ break;
+
+ default:
+ console.warn('[ConnectionCore] ⚠️ 未知消息类型:', message.type);
+ }
+ } catch (error) {
+ console.error('[ConnectionCore] ❌ 处理信令消息失败:', error);
+ stateManager.updateState({ error: '信令处理失败: ' + error, isConnecting: false, canRetry: true });
}
- } catch (error) {
- console.error('[ConnectionCore] ❌ 处理信令消息失败:', error);
- stateManager.updateState({ error: '信令处理失败: ' + error, isConnecting: false, canRetry: true });
+ };
+
+ // 对于外部WebSocket,需要设置错误和关闭事件处理器
+ if (isExternalWebSocket.current) {
+ ws.onerror = (error: Event) => {
+ console.error('[ConnectionCore] ❌ WebSocket 错误:', error);
+ stateManager.updateState({ error: 'WebSocket连接失败', isConnecting: false, canRetry: true });
+
+ // 调用原始错误处理器
+ if (originalOnError) {
+ originalOnError.call(ws, error);
+ }
+ };
+
+ ws.onclose = (event: CloseEvent) => {
+ console.log('[ConnectionCore] 🔌 WebSocket 连接已关闭, 代码:', event.code, '原因:', event.reason);
+ stateManager.updateState({ isWebSocketConnected: false });
+
+ // 检查是否是用户主动断开
+ if (isUserDisconnecting.current) {
+ console.log('[ConnectionCore] ✅ 用户主动断开,正常关闭');
+ // 用户主动断开时不显示错误消息
+ return;
+ }
+
+ // 只有在非正常关闭且不是用户主动断开时才显示错误
+ if (event.code !== 1000 && event.code !== 1001) { // 非正常关闭
+ stateManager.updateState({ error: `WebSocket异常关闭 (${event.code}): ${event.reason || '连接意外断开'}`, isConnecting: false, canRetry: true });
+ }
+
+ // 调用原始关闭处理器
+ if (originalOnClose) {
+ originalOnClose.call(ws, event);
+ }
+ };
}
- };
-
- ws.onerror = (error) => {
- console.error('[ConnectionCore] ❌ WebSocket 错误:', error);
- stateManager.updateState({ error: 'WebSocket连接失败', isConnecting: false, canRetry: true });
- };
-
- ws.onclose = (event) => {
- console.log('[ConnectionCore] 🔌 WebSocket 连接已关闭, 代码:', event.code, '原因:', event.reason);
- stateManager.updateState({ isWebSocketConnected: false });
-
- // 检查是否是用户主动断开
- if (isUserDisconnecting.current) {
- console.log('[ConnectionCore] ✅ 用户主动断开,正常关闭');
- // 用户主动断开时不显示错误消息
- return;
- }
-
- // 只有在非正常关闭且不是用户主动断开时才显示错误
- if (event.code !== 1000 && event.code !== 1001) { // 非正常关闭
- stateManager.updateState({ error: `WebSocket异常关闭 (${event.code}): ${event.reason || '连接意外断开'}`, isConnecting: false, canRetry: true });
- }
- };
+ }
} catch (error) {
console.error('[ConnectionCore] 连接失败:', error);
@@ -593,6 +634,13 @@ export function useWebRTCConnectionCore(
onDisconnectCallback.current = callback;
}, []);
+ // 动态注入 WebSocket 连接
+ const injectWebSocket = useCallback((ws: WebSocket) => {
+ console.log('[ConnectionCore] 注入外部 WebSocket 连接');
+ wsRef.current = ws;
+ isExternalWebSocket.current = true;
+ }, []);
+
return {
connect,
disconnect,
@@ -601,5 +649,6 @@ export function useWebRTCConnectionCore(
getWebSocket,
getCurrentRoom,
setOnDisconnectCallback,
+ injectWebSocket,
};
}
\ No newline at end of file
diff --git a/chuan-next/src/hooks/connection/webrtc/useWebRTCDataChannelManager.ts b/chuan-next/src/hooks/connection/webrtc/useWebRTCDataChannelManager.ts
index e927b36..0d7d838 100644
--- a/chuan-next/src/hooks/connection/webrtc/useWebRTCDataChannelManager.ts
+++ b/chuan-next/src/hooks/connection/webrtc/useWebRTCDataChannelManager.ts
@@ -53,7 +53,8 @@ export function useWebRTCDataChannelManager(
error: null,
isConnecting: false,
canRetry: false,
- state: 'open'
+ state: 'open',
+ stateMsg: "数据通道已打开"
});
// 如果是重新连接,触发数据同步
diff --git a/chuan-next/src/hooks/connection/ws/useWebSocketConnection.ts b/chuan-next/src/hooks/connection/ws/useWebSocketConnection.ts
index 8ce1687..f2c8dc8 100644
--- a/chuan-next/src/hooks/connection/ws/useWebSocketConnection.ts
+++ b/chuan-next/src/hooks/connection/ws/useWebSocketConnection.ts
@@ -1,12 +1,14 @@
import { useCallback, useEffect, useRef } from 'react';
+import { useWebConnectStateManager } from '../state/useWebConnectStateManager';
import { WebConnectState } from '../state/webConnectStore';
import { ConnectType, DataHandler, IWebConnection, IWebMessage, MessageHandler, Role } from '../types';
/**
* WebSocket 连接管理器
* 实现 IWebConnection 接口,提供基于 WebSocket 的数据传输
+ * 支持注入外部 WebSocket 连接
*/
-export function useWebSocketConnection(): IWebConnection {
+export function useWebSocketConnection(): IWebConnection & { injectWebSocket: (ws: WebSocket) => void } {
const wsRef = useRef(null);
const currentRoomRef = useRef<{ code: string; role: Role } | null>(null);
@@ -17,96 +19,73 @@ export function useWebSocketConnection(): IWebConnection {
// 断开连接回调
const onDisconnectCallback = useRef<(() => void) | null>(null);
- // 连接状态
- const connectionState = useRef({
- isConnected: false,
- isConnecting: false,
- isWebSocketConnected: false,
- isPeerConnected: false,
- isDataChannelConnected: false,
- isMediaStreamConnected: false,
- currentConnectType: 'websocket',
- state: 'closed',
- error: null,
- canRetry: false,
- currentRoom: null
- });
+ // 全局状态管理器
+ const stateManager = useWebConnectStateManager();
- // 更新连接状态
+ // 创建稳定的状态管理器引用,避免无限循环
+ const stateManagerRef = useRef(stateManager);
+ stateManagerRef.current = stateManager;
+
+ // 缓存上次的状态,用于比较是否真正改变
+ const lastStateRef = useRef>({});
+
+ // 智能状态更新 - 只在状态真正改变时才更新,使用稳定引用
const updateState = useCallback((updates: Partial) => {
- connectionState.current = {
- ...connectionState.current,
- ...updates
- };
- }, []);
+ // 检查状态是否真正改变
+ const hasChanged = Object.keys(updates).some(key => {
+ const typedKey = key as keyof WebConnectState;
+ return lastStateRef.current[typedKey] !== updates[typedKey];
+ });
+
+ if (hasChanged) {
+ console.log('[WebSocket] 状态更新:', updates);
+ lastStateRef.current = { ...lastStateRef.current, ...updates };
+ stateManagerRef.current.updateState(updates);
+ } else {
+ console.log('[WebSocket] 状态未改变,跳过更新:', updates);
+ }
+ }, []); // 空依赖数组,使用 ref 访问最新的 stateManager
// 连接到房间
const connect = useCallback(async (roomCode: string, role: Role) => {
- if (wsRef.current?.readyState === WebSocket.OPEN) {
- console.log('[WebSocket] 已存在连接,先断开');
- disconnect();
+ // 检查是否已经注入了 WebSocket
+ if (!wsRef.current) {
+ throw new Error('[WebSocket] 尚未注入 WebSocket 连接,请先调用 injectWebSocket');
+ }
+
+ const ws = wsRef.current;
+
+ // 检查 WebSocket 状态
+ if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
+ throw new Error('[WebSocket] 注入的 WebSocket 连接已关闭');
}
updateState({ isConnecting: true, error: null, canRetry: false });
currentRoomRef.current = { code: roomCode, role };
try {
- // 构建 WebSocket URL
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
- const host = window.location.host;
- const wsUrl = `${protocol}//${host}/api/ws/${roomCode}?role=${role}`;
+ console.log('[WebSocket] 使用注入的 WebSocket 连接到房间:', roomCode, '角色:', role);
- console.log('[WebSocket] 连接到:', wsUrl);
-
- const ws = new WebSocket(wsUrl);
- wsRef.current = ws;
-
- // 连接成功
- ws.onopen = () => {
- console.log('[WebSocket] 连接成功');
+ // 如果 WebSocket 已经连接,直接更新状态
+ if (ws.readyState === WebSocket.OPEN) {
+ console.log('[WebSocket] WebSocket 已连接,直接设置为已连接状态');
updateState({
isConnected: true,
isConnecting: false,
isWebSocketConnected: true,
+ isPeerConnected: true, // 欺骗 UI,让 WebRTC 相关功能正常工作
+ isDataChannelConnected: true, // 欺骗 UI,WebSocket 也能传输数据
+ isMediaStreamConnected: true, // 欺骗 UI,保证所有功能可用
+ state: 'open', // RTCDataChannelState.open
error: null,
canRetry: false
});
- };
-
- // 接收消息
- ws.onmessage = (event) => {
- handleMessage(event);
- };
-
- // 连接错误
- ws.onerror = (error) => {
- console.error('[WebSocket] 连接错误:', error);
- updateState({
- isConnected: false,
- isConnecting: false,
- isWebSocketConnected: false,
- error: 'WebSocket 连接失败',
- canRetry: true
- });
- };
-
- // 连接关闭
- ws.onclose = (event) => {
- console.log('[WebSocket] 连接关闭:', event.code, event.reason);
- updateState({
- isConnected: false,
- isConnecting: false,
- isWebSocketConnected: false,
- error: event.wasClean ? null : 'WebSocket 连接意外断开',
- canRetry: !event.wasClean
- });
-
- // 调用断开连接回调
- if (onDisconnectCallback.current) {
- console.log('[WebSocket] 调用断开连接回调');
- onDisconnectCallback.current();
- }
- };
+ } else if (ws.readyState === WebSocket.CONNECTING) {
+ console.log('[WebSocket] WebSocket 正在连接中,等待连接完成');
+ // WebSocket 正在连接中,等待 onopen 事件
+ } else {
+ throw new Error('[WebSocket] WebSocket 状态异常: ' + ws.readyState);
+ }
} catch (error) {
console.error('[WebSocket] 连接异常:', error);
@@ -114,33 +93,28 @@ export function useWebSocketConnection(): IWebConnection {
isConnected: false,
isConnecting: false,
isWebSocketConnected: false,
- error: '无法建立 WebSocket 连接',
+ isPeerConnected: false, // 重置所有 WebRTC 相关状态
+ isDataChannelConnected: false,
+ isMediaStreamConnected: false,
+ state: 'closed', // RTCDataChannelState.closed
+ error: error instanceof Error ? error.message : '无法使用注入的 WebSocket 连接',
canRetry: true
});
+ throw error;
}
}, [updateState]);
// 处理收到的消息
- const handleMessage = useCallback((event: MessageEvent) => {
+ const handleMessage = useCallback(async (event: MessageEvent) => {
try {
- if (typeof event.data === 'string') {
- // JSON 消息
- const message = JSON.parse(event.data) as IWebMessage;
- console.log('[WebSocket] 收到消息:', message.type, message.channel || 'default');
+ console.log('[WebSocket] 收到消息事件:', typeof event.data, event.data.constructor?.name,
+ event.data instanceof ArrayBuffer ? `ArrayBuffer ${event.data.byteLength} bytes` :
+ event.data instanceof Blob ? `Blob ${event.data.size} bytes` : 'JSON');
- // 根据通道分发消息
- if (message.channel) {
- const handler = messageHandlers.current.get(message.channel);
- if (handler) {
- handler(message);
- }
- } else {
- // 广播给所有处理器
- messageHandlers.current.forEach(handler => handler(message));
- }
- } else if (event.data instanceof ArrayBuffer) {
- // 二进制数据
- console.log('[WebSocket] 收到二进制数据:', event.data.byteLength, 'bytes');
+ // 处理二进制数据 - 支持 ArrayBuffer 和 Blob
+ if (event.data instanceof ArrayBuffer) {
+ // 直接的 ArrayBuffer 数据
+ console.log('[WebSocket] 收到 ArrayBuffer 数据:', event.data.byteLength, 'bytes');
// 优先发给文件传输处理器
const fileHandler = dataHandlers.current.get('file-transfer');
@@ -153,6 +127,75 @@ export function useWebSocketConnection(): IWebConnection {
firstHandler(event.data);
}
}
+ } else if (event.data instanceof Blob) {
+ // Blob 数据,需要转换为 ArrayBuffer
+ console.log('[WebSocket] 收到 Blob 数据:', event.data.size, 'bytes,正在转换为 ArrayBuffer');
+
+ try {
+ const arrayBuffer = await event.data.arrayBuffer();
+ console.log('[WebSocket] Blob 转换完成,ArrayBuffer 大小:', arrayBuffer.byteLength, 'bytes');
+
+ // 优先发给文件传输处理器
+ const fileHandler = dataHandlers.current.get('file-transfer');
+ if (fileHandler) {
+ fileHandler(arrayBuffer);
+ } else {
+ // 发给第一个处理器
+ const firstHandler = dataHandlers.current.values().next().value;
+ if (firstHandler) {
+ firstHandler(arrayBuffer);
+ }
+ }
+ } catch (blobError) {
+ console.error('[WebSocket] Blob 转换为 ArrayBuffer 失败:', blobError);
+ }
+ } else if (typeof event.data === 'string') {
+ // JSON 消息
+ const message = JSON.parse(event.data) as IWebMessage;
+
+ // 特殊处理 disconnection 消息 - 与 WebRTC 保持一致
+ if (message.type === 'disconnection') {
+ console.log('[WebSocket] 🔌 对方主动断开连接');
+ // 更新连接状态
+ updateState({
+ isPeerConnected: false,
+ isConnected: false,
+ error: '对方已离开房间',
+ stateMsg: null,
+ canRetry: true
+ });
+
+ // 调用断开连接回调,通知上层应用清除数据
+ if (onDisconnectCallback.current) {
+ console.log('[WebSocket] 📞 调用断开连接回调');
+ onDisconnectCallback.current();
+ }
+ }
+ if (message.type === 'peer-joined') {
+ console.log('[WebSocket] 🎉 对方加入房间')
+ updateState({
+ isPeerConnected: true,
+ isConnected: true,
+ isWebSocketConnected: true,
+ currentConnectType: 'websocket',
+ error: null,
+ stateMsg: '对方已经加入房间',
+ canRetry: true
+ });
+ }
+
+ // 根据通道分发消息
+ if (message.channel) {
+ const handler = messageHandlers.current.get(message.channel);
+ if (handler) {
+ handler(message);
+ }
+ } else {
+ // 广播给所有处理器
+ messageHandlers.current.forEach(handler => handler(message));
+ }
+ } else {
+ console.warn('[WebSocket] 收到未知数据类型:', typeof event.data, event.data.constructor?.name, event.data);
}
} catch (error) {
console.error('[WebSocket] 处理消息失败:', error);
@@ -171,6 +214,10 @@ export function useWebSocketConnection(): IWebConnection {
isConnected: false,
isConnecting: false,
isWebSocketConnected: false,
+ isPeerConnected: false, // 重置所有 WebRTC 相关状态
+ isDataChannelConnected: false,
+ isMediaStreamConnected: false,
+ state: 'closed', // RTCDataChannelState.closed
error: null,
canRetry: false
});
@@ -213,7 +260,6 @@ export function useWebSocketConnection(): IWebConnection {
try {
ws.send(data);
- console.log('[WebSocket] 发送二进制数据:', data.byteLength, 'bytes');
return true;
} catch (error) {
console.error('[WebSocket] 发送数据失败:', error);
@@ -245,14 +291,12 @@ export function useWebSocketConnection(): IWebConnection {
// 获取连接状态
const getConnectState = useCallback((): WebConnectState => {
- return { ...connectionState.current };
+ return { ...stateManagerRef.current.getState() };
}, []);
// 检查是否连接到指定房间
const isConnectedToRoom = useCallback((roomCode: string, role: Role) => {
- return currentRoomRef.current?.code === roomCode &&
- currentRoomRef.current?.role === role &&
- connectionState.current.isConnected;
+ return stateManagerRef.current.isConnectedToRoom(roomCode, role);
}, []);
// 媒体轨道方法(WebSocket 不支持,返回 null)
@@ -279,6 +323,91 @@ export function useWebSocketConnection(): IWebConnection {
return false;
}, []);
+ // 注入外部 WebSocket 连接
+ const injectWebSocket = useCallback((ws: WebSocket) => {
+ console.log('[WebSocket] 注入外部 WebSocket 连接');
+
+ // 如果已有连接,先断开
+ if (wsRef.current) {
+ wsRef.current.close();
+ }
+
+ wsRef.current = ws;
+
+ // 设置事件处理器
+ ws.onopen = () => {
+ console.log('[WebSocket] 注入的 WebSocket 连接成功');
+ updateState({
+ currentConnectType: 'websocket',
+ isConnected: true,
+ isConnecting: false,
+ isWebSocketConnected: true,
+ isPeerConnected: true, // 欺骗 UI,让 WebRTC 相关功能正常工作
+ isDataChannelConnected: true, // 欺骗 UI,WebSocket 也能传输数据
+ isMediaStreamConnected: true, // 欺骗 UI,保证所有功能可用
+ state: 'open', // RTCDataChannelState.open
+ error: null,
+ canRetry: false,
+ });
+ };
+
+ ws.onmessage = (event) => {
+ handleMessage(event);
+ };
+
+ ws.onerror = (error) => {
+ console.error('[WebSocket] 注入的 WebSocket 连接错误:', error);
+ updateState({
+ isConnected: false,
+ isConnecting: false,
+ isWebSocketConnected: false,
+ isPeerConnected: false, // 重置所有 WebRTC 相关状态
+ isDataChannelConnected: false,
+ isMediaStreamConnected: false,
+ state: 'closed', // RTCDataChannelState.closed
+ error: 'WebSocket 连接失败',
+ canRetry: true
+ });
+ };
+
+ ws.onclose = (event) => {
+ console.log('[WebSocket] 注入的 WebSocket 连接关闭:', event.code, event.reason);
+ updateState({
+ isConnected: false,
+ isConnecting: false,
+ isWebSocketConnected: false,
+ isPeerConnected: false, // 重置所有 WebRTC 相关状态
+ isDataChannelConnected: false,
+ isMediaStreamConnected: false,
+ state: 'closed', // RTCDataChannelState.closed
+ error: event.wasClean ? null : 'WebSocket 连接意外断开',
+ canRetry: !event.wasClean
+ });
+
+ // 调用断开连接回调
+ if (onDisconnectCallback.current) {
+ console.log('[WebSocket] 调用断开连接回调');
+ onDisconnectCallback.current();
+ }
+ };
+
+ // 如果 WebSocket 已经连接,立即更新状态
+ if (ws.readyState === WebSocket.OPEN) {
+ console.log('[WebSocket] 注入的 WebSocket 已连接,立即更新状态');
+ updateState({
+ isConnected: true,
+ isConnecting: false,
+ isWebSocketConnected: true,
+ isPeerConnected: true, // 欺骗 UI,让 WebRTC 相关功能正常工作
+ isDataChannelConnected: true, // 欺骗 UI,WebSocket 也能传输数据
+ isMediaStreamConnected: true, // 欺骗 UI,保证所有功能可用
+ state: 'open', // RTCDataChannelState.open
+ error: null,
+ canRetry: false
+ });
+ }
+ }, [handleMessage, updateState]);
+
// 设置断开连接回调
const setOnDisconnectCallback = useCallback((callback: () => void) => {
onDisconnectCallback.current = callback;
@@ -287,9 +416,15 @@ export function useWebSocketConnection(): IWebConnection {
// 清理连接
useEffect(() => {
return () => {
- disconnect();
+ // 清理时直接关闭 WebSocket,不调用 disconnect 避免状态更新循环
+ if (wsRef.current) {
+ console.log('[WebSocket] 组件卸载,清理 WebSocket 连接');
+ wsRef.current.close(1000, '组件卸载');
+ wsRef.current = null;
+ }
+ currentRoomRef.current = null;
};
- }, [disconnect]);
+ }, []); // 空依赖数组,只在组件挂载和卸载时执行
return {
connectType: 'websocket' as ConnectType,
@@ -309,5 +444,6 @@ export function useWebSocketConnection(): IWebConnection {
getPeerConnection,
createOfferNow,
setOnDisconnectCallback,
+ injectWebSocket,
};
}
\ No newline at end of file
diff --git a/chuan-next/src/hooks/desktop-share/useDesktopShareBusiness.ts b/chuan-next/src/hooks/desktop-share/useDesktopShareBusiness.ts
index 109892d..94c9d4c 100644
--- a/chuan-next/src/hooks/desktop-share/useDesktopShareBusiness.ts
+++ b/chuan-next/src/hooks/desktop-share/useDesktopShareBusiness.ts
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
-import { useSharedWebRTCManager } from '../connection/useConnectManager';
+import { useConnectManager } from '../connection';
interface DesktopShareState {
isSharing: boolean;
@@ -11,7 +11,7 @@ interface DesktopShareState {
}
export function useDesktopShareBusiness() {
- const webRTC = useSharedWebRTCManager();
+ const webRTC = useConnectManager();
const [state, setState] = useState({
isSharing: false,
isViewing: false,
@@ -46,14 +46,14 @@ export function useDesktopShareBusiness() {
webRTC.onTrack((event: RTCTrackEvent) => {
console.log('[DesktopShare] 🎥 收到远程轨道:', event.track.kind, event.track.id, '状态:', event.track.readyState);
console.log('[DesktopShare] 远程流数量:', event.streams.length);
-
+
if (event.streams.length > 0) {
const remoteStream = event.streams[0];
console.log('[DesktopShare] 🎬 设置远程流,轨道数量:', remoteStream.getTracks().length);
remoteStream.getTracks().forEach(track => {
console.log('[DesktopShare] 远程轨道:', track.kind, track.id, '启用:', track.enabled, '状态:', track.readyState);
});
-
+
// 确保轨道已启用
remoteStream.getTracks().forEach(track => {
if (!track.enabled) {
@@ -61,7 +61,7 @@ export function useDesktopShareBusiness() {
track.enabled = true;
}
});
-
+
handleRemoteStream(remoteStream);
} else {
console.warn('[DesktopShare] ⚠️ 收到轨道但没有关联的流');
@@ -69,7 +69,7 @@ export function useDesktopShareBusiness() {
try {
const newStream = new MediaStream([event.track]);
console.log('[DesktopShare] 🔄 从轨道创建新流:', newStream.id);
-
+
// 确保轨道已启用
newStream.getTracks().forEach(track => {
if (!track.enabled) {
@@ -77,7 +77,7 @@ export function useDesktopShareBusiness() {
track.enabled = true;
}
});
-
+
handleRemoteStream(newStream);
} catch (error) {
console.error('[DesktopShare] ❌ 从轨道创建流失败:', error);
@@ -112,27 +112,27 @@ export function useDesktopShareBusiness() {
// 设置视频轨道发送
const setupVideoSending = useCallback(async (stream: MediaStream) => {
console.log('[DesktopShare] 🎬 开始设置视频轨道发送...');
-
+
// 检查P2P连接状态
if (!webRTC.getConnectState().isPeerConnected) {
console.warn('[DesktopShare] ⚠️ P2P连接尚未完全建立,等待连接稳定...');
// 等待连接稳定
await new Promise(resolve => setTimeout(resolve, 1000));
-
+
// 再次检查
if (!webRTC.getConnectState().isPeerConnected) {
console.error('[DesktopShare] ❌ P2P连接仍未建立,无法开始媒体传输');
throw new Error('P2P连接尚未建立');
}
}
-
+
// 移除之前的轨道(如果存在)
if (currentSenderRef.current) {
console.log('[DesktopShare] 🗑️ 移除之前的视频轨道');
webRTC.removeTrack(currentSenderRef.current);
currentSenderRef.current = null;
}
-
+
// 添加新的视频轨道到PeerConnection
const videoTrack = stream.getVideoTracks()[0];
const audioTrack = stream.getAudioTracks()[0];
@@ -169,7 +169,7 @@ export function useDesktopShareBusiness() {
// 轨道添加完成,现在需要重新协商以包含媒体轨道
console.log('[DesktopShare] ✅ 桌面共享轨道添加完成,开始重新协商');
-
+
// 获取PeerConnection实例以便调试
const pc = webRTC.getPeerConnection();
if (pc) {
@@ -180,20 +180,20 @@ export function useDesktopShareBusiness() {
senders: pc.getSenders().length
});
}
-
+
// 等待一小段时间确保轨道完全添加
await new Promise(resolve => setTimeout(resolve, 500));
-
+
// 创建新的offer包含媒体轨道
console.log('[DesktopShare] 📨 创建包含媒体轨道的新offer进行重新协商');
const success = await webRTC.createOfferNow();
if (success) {
console.log('[DesktopShare] ✅ 媒体轨道重新协商成功');
-
+
// 等待重新协商完成
console.log('[DesktopShare] ⏳ 等待重新协商完成...');
await new Promise(resolve => setTimeout(resolve, 2000));
-
+
// 检查连接状态
if (pc) {
console.log('[DesktopShare] 🔍 重新协商后连接状态:', {
@@ -233,7 +233,7 @@ export function useDesktopShareBusiness() {
});
const data = await response.json();
-
+
if (!response.ok) {
throw new Error(data.error || '创建房间失败');
}
@@ -283,12 +283,12 @@ export function useDesktopShareBusiness() {
// 获取桌面流
const stream = await getDesktopStream();
-
+
// 停止之前的流(如果有)- 与switchDesktop保持一致
if (localStreamRef.current) {
localStreamRef.current.getTracks().forEach(track => track.stop());
}
-
+
localStreamRef.current = stream;
console.log('[DesktopShare] ✅ 桌面流获取成功');
@@ -306,13 +306,13 @@ export function useDesktopShareBusiness() {
const errorMessage = error instanceof Error ? error.message : '开始桌面共享失败';
console.error('[DesktopShare] ❌ 开始共享失败:', error);
updateState({ error: errorMessage, isSharing: false });
-
+
// 清理资源
if (localStreamRef.current) {
localStreamRef.current.getTracks().forEach(track => track.stop());
localStreamRef.current = null;
}
-
+
throw error;
}
}, [webRTC, getDesktopStream, setupVideoSending, updateState]);
@@ -333,12 +333,12 @@ export function useDesktopShareBusiness() {
// 获取新的桌面流
const newStream = await getDesktopStream();
-
+
// 停止之前的流
if (localStreamRef.current) {
localStreamRef.current.getTracks().forEach(track => track.stop());
}
-
+
localStreamRef.current = newStream;
console.log('[DesktopShare] ✅ 新桌面流获取成功');
@@ -454,7 +454,7 @@ export function useDesktopShareBusiness() {
updateState({ isViewing: true });
console.log('[DesktopShare] 👁️ 已进入桌面共享观看模式,等待接收流...');
-
+
// 设置一个超时检查,如果长时间没有收到流,输出警告
setTimeout(() => {
if (!state.remoteStream) {
@@ -536,7 +536,7 @@ export function useDesktopShareBusiness() {
// WebRTC连接状态
webRTCError: webRTC.getConnectState().error,
-
+
// 暴露WebRTC连接对象
webRTCConnection: webRTC,
};
diff --git a/chuan-next/src/hooks/file-transfer/useFileStateManager.ts b/chuan-next/src/hooks/file-transfer/useFileStateManager.ts
index 9c778da..a044f7c 100644
--- a/chuan-next/src/hooks/file-transfer/useFileStateManager.ts
+++ b/chuan-next/src/hooks/file-transfer/useFileStateManager.ts
@@ -93,7 +93,6 @@ export const useFileStateManager = ({
const newStatus = progress >= 100 ? 'completed' as const : 'downloading' as const;
setFileList(prev => prev.map(item => {
if (item.id === fileId || item.name === fileName) {
- console.log(`更新文件 ${item.name} 进度: ${item.progress} -> ${progress}${transferSpeed ? `, 速度: ${transferSpeed} B/s` : ''}`);
return {
...item,
progress,
diff --git a/chuan-next/src/hooks/file-transfer/useFileTransferBusiness.ts b/chuan-next/src/hooks/file-transfer/useFileTransferBusiness.ts
index 72ebded..d9b594c 100644
--- a/chuan-next/src/hooks/file-transfer/useFileTransferBusiness.ts
+++ b/chuan-next/src/hooks/file-transfer/useFileTransferBusiness.ts
@@ -76,11 +76,20 @@ type FileProgressCallback = (progressInfo: { fileId: string; fileName: string; p
type FileListReceivedCallback = (fileList: FileInfo[]) => void;
const CHANNEL_NAME = 'file-transfer';
-const CHUNK_SIZE = 256 * 1024; // 256KB
+const WEBRTC_CHUNK_SIZE = 256 * 1024; // 256KB for WebRTC
+const WEBSOCKET_CHUNK_SIZE = 3 * 1024 * 1024; // 3MB for WebSocket
const MAX_RETRIES = 5; // 最大重试次数
const RETRY_DELAY = 1000; // 重试延迟(毫秒)
const ACK_TIMEOUT = 5000; // 确认超时(毫秒)
+/**
+ * 根据连接类型获取块大小
+ */
+function getChunkSize(connectType: string): number {
+ return connectType === 'websocket' ? WEBSOCKET_CHUNK_SIZE : WEBRTC_CHUNK_SIZE;
+}
+
+
/**
* 计算数据的CRC32校验和
*/
@@ -98,19 +107,6 @@ function calculateChecksum(data: ArrayBuffer): string {
return (crc ^ 0xFFFFFFFF).toString(16).padStart(8, '0');
}
-/**
- * 生成简单的校验和(备用方案)
- */
-function simpleChecksum(data: ArrayBuffer): string {
- const buffer = new Uint8Array(data);
- let sum = 0;
-
- for (let i = 0; i < Math.min(buffer.length, 1000); i++) {
- sum += buffer[i];
- }
-
- return sum.toString(16);
-}
/**
* 文件传输业务层
@@ -174,7 +170,7 @@ export function useFileTransferBusiness(connection: IWebConnection) {
});
// 初始化接收进度跟踪
- const totalChunks = Math.ceil(metadata.size / CHUNK_SIZE);
+ const totalChunks = Math.ceil(metadata.size / getChunkSize(connection.connectType));
receiveProgress.current.set(metadata.id, {
fileId: metadata.id,
fileName: metadata.name,
@@ -453,9 +449,10 @@ export function useFileTransferBusiness(connection: IWebConnection) {
}
const actualFileId = fileId || `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
+ const chunkSize = getChunkSize(connection.connectType);
+ const totalChunks = Math.ceil(file.size / chunkSize);
- console.log('开始安全发送文件:', file.name, '文件ID:', actualFileId, '总块数:', totalChunks);
+ console.log('开始安全发送文件:', file.name, '文件ID:', actualFileId, '总块数:', totalChunks, '块大小:', chunkSize);
updateState({ isTransferring: true, progress: 0, error: null });
@@ -503,8 +500,8 @@ export function useFileTransferBusiness(connection: IWebConnection) {
console.warn(`WebRTC 连接暂时断开,但数据通道正在连接,继续尝试发送文件块 ${chunkIndex}`);
}
- const start = chunkIndex * CHUNK_SIZE;
- const end = Math.min(start + CHUNK_SIZE, file.size);
+ const start = chunkIndex * chunkSize;
+ const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const arrayBuffer = await chunk.arrayBuffer();
const checksum = calculateChecksum(arrayBuffer);
@@ -555,8 +552,8 @@ export function useFileTransferBusiness(connection: IWebConnection) {
// 自适应流控:根据传输速度调整发送间隔
if (status.averageSpeed > 0) {
- const chunkSize = Math.min(CHUNK_SIZE, file.size - chunkIndex * CHUNK_SIZE);
- const expectedTime = (chunkSize / 1024) / status.averageSpeed;
+ const currentChunkSize = Math.min(chunkSize, file.size - chunkIndex * chunkSize);
+ const expectedTime = (currentChunkSize / 1024) / status.averageSpeed;
const actualTime = Date.now() - status.lastChunkTime;
const delay = Math.max(0, expectedTime - actualTime);
diff --git a/chuan-next/src/hooks/ui/useTabNavigation.ts b/chuan-next/src/hooks/ui/useTabNavigation.ts
index 45d2cc2..caabcfb 100644
--- a/chuan-next/src/hooks/ui/useTabNavigation.ts
+++ b/chuan-next/src/hooks/ui/useTabNavigation.ts
@@ -1,6 +1,6 @@
import { useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
-import { useSharedWebRTCManager } from '../connection';
+import { useConnectManager } from '../connection';
import { useWebRTCStore } from '../connection/state/webConnectStore';
import { useConfirmDialog } from './useConfirmDialog';
import { FeatureType, useURLHandler } from './useURLHandler';
@@ -29,33 +29,33 @@ export const useTabNavigation = () => {
const [activeTab, setActiveTab] = useState('webrtc');
const [hasInitialized, setHasInitialized] = useState(false);
const { showConfirmDialog, dialogState, closeDialog } = useConfirmDialog();
-
+
// 获取WebRTC全局状态
- const {
- isConnected,
- isConnecting,
+ const {
+ isConnected,
+ isConnecting,
isPeerConnected,
currentRoom,
} = useWebRTCStore();
// 获取WebRTC连接管理器
- const { disconnect: disconnectWebRTC } = useSharedWebRTCManager();
+ const { disconnect: disconnectWebRTC } = useConnectManager();
// 创建一个通用的URL处理器(用于断开连接)
const { hasActiveConnection } = useURLHandler({
featureType: 'webrtc', // 默认值,实际使用时会被覆盖
- onModeChange: () => {},
+ onModeChange: () => { },
});
// 根据URL参数设置初始标签(仅首次加载时)
useEffect(() => {
if (!hasInitialized) {
const urlType = searchParams.get('type');
-
+
console.log('=== HomePage URL处理 ===');
console.log('URL type参数:', urlType);
console.log('所有搜索参数:', Object.fromEntries(searchParams.entries()));
-
+
// 将旧的text类型重定向到message
if (urlType === 'text') {
console.log('检测到text类型,重定向到message标签页');
@@ -70,7 +70,7 @@ export const useTabNavigation = () => {
console.log('没有有效的type参数,使用默认标签页:webrtc(文件传输)');
// 保持默认的webrtc标签
}
-
+
setHasInitialized(true);
}
}, [searchParams, hasInitialized]);
@@ -79,7 +79,7 @@ export const useTabNavigation = () => {
const handleTabChange = useCallback(async (newTab: TabType) => {
console.log('=== Tab切换 ===');
console.log('当前tab:', activeTab, '目标tab:', newTab);
-
+
// 对于任何非WebRTC功能的tab(wechat、settings),如果有活跃连接需要确认
if ((newTab === 'wechat' || newTab === 'settings') && hasActiveConnection()) {
const currentTabName = TAB_NAMES[activeTab];
@@ -91,15 +91,15 @@ export const useTabNavigation = () => {
cancelText: '取消',
type: 'warning'
});
-
+
if (!confirmed) {
return false;
}
-
+
// 断开连接并清除状态
disconnectWebRTC();
console.log(`已清除WebRTC连接状态,切换到${targetTabName}`);
-
+
setActiveTab(newTab);
// 清除URL参数
const newUrl = new URL(window.location.href);
@@ -122,7 +122,7 @@ export const useTabNavigation = () => {
if (hasActiveConnection() && newTab !== activeTab && WEBRTC_FEATURES[newTab]) {
const currentTabName = TAB_NAMES[activeTab];
const targetTabName = TAB_NAMES[newTab];
-
+
const confirmed = await showConfirmDialog({
title: '切换功能确认',
message: `切换到${targetTabName}功能需要关闭当前的${currentTabName}连接,是否继续?`,
@@ -130,7 +130,7 @@ export const useTabNavigation = () => {
cancelText: '取消',
type: 'warning'
});
-
+
if (!confirmed) {
return false;
}
@@ -142,7 +142,7 @@ export const useTabNavigation = () => {
// 执行tab切换
setActiveTab(newTab);
-
+
// 更新URL(对于WebRTC功能)
if (WEBRTC_FEATURES[newTab]) {
const params = new URLSearchParams();
@@ -156,7 +156,7 @@ export const useTabNavigation = () => {
newUrl.search = '';
window.history.pushState({}, '', newUrl.toString());
}
-
+
return true;
}, [activeTab, hasActiveConnection, disconnectWebRTC]);
diff --git a/internal/services/webrtc_service.go b/internal/services/webrtc_service.go
index 6bd92c7..49ad5e5 100644
--- a/internal/services/webrtc_service.go
+++ b/internal/services/webrtc_service.go
@@ -1,6 +1,7 @@
package services
import (
+ "encoding/json"
"fmt"
"log"
"math/rand"
@@ -146,18 +147,36 @@ func (ws *WebRTCService) HandleWebSocket(w http.ResponseWriter, r *http.Request)
// 处理消息
for {
- var msg WebRTCMessage
- err := conn.ReadJSON(&msg)
+ // 首先读取原始消息类型和数据
+ messageType, data, err := conn.ReadMessage()
if err != nil {
log.Printf("读取WebRTC WebSocket消息失败: %v", err)
break
}
- msg.From = clientID
- log.Printf("收到WebRTC信令: 类型=%s, 来自=%s, 房间=%s", msg.Type, clientID, code)
+ if messageType == websocket.TextMessage {
+ // 文本消息,尝试解析为JSON
+ var msg WebRTCMessage
+ if err := json.Unmarshal(data, &msg); err != nil {
+ log.Printf("解析WebRTC JSON消息失败: %v", err)
+ continue
+ }
- // 转发信令消息给对方
- ws.forwardMessage(code, clientID, &msg)
+ msg.From = clientID
+ log.Printf("收到WebRTC信令: 类型=%s, 来自=%s, 房间=%s", msg.Type, clientID, code)
+
+ // 转发信令消息给对方
+ ws.forwardMessage(code, clientID, &msg)
+
+ } else if messageType == websocket.BinaryMessage {
+ // 二进制消息,直接转发
+ log.Printf("收到WebRTC二进制数据: 大小=%d bytes, 来自=%s, 房间=%s", len(data), clientID, code)
+
+ // 转发二进制数据给对方
+ ws.forwardBinaryMessage(code, clientID, data)
+ } else {
+ log.Printf("收到未知消息类型: %d", messageType)
+ }
}
}
@@ -259,6 +278,37 @@ func (ws *WebRTCService) forwardMessage(roomCode string, fromClientID string, ms
}
}
+// 转发二进制消息
+func (ws *WebRTCService) forwardBinaryMessage(roomCode string, fromClientID string, data []byte) {
+ ws.roomsMux.Lock()
+ defer ws.roomsMux.Unlock()
+
+ room := ws.rooms[roomCode]
+ if room == nil {
+ return
+ }
+
+ var targetClient *WebRTCClient
+ if room.Sender != nil && room.Sender.ID == fromClientID {
+ // 消息来自sender,转发给receiver
+ targetClient = room.Receiver
+ } else if room.Receiver != nil && room.Receiver.ID == fromClientID {
+ // 消息来自receiver,转发给sender
+ targetClient = room.Sender
+ }
+
+ if targetClient != nil && targetClient.Connection != nil {
+ err := targetClient.Connection.WriteMessage(websocket.BinaryMessage, data)
+ if err != nil {
+ log.Printf("转发WebRTC二进制数据失败: %v", err)
+ } else {
+ log.Printf("转发WebRTC二进制数据: 大小=%d bytes, 从=%s到=%s", len(data), fromClientID, targetClient.ID)
+ }
+ } else {
+ log.Printf("目标客户端不在线,无法转发二进制数据")
+ }
+}
+
// CreateRoom 创建或获取房间
func (ws *WebRTCService) CreateRoom(code string) {
ws.roomsMux.Lock()