feat: ws实现

This commit is contained in:
MatrixSeven
2025-09-16 16:41:38 +08:00
parent 15d23de5a7
commit 2fc478e889
18 changed files with 780 additions and 775 deletions

View File

@@ -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 (
<div className={cn(iconClass, 'bg-yellow-500 rounded-full animate-pulse')} />
);
case 'websocket-ready':
return <div className={cn(iconClass, 'bg-orange-500 rounded-full')} />;
case 'error':
return <div className={cn(iconClass, 'bg-red-500 rounded-full')} />;
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;

View File

@@ -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 <Loader2 className="h-4 w-4 animate-spin text-blue-500" />;
}
if (error) {
// 区分信息提示和错误
if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
return <WifiOff className="h-4 w-4 text-yellow-500" />;
}
return <AlertCircle className="h-4 w-4 text-red-500" />;
}
if (isPeerConnected) {
return <Wifi className="h-4 w-4 text-green-500" />;
}
if (isWebSocketConnected) {
return <Wifi className="h-4 w-4 text-yellow-500" />;
}
return <WifiOff className="h-4 w-4 text-gray-400" />;
};
// 状态文本
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 (
<div className={`flex items-center justify-between p-3 bg-white border rounded-lg ${className}`}>
<div className="flex items-center gap-2">
{getStatusIcon()}
<span className={`text-sm font-medium ${getStatusColor()}`}>
{getStatusText()}
</span>
</div>
{/* 连接详细状态指示器 */}
<div className="flex items-center gap-1">
{/* WebSocket状态 */}
<div
className={`w-2 h-2 rounded-full ${
isWebSocketConnected ? 'bg-green-400' : 'bg-gray-300'
}`}
title={isWebSocketConnected ? 'WebSocket已连接' : 'WebSocket未连接'}
/>
{/* P2P状态 */}
<div
className={`w-2 h-2 rounded-full ${
isPeerConnected ? 'bg-green-400' : 'bg-gray-300'
}`}
title={isPeerConnected ? 'P2P连接已建立' : 'P2P连接未建立'}
/>
{/* 重试按钮 */}
{canRetry && (
<button
onClick={handleRetry}
disabled={isConnecting}
className="ml-2 p-1 text-gray-500 hover:text-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
title="重试连接"
>
<RotateCcw className={`h-3 w-3 ${isConnecting ? 'animate-spin' : ''}`} />
</button>
)}
</div>
</div>
);
}
/**
* 简单的连接状态指示器(用于空间受限的地方)
*/
export function WebRTCStatusIndicator({ webrtc, className = '' }: Props) {
const { isPeerConnected, isConnecting, error } = webrtc;
if (error) {
// 区分信息提示和错误
if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
return (
<div className={`flex items-center gap-1 ${className}`}>
<div className="w-2 h-2 bg-yellow-400 rounded-full" />
<span className="text-xs text-yellow-600"></span>
</div>
);
}
return (
<div className={`flex items-center gap-1 ${className}`}>
<div className="w-2 h-2 bg-red-400 rounded-full animate-pulse" />
<span className="text-xs text-red-600"></span>
</div>
);
}
if (isConnecting) {
return (
<div className={`flex items-center gap-1 ${className}`}>
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse" />
<span className="text-xs text-blue-600"></span>
</div>
);
}
if (isPeerConnected) {
return (
<div className={`flex items-center gap-1 ${className}`}>
<div className="w-2 h-2 bg-green-400 rounded-full" />
<span className="text-xs text-green-600"></span>
</div>
);
}
return (
<div className={`flex items-center gap-1 ${className}`}>
<div className="w-2 h-2 bg-gray-300 rounded-full" />
<span className="text-xs text-gray-600"></span>
</div>
);
}

View File

