mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-02-04 03:25:03 +08:00
feat: ws实现
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -53,7 +53,8 @@ export function useWebRTCDataChannelManager(
|
||||
error: null,
|
||||
isConnecting: false,
|
||||
canRetry: false,
|
||||
state: 'open'
|
||||
state: 'open',
|
||||
stateMsg: "数据通道已打开"
|
||||
});
|
||||
|
||||
// 如果是重新连接,触发数据同步
|
||||
|
||||
@@ -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, // 欺骗 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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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功能的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]);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user