@@ -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<HTMLInputElement>(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,

View File

@@ -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 (
<div className="p-4 max-w-md mx-auto bg-white rounded-lg shadow">
<h2 className="text-xl font-bold mb-4">WebSocket </h2>
{/* 连接状态 */}
<div className="mb-4 p-2 bg-gray-100 rounded">
<p><strong>:</strong> {state.isConnected ? '已连接' : '未连接'}</p>
<p><strong>:</strong> {connection.connectType}</p>
{state.error && (
<p className="text-red-600"><strong>:</strong> {state.error}</p>
)}
{state.currentRoom && (
<p><strong>:</strong> {state.currentRoom.code} ({state.currentRoom.role})</p>
)}
</div>
{/* 连接控制 */}
{!state.isConnected ? (
<div className="space-y-3">
<div>
<label className="block text-sm font-medium mb-1">:</label>
<input
type="text"
value={roomCode}
onChange={(e) => setRoomCode(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded"
placeholder="输入房间代码"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">:</label>
<select
value={role}
onChange={(e) => setRole(e.target.value as 'sender' | 'receiver')}
className="w-full px-3 py-2 border border-gray-300 rounded"
>
<option value="sender"></option>
<option value="receiver"></option>
</select>
</div>
<button
onClick={handleConnect}
disabled={state.isConnecting || !roomCode.trim()}
className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{state.isConnecting ? '连接中...' : '连接'}
</button>
{state.canRetry && (
<button
onClick={connection.retry}
className="w-full px-4 py-2 bg-orange-500 text-white rounded hover:bg-orange-600"
>
</button>
)}
</div>
) : (
<div className="space-y-3">
{/* 消息发送 */}
<div>
<label className="block text-sm font-medium mb-1">:</label>
<div className="flex space-x-2">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="flex-1 px-3 py-2 border border-gray-300 rounded"
placeholder="输入消息内容"
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
/>
<button
onClick={handleSendMessage}
disabled={!message.trim()}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 disabled:opacity-50"
>
</button>
</div>
</div>
{/* 断开连接 */}
<button
onClick={connection.disconnect}
className="w-full px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
</button>
</div>
)}
{/* 功能说明 */}
<div className="mt-4 p-2 bg-blue-50 rounded text-sm">
<p><strong>:</strong></p>
<ul className="list-disc list-inside space-y-1">
<li> WebSocket </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> WebSocket </li>
<li> P2P WebSocket </li>
</ul>
</div>
</div>
);
}

View File

@@ -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<WebRTCTextReceiverProps> = ({
const hasTriedAutoConnect = useRef(false);
// 创建共享连接
const connection = useSharedWebRTCManager();
const connection = useConnectManager();
const {getConnectState} = connection;

View File

@@ -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<WebRTCTextSenderProps> = ({ onRestart, o
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 创建共享连接
const connection = useSharedWebRTCManager();
const connection = useConnectManager();
const { getConnectState } = connection;

View File

@@ -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';

View File

@@ -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',

View File

@@ -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<ConnectType>('webrtc');
// 连接实例
// 统一的 WebSocket 连接引用
const wsRef = useRef<WebSocket | null>(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,

View File

@@ -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,
};
}

View File

@@ -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<WebSocket | null>(null);
const isExternalWebSocket = useRef<boolean>(false);
const pcRef = useRef<RTCPeerConnection | null>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(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,
};
}

View File

@@ -53,7 +53,8 @@ export function useWebRTCDataChannelManager(
error: null,
isConnecting: false,
canRetry: false,
state: 'open'
state: 'open',
stateMsg: "数据通道已打开"
});
// 如果是重新连接,触发数据同步

View File

@@ -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<WebSocket | null>(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<WebConnectState>({
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<Partial<WebConnectState>>({});
// 智能状态更新 - 只在状态真正改变时才更新,使用稳定引用
const updateState = useCallback((updates: Partial<WebConnectState>) => {
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, // 欺骗 UIWebSocket 也能传输数据
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, // 欺骗 UIWebSocket 也能传输数据
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, // 欺骗 UIWebSocket 也能传输数据
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,
};
}

View File

@@ -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<DesktopShareState>({
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,
};

View File

@@ -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,

View File

@@ -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);

View File

@@ -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<TabType>('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功能的tabwechat、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]);

View File

@@ -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()