mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-03-06 23:29:42 +08:00
feat:桌面共享支持
This commit is contained in:
407
chuan-next/src/hooks/webrtc/useDesktopShareBusiness.ts
Normal file
407
chuan-next/src/hooks/webrtc/useDesktopShareBusiness.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import { useSharedWebRTCManager } from './useSharedWebRTCManager';
|
||||
|
||||
interface DesktopShareState {
|
||||
isSharing: boolean;
|
||||
isViewing: boolean;
|
||||
connectionCode: string;
|
||||
remoteStream: MediaStream | null;
|
||||
error: string | null;
|
||||
isWaitingForPeer: boolean; // 新增:是否等待对方连接
|
||||
}
|
||||
|
||||
export function useDesktopShareBusiness() {
|
||||
const webRTC = useSharedWebRTCManager();
|
||||
const [state, setState] = useState<DesktopShareState>({
|
||||
isSharing: false,
|
||||
isViewing: false,
|
||||
connectionCode: '',
|
||||
remoteStream: null,
|
||||
error: null,
|
||||
isWaitingForPeer: false,
|
||||
});
|
||||
|
||||
const localStreamRef = useRef<MediaStream | null>(null);
|
||||
const remoteVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const currentSenderRef = useRef<RTCRtpSender | null>(null);
|
||||
|
||||
const updateState = useCallback((updates: Partial<DesktopShareState>) => {
|
||||
setState(prev => ({ ...prev, ...updates }));
|
||||
}, []);
|
||||
|
||||
// 生成6位房间代码
|
||||
const generateRoomCode = useCallback(() => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
// 获取桌面共享流
|
||||
const getDesktopStream = useCallback(async (): Promise<MediaStream> => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: {
|
||||
cursor: 'always',
|
||||
displaySurface: 'monitor',
|
||||
} as DisplayMediaStreamOptions['video'],
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false,
|
||||
} as DisplayMediaStreamOptions['audio'],
|
||||
});
|
||||
|
||||
console.log('[DesktopShare] 获取桌面流成功:', stream.getTracks().length, '个轨道');
|
||||
return stream;
|
||||
} catch (error) {
|
||||
console.error('[DesktopShare] 获取桌面流失败:', error);
|
||||
throw new Error('无法获取桌面共享权限,请确保允许屏幕共享');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 设置视频轨道发送
|
||||
const setupVideoSending = useCallback(async (stream: MediaStream) => {
|
||||
console.log('[DesktopShare] 🎬 开始设置视频轨道发送...');
|
||||
|
||||
// 移除之前的轨道(如果存在)
|
||||
if (currentSenderRef.current) {
|
||||
console.log('[DesktopShare] 🗑️ 移除之前的视频轨道');
|
||||
webRTC.removeTrack(currentSenderRef.current);
|
||||
currentSenderRef.current = null;
|
||||
}
|
||||
|
||||
// 添加新的视频轨道到PeerConnection
|
||||
const videoTrack = stream.getVideoTracks()[0];
|
||||
const audioTrack = stream.getAudioTracks()[0];
|
||||
|
||||
if (videoTrack) {
|
||||
console.log('[DesktopShare] 📹 添加视频轨道:', videoTrack.id, videoTrack.readyState);
|
||||
const videoSender = webRTC.addTrack(videoTrack, stream);
|
||||
if (videoSender) {
|
||||
currentSenderRef.current = videoSender;
|
||||
console.log('[DesktopShare] ✅ 视频轨道添加成功');
|
||||
} else {
|
||||
console.warn('[DesktopShare] ⚠️ 视频轨道添加返回null');
|
||||
}
|
||||
} else {
|
||||
console.error('[DesktopShare] ❌ 未找到视频轨道');
|
||||
throw new Error('未找到视频轨道');
|
||||
}
|
||||
|
||||
if (audioTrack) {
|
||||
try {
|
||||
console.log('[DesktopShare] 🎵 添加音频轨道:', audioTrack.id, audioTrack.readyState);
|
||||
const audioSender = webRTC.addTrack(audioTrack, stream);
|
||||
if (audioSender) {
|
||||
console.log('[DesktopShare] ✅ 音频轨道添加成功');
|
||||
} else {
|
||||
console.warn('[DesktopShare] ⚠️ 音频轨道添加返回null');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[DesktopShare] ⚠️ 音频轨道添加失败,继续视频共享:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('[DesktopShare] ℹ️ 未检测到音频轨道(这通常是正常的)');
|
||||
}
|
||||
|
||||
// 轨道添加完成,现在需要重新协商以包含媒体轨道
|
||||
console.log('[DesktopShare] ✅ 桌面共享轨道添加完成,开始重新协商');
|
||||
|
||||
// 检查P2P连接是否已建立
|
||||
if (!webRTC.isPeerConnected) {
|
||||
console.error('[DesktopShare] ❌ P2P连接尚未建立,无法开始媒体传输');
|
||||
throw new Error('P2P连接尚未建立');
|
||||
}
|
||||
|
||||
// 创建新的offer包含媒体轨道
|
||||
console.log('[DesktopShare] 📨 创建包含媒体轨道的新offer进行重新协商');
|
||||
const success = await webRTC.createOfferNow();
|
||||
if (success) {
|
||||
console.log('[DesktopShare] ✅ 媒体轨道重新协商成功');
|
||||
} else {
|
||||
console.error('[DesktopShare] ❌ 媒体轨道重新协商失败');
|
||||
throw new Error('媒体轨道重新协商失败');
|
||||
}
|
||||
|
||||
// 监听流结束事件(用户停止共享)
|
||||
const handleStreamEnded = () => {
|
||||
console.log('[DesktopShare] 🛑 用户停止了屏幕共享');
|
||||
stopSharing();
|
||||
};
|
||||
|
||||
videoTrack?.addEventListener('ended', handleStreamEnded);
|
||||
audioTrack?.addEventListener('ended', handleStreamEnded);
|
||||
|
||||
return () => {
|
||||
videoTrack?.removeEventListener('ended', handleStreamEnded);
|
||||
audioTrack?.removeEventListener('ended', handleStreamEnded);
|
||||
};
|
||||
}, [webRTC]);
|
||||
|
||||
// 处理远程流
|
||||
const handleRemoteStream = useCallback((stream: MediaStream) => {
|
||||
console.log('[DesktopShare] 收到远程流:', stream.getTracks().length, '个轨道');
|
||||
updateState({ remoteStream: stream });
|
||||
|
||||
// 如果有视频元素引用,设置流
|
||||
if (remoteVideoRef.current) {
|
||||
remoteVideoRef.current.srcObject = stream;
|
||||
}
|
||||
}, [updateState]);
|
||||
|
||||
// 创建房间(只建立连接,等待对方加入)
|
||||
const createRoom = useCallback(async (): Promise<string> => {
|
||||
try {
|
||||
updateState({ error: null, isWaitingForPeer: false });
|
||||
|
||||
// 生成房间代码
|
||||
const roomCode = generateRoomCode();
|
||||
console.log('[DesktopShare] 🚀 创建桌面共享房间,代码:', roomCode);
|
||||
|
||||
// 建立WebRTC连接(作为发送方)
|
||||
console.log('[DesktopShare] 📡 正在建立WebRTC连接...');
|
||||
await webRTC.connect(roomCode, 'sender');
|
||||
console.log('[DesktopShare] ✅ WebSocket连接已建立');
|
||||
|
||||
updateState({
|
||||
connectionCode: roomCode,
|
||||
isWaitingForPeer: true, // 标记为等待对方连接
|
||||
});
|
||||
|
||||
console.log('[DesktopShare] 🎯 房间创建完成,等待对方加入建立P2P连接');
|
||||
return roomCode;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '创建房间失败';
|
||||
console.error('[DesktopShare] ❌ 创建房间失败:', error);
|
||||
updateState({ error: errorMessage, connectionCode: '', isWaitingForPeer: false });
|
||||
throw error;
|
||||
}
|
||||
}, [webRTC, generateRoomCode, updateState]);
|
||||
|
||||
// 开始桌面共享(在接收方加入后)
|
||||
const startSharing = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
// 检查WebSocket连接状态
|
||||
if (!webRTC.isWebSocketConnected) {
|
||||
throw new Error('WebSocket连接未建立,请先创建房间');
|
||||
}
|
||||
|
||||
updateState({ error: null });
|
||||
console.log('[DesktopShare] 📺 正在请求桌面共享权限...');
|
||||
|
||||
// 获取桌面流
|
||||
const stream = await getDesktopStream();
|
||||
localStreamRef.current = stream;
|
||||
console.log('[DesktopShare] ✅ 桌面流获取成功');
|
||||
|
||||
// 设置视频发送(这会添加轨道并创建offer,启动P2P连接)
|
||||
console.log('[DesktopShare] 📤 正在设置视频轨道推送并建立P2P连接...');
|
||||
await setupVideoSending(stream);
|
||||
console.log('[DesktopShare] ✅ 视频轨道推送设置完成');
|
||||
|
||||
updateState({
|
||||
isSharing: true,
|
||||
isWaitingForPeer: false,
|
||||
});
|
||||
|
||||
console.log('[DesktopShare] 🎉 桌面共享已开始');
|
||||
} catch (error) {
|
||||
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]);
|
||||
|
||||
// 切换桌面共享(重新选择屏幕)
|
||||
const switchDesktop = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
if (!webRTC.isPeerConnected) {
|
||||
throw new Error('P2P连接未建立');
|
||||
}
|
||||
|
||||
if (!state.isSharing) {
|
||||
throw new Error('当前未在共享桌面');
|
||||
}
|
||||
|
||||
updateState({ error: null });
|
||||
console.log('[DesktopShare] 🔄 正在切换桌面共享...');
|
||||
|
||||
// 获取新的桌面流
|
||||
const newStream = await getDesktopStream();
|
||||
|
||||
// 停止之前的流
|
||||
if (localStreamRef.current) {
|
||||
localStreamRef.current.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
|
||||
localStreamRef.current = newStream;
|
||||
console.log('[DesktopShare] ✅ 新桌面流获取成功');
|
||||
|
||||
// 设置新的视频发送
|
||||
await setupVideoSending(newStream);
|
||||
console.log('[DesktopShare] ✅ 桌面切换完成');
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '切换桌面失败';
|
||||
console.error('[DesktopShare] ❌ 切换桌面失败:', error);
|
||||
updateState({ error: errorMessage });
|
||||
throw error;
|
||||
}
|
||||
}, [webRTC, state.isSharing, getDesktopStream, setupVideoSending, updateState]);
|
||||
|
||||
// 停止桌面共享
|
||||
const stopSharing = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
console.log('[DesktopShare] 停止桌面共享');
|
||||
|
||||
// 停止本地流
|
||||
if (localStreamRef.current) {
|
||||
localStreamRef.current.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
console.log('[DesktopShare] 停止轨道:', track.kind);
|
||||
});
|
||||
localStreamRef.current = null;
|
||||
}
|
||||
|
||||
// 移除发送器
|
||||
if (currentSenderRef.current) {
|
||||
webRTC.removeTrack(currentSenderRef.current);
|
||||
currentSenderRef.current = null;
|
||||
}
|
||||
|
||||
// 断开WebRTC连接
|
||||
webRTC.disconnect();
|
||||
|
||||
updateState({
|
||||
isSharing: false,
|
||||
connectionCode: '',
|
||||
error: null,
|
||||
isWaitingForPeer: false,
|
||||
});
|
||||
|
||||
console.log('[DesktopShare] 桌面共享已停止');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '停止桌面共享失败';
|
||||
console.error('[DesktopShare] 停止共享失败:', error);
|
||||
updateState({ error: errorMessage });
|
||||
}
|
||||
}, [webRTC, updateState]);
|
||||
|
||||
// 加入桌面共享观看
|
||||
const joinSharing = useCallback(async (code: string): Promise<void> => {
|
||||
try {
|
||||
updateState({ error: null });
|
||||
console.log('[DesktopShare] 🔍 正在加入桌面共享观看:', code);
|
||||
|
||||
// 连接WebRTC
|
||||
console.log('[DesktopShare] 🔗 正在连接WebRTC作为接收方...');
|
||||
await webRTC.connect(code, 'receiver');
|
||||
console.log('[DesktopShare] ✅ WebRTC连接建立完成');
|
||||
|
||||
// 等待连接完全建立
|
||||
console.log('[DesktopShare] ⏳ 等待连接稳定...');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 设置远程流处理 - 在连接建立后设置
|
||||
console.log('[DesktopShare] 📡 设置远程流处理器...');
|
||||
webRTC.onTrack((event: RTCTrackEvent) => {
|
||||
console.log('[DesktopShare] 🎥 收到远程轨道:', event.track.kind, event.track.id);
|
||||
console.log('[DesktopShare] 远程流数量:', event.streams.length);
|
||||
|
||||
if (event.streams.length > 0) {
|
||||
const remoteStream = event.streams[0];
|
||||
console.log('[DesktopShare] 🎬 设置远程流,轨道数量:', remoteStream.getTracks().length);
|
||||
handleRemoteStream(remoteStream);
|
||||
} else {
|
||||
console.warn('[DesktopShare] ⚠️ 收到轨道但没有关联的流');
|
||||
}
|
||||
});
|
||||
|
||||
updateState({ isViewing: true });
|
||||
console.log('[DesktopShare] 👁️ 已进入桌面共享观看模式,等待接收流...');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '加入桌面共享失败';
|
||||
console.error('[DesktopShare] ❌ 加入观看失败:', error);
|
||||
updateState({ error: errorMessage, isViewing: false });
|
||||
throw error;
|
||||
}
|
||||
}, [webRTC, handleRemoteStream, updateState]);
|
||||
|
||||
// 停止观看桌面共享
|
||||
const stopViewing = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
console.log('[DesktopShare] 停止观看桌面共享');
|
||||
|
||||
// 断开WebRTC连接
|
||||
webRTC.disconnect();
|
||||
|
||||
updateState({
|
||||
isViewing: false,
|
||||
remoteStream: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
console.log('[DesktopShare] 已停止观看桌面共享');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '停止观看失败';
|
||||
console.error('[DesktopShare] 停止观看失败:', error);
|
||||
updateState({ error: errorMessage });
|
||||
}
|
||||
}, [webRTC, updateState]);
|
||||
|
||||
// 设置远程视频元素引用
|
||||
const setRemoteVideoRef = useCallback((videoElement: HTMLVideoElement | null) => {
|
||||
remoteVideoRef.current = videoElement;
|
||||
if (videoElement && state.remoteStream) {
|
||||
videoElement.srcObject = state.remoteStream;
|
||||
}
|
||||
}, [state.remoteStream]);
|
||||
|
||||
// 清理资源
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (localStreamRef.current) {
|
||||
localStreamRef.current.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isSharing: state.isSharing,
|
||||
isViewing: state.isViewing,
|
||||
connectionCode: state.connectionCode,
|
||||
remoteStream: state.remoteStream,
|
||||
error: state.error,
|
||||
isWaitingForPeer: state.isWaitingForPeer,
|
||||
isConnected: webRTC.isConnected,
|
||||
isConnecting: webRTC.isConnecting,
|
||||
isWebSocketConnected: webRTC.isWebSocketConnected,
|
||||
isPeerConnected: webRTC.isPeerConnected,
|
||||
// 新增:表示是否可以开始共享(WebSocket已连接且有房间代码)
|
||||
canStartSharing: webRTC.isWebSocketConnected && !!state.connectionCode,
|
||||
|
||||
// 方法
|
||||
createRoom, // 创建房间
|
||||
startSharing, // 选择桌面并建立P2P连接
|
||||
switchDesktop, // 新增:切换桌面
|
||||
stopSharing,
|
||||
joinSharing,
|
||||
stopViewing,
|
||||
setRemoteVideoRef,
|
||||
|
||||
// WebRTC连接状态
|
||||
webRTCError: webRTC.error,
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,10 @@ import type { WebRTCConnection } from './useSharedWebRTCManager';
|
||||
|
||||
// 文件传输状态
|
||||
interface FileTransferState {
|
||||
isConnecting: boolean;
|
||||
isConnected: boolean;
|
||||
isWebSocketConnected: boolean;
|
||||
connectionError: string | null;
|
||||
isTransferring: boolean;
|
||||
progress: number;
|
||||
error: string | null;
|
||||
@@ -50,6 +54,10 @@ const CHUNK_SIZE = 256 * 1024; // 256KB
|
||||
export function useFileTransferBusiness(connection: WebRTCConnection) {
|
||||
|
||||
const [state, setState] = useState<FileTransferState>({
|
||||
isConnecting: false,
|
||||
isConnected: false,
|
||||
isWebSocketConnected: false,
|
||||
connectionError: null,
|
||||
isTransferring: false,
|
||||
progress: 0,
|
||||
error: null,
|
||||
@@ -177,6 +185,17 @@ export function useFileTransferBusiness(connection: WebRTCConnection) {
|
||||
};
|
||||
}, [handleMessage, handleData]);
|
||||
|
||||
// 监听连接状态变化 (直接使用 connection 的状态)
|
||||
useEffect(() => {
|
||||
// 同步连接状态
|
||||
updateState({
|
||||
isConnecting: connection.isConnecting,
|
||||
isConnected: connection.isConnected,
|
||||
isWebSocketConnected: connection.isWebSocketConnected,
|
||||
connectionError: connection.error
|
||||
});
|
||||
}, [connection.isConnecting, connection.isConnected, connection.isWebSocketConnected, connection.error, updateState]);
|
||||
|
||||
// 连接
|
||||
const connect = useCallback((roomCode: string, role: 'sender' | 'receiver') => {
|
||||
return connection.connect(roomCode, role);
|
||||
@@ -263,6 +282,11 @@ export function useFileTransferBusiness(connection: WebRTCConnection) {
|
||||
|
||||
// 发送文件列表
|
||||
const sendFileList = useCallback((fileList: FileInfo[]) => {
|
||||
if (!connection.isPeerConnected) {
|
||||
console.log('P2P连接未建立,等待连接后再发送文件列表');
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection.getChannelState() !== 'open') {
|
||||
console.error('数据通道未准备就绪,无法发送文件列表');
|
||||
return;
|
||||
@@ -313,13 +337,7 @@ export function useFileTransferBusiness(connection: WebRTCConnection) {
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// 继承基础连接状态
|
||||
isConnected: connection.isConnected,
|
||||
isConnecting: connection.isConnecting,
|
||||
isWebSocketConnected: connection.isWebSocketConnected,
|
||||
connectionError: connection.error,
|
||||
|
||||
// 文件传输状态
|
||||
// 文件传输状态(包括连接状态)
|
||||
...state,
|
||||
|
||||
// 操作方法
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { config } from '@/lib/config';
|
||||
import { getWsUrl } from '@/lib/config';
|
||||
|
||||
// 基础连接状态
|
||||
interface WebRTCState {
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
isWebSocketConnected: boolean;
|
||||
isPeerConnected: boolean; // 新增:P2P连接状态
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
@@ -26,6 +27,7 @@ export interface WebRTCConnection {
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
isWebSocketConnected: boolean;
|
||||
isPeerConnected: boolean; // 新增:P2P连接状态
|
||||
error: string | null;
|
||||
|
||||
// 操作方法
|
||||
@@ -44,6 +46,13 @@ export interface WebRTCConnection {
|
||||
|
||||
// 当前房间信息
|
||||
currentRoom: { code: string; role: 'sender' | 'receiver' } | null;
|
||||
|
||||
// 媒体轨道方法
|
||||
addTrack: (track: MediaStreamTrack, stream: MediaStream) => RTCRtpSender | null;
|
||||
removeTrack: (sender: RTCRtpSender) => void;
|
||||
onTrack: (callback: (event: RTCTrackEvent) => void) => void;
|
||||
getPeerConnection: () => RTCPeerConnection | null;
|
||||
createOfferNow: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +64,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isWebSocketConnected: false,
|
||||
isPeerConnected: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
@@ -70,12 +80,12 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
const messageHandlers = useRef<Map<string, MessageHandler>>(new Map());
|
||||
const dataHandlers = useRef<Map<string, DataHandler>>(new Map());
|
||||
|
||||
// STUN 服务器配置
|
||||
// STUN 服务器配置 - 使用更稳定的服务器
|
||||
const STUN_SERVERS = [
|
||||
{ urls: 'stun:stun.chat.bilibili.com' },
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun.miwifi.com' },
|
||||
{ urls: 'stun:turn.cloudflare.com:3478' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' },
|
||||
{ urls: 'stun:global.stun.twilio.com:3478' },
|
||||
];
|
||||
|
||||
const updateState = useCallback((updates: Partial<WebRTCState>) => {
|
||||
@@ -84,44 +94,47 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
|
||||
// 清理连接
|
||||
const cleanup = useCallback(() => {
|
||||
// console.log('[SharedWebRTC] 清理连接');
|
||||
// if (timeoutRef.current) {
|
||||
// clearTimeout(timeoutRef.current);
|
||||
// timeoutRef.current = null;
|
||||
// }
|
||||
console.log('[SharedWebRTC] 清理连接');
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
|
||||
// if (dcRef.current) {
|
||||
// dcRef.current.close();
|
||||
// dcRef.current = null;
|
||||
// }
|
||||
if (dcRef.current) {
|
||||
dcRef.current.close();
|
||||
dcRef.current = null;
|
||||
}
|
||||
|
||||
// if (pcRef.current) {
|
||||
// pcRef.current.close();
|
||||
// pcRef.current = null;
|
||||
// }
|
||||
if (pcRef.current) {
|
||||
pcRef.current.close();
|
||||
pcRef.current = null;
|
||||
}
|
||||
|
||||
// if (wsRef.current) {
|
||||
// wsRef.current.close();
|
||||
// wsRef.current = null;
|
||||
// }
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
|
||||
// currentRoom.current = null;
|
||||
currentRoom.current = null;
|
||||
}, []);
|
||||
|
||||
// 创建 Offer
|
||||
const createOffer = useCallback(async (pc: RTCPeerConnection, ws: WebSocket) => {
|
||||
try {
|
||||
console.log('[SharedWebRTC] 🎬 开始创建offer,当前轨道数量:', pc.getSenders().length);
|
||||
|
||||
const offer = await pc.createOffer({
|
||||
offerToReceiveAudio: false,
|
||||
offerToReceiveVideo: false,
|
||||
offerToReceiveAudio: true, // 改为true以支持音频接收
|
||||
offerToReceiveVideo: true, // 改为true以支持视频接收
|
||||
});
|
||||
|
||||
console.log('[SharedWebRTC] 📝 Offer创建成功,设置本地描述...');
|
||||
await pc.setLocalDescription(offer);
|
||||
|
||||
const iceTimeout = setTimeout(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[SharedWebRTC] 发送 offer (超时发送)');
|
||||
console.log('[SharedWebRTC] 📤 发送 offer (超时发送)');
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
@@ -129,7 +142,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
clearTimeout(iceTimeout);
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[SharedWebRTC] 发送 offer (ICE收集完成)');
|
||||
console.log('[SharedWebRTC] 📤 发送 offer (ICE收集完成)');
|
||||
}
|
||||
} else {
|
||||
pc.onicegatheringstatechange = () => {
|
||||
@@ -137,13 +150,13 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
clearTimeout(iceTimeout);
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[SharedWebRTC] 发送 offer (ICE收集完成)');
|
||||
console.log('[SharedWebRTC] 📤 发送 offer (ICE收集完成)');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 创建 offer 失败:', error);
|
||||
console.error('[SharedWebRTC] ❌ 创建 offer 失败:', error);
|
||||
updateState({ error: '创建连接失败', isConnecting: false });
|
||||
}
|
||||
}, [updateState]);
|
||||
@@ -187,60 +200,24 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
|
||||
// 连接到房间
|
||||
const connect = useCallback(async (roomCode: string, role: 'sender' | 'receiver') => {
|
||||
console.log('[SharedWebRTC] 连接到房间:', roomCode, role);
|
||||
|
||||
// 检查是否已经连接到相同房间
|
||||
if (currentRoom.current?.code === roomCode && currentRoom.current?.role === role) {
|
||||
if (state.isConnected) {
|
||||
console.log('[SharedWebRTC] 已连接到相同房间,复用连接');
|
||||
return;
|
||||
}
|
||||
if (state.isConnecting) {
|
||||
console.log('[SharedWebRTC] 正在连接到相同房间,等待连接完成');
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const checkConnection = () => {
|
||||
if (state.isConnected) {
|
||||
resolve();
|
||||
} else if (!state.isConnecting) {
|
||||
reject(new Error('连接失败'));
|
||||
} else {
|
||||
setTimeout(checkConnection, 100);
|
||||
}
|
||||
};
|
||||
checkConnection();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果要连接到不同房间,先断开当前连接
|
||||
if (currentRoom.current && (currentRoom.current.code !== roomCode || currentRoom.current.role !== role)) {
|
||||
console.log('[SharedWebRTC] 切换到新房间,断开当前连接');
|
||||
cleanup();
|
||||
updateState({
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isWebSocketConnected: false,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
console.log('[SharedWebRTC] 🚀 开始连接到房间:', roomCode, role);
|
||||
|
||||
// 如果正在连接中,避免重复连接
|
||||
if (state.isConnecting) {
|
||||
console.warn('[SharedWebRTC] 正在连接中,跳过重复连接请求');
|
||||
console.warn('[SharedWebRTC] ⚠️ 正在连接中,跳过重复连接请求');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理之前的连接
|
||||
cleanup();
|
||||
currentRoom.current = { code: roomCode, role };
|
||||
updateState({ isConnecting: true, error: null });
|
||||
|
||||
// 设置连接超时
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
console.warn('[SharedWebRTC] 连接超时');
|
||||
updateState({ error: '连接超时,请检查网络状况或重新尝试', isConnecting: false });
|
||||
cleanup();
|
||||
}, 30000);
|
||||
// 注意:不在这里设置超时,因为WebSocket连接很快,
|
||||
// WebRTC连接的建立是在后续添加轨道时进行的
|
||||
|
||||
try {
|
||||
console.log('[SharedWebRTC] 🔧 创建PeerConnection...');
|
||||
// 创建 PeerConnection
|
||||
const pc = new RTCPeerConnection({
|
||||
iceServers: STUN_SERVERS,
|
||||
@@ -248,69 +225,119 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
});
|
||||
pcRef.current = pc;
|
||||
|
||||
// 连接 WebSocket
|
||||
const wsUrl = config.api.wsUrl.replace('/ws/p2p', '/ws/webrtc');
|
||||
const ws = new WebSocket(`${wsUrl}?code=${roomCode}&role=${role}&channel=shared`);
|
||||
// 连接 WebSocket - 使用动态URL
|
||||
const baseWsUrl = getWsUrl();
|
||||
if (!baseWsUrl) {
|
||||
throw new Error('WebSocket URL未配置');
|
||||
}
|
||||
|
||||
// 构建完整的WebSocket URL
|
||||
const wsUrl = baseWsUrl.replace('/ws/p2p', `/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`);
|
||||
console.log('[SharedWebRTC] 🌐 连接WebSocket:', wsUrl);
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
// WebSocket 事件处理
|
||||
ws.onopen = () => {
|
||||
console.log('[SharedWebRTC] WebSocket 连接已建立');
|
||||
updateState({ isWebSocketConnected: true });
|
||||
|
||||
if (role === 'sender') {
|
||||
createOffer(pc, ws);
|
||||
}
|
||||
console.log('[SharedWebRTC] ✅ WebSocket 连接已建立,房间准备就绪');
|
||||
updateState({
|
||||
isWebSocketConnected: true,
|
||||
isConnecting: false, // WebSocket连接成功即表示初始连接完成
|
||||
isConnected: true // 可以开始后续操作
|
||||
});
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('[SharedWebRTC] 收到信令消息:', message.type);
|
||||
console.log('[SharedWebRTC] 📨 收到信令消息:', message.type);
|
||||
|
||||
switch (message.type) {
|
||||
case 'peer-joined':
|
||||
// 对方加入房间的通知
|
||||
console.log('[SharedWebRTC] 👥 对方已加入房间,角色:', message.payload?.role);
|
||||
if (role === 'sender' && message.payload?.role === 'receiver') {
|
||||
console.log('[SharedWebRTC] 🚀 接收方已连接,发送方自动建立P2P连接');
|
||||
updateState({ isPeerConnected: true }); // 标记对方已加入,可以开始P2P
|
||||
|
||||
// 发送方自动创建offer建立基础P2P连接
|
||||
try {
|
||||
console.log('[SharedWebRTC] 📡 自动创建基础P2P连接offer');
|
||||
await createOffer(pc, ws);
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 自动创建基础P2P连接失败:', error);
|
||||
}
|
||||
} else if (role === 'receiver' && message.payload?.role === 'sender') {
|
||||
console.log('[SharedWebRTC] 🚀 发送方已连接,接收方准备接收P2P连接');
|
||||
updateState({ isPeerConnected: true }); // 标记对方已加入
|
||||
}
|
||||
break;
|
||||
|
||||
case 'offer':
|
||||
console.log('[SharedWebRTC] 📬 处理offer...');
|
||||
if (pc.signalingState === 'stable') {
|
||||
await pc.setRemoteDescription(new RTCSessionDescription(message.payload));
|
||||
console.log('[SharedWebRTC] ✅ 设置远程描述完成');
|
||||
|
||||
const answer = await pc.createAnswer();
|
||||
await pc.setLocalDescription(answer);
|
||||
console.log('[SharedWebRTC] ✅ 创建并设置answer完成');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'answer', payload: answer }));
|
||||
console.log('[SharedWebRTC] 发送 answer');
|
||||
console.log('[SharedWebRTC] 📤 发送 answer');
|
||||
} else {
|
||||
console.warn('[SharedWebRTC] ⚠️ PeerConnection状态不是stable:', pc.signalingState);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'answer':
|
||||
console.log('[SharedWebRTC] 📬 处理answer...');
|
||||
if (pc.signalingState === 'have-local-offer') {
|
||||
await pc.setRemoteDescription(new RTCSessionDescription(message.payload));
|
||||
console.log('[SharedWebRTC] 处理 answer 完成');
|
||||
console.log('[SharedWebRTC] ✅ answer 处理完成');
|
||||
} else {
|
||||
console.warn('[SharedWebRTC] ⚠️ PeerConnection状态不是have-local-offer:', pc.signalingState);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ice-candidate':
|
||||
if (message.payload && pc.remoteDescription) {
|
||||
await pc.addIceCandidate(new RTCIceCandidate(message.payload));
|
||||
console.log('[SharedWebRTC] 添加 ICE 候选');
|
||||
try {
|
||||
await pc.addIceCandidate(new RTCIceCandidate(message.payload));
|
||||
console.log('[SharedWebRTC] ✅ 添加 ICE 候选成功');
|
||||
} catch (err) {
|
||||
console.warn('[SharedWebRTC] ⚠️ 添加 ICE 候选失败:', err);
|
||||
}
|
||||
} else {
|
||||
console.warn('[SharedWebRTC] ⚠️ ICE候选无效或远程描述未设置');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error('[SharedWebRTC] 信令错误:', message.error);
|
||||
console.error('[SharedWebRTC] ❌ 信令服务器错误:', message.error);
|
||||
updateState({ error: message.error, isConnecting: false });
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('[SharedWebRTC] ⚠️ 未知消息类型:', message.type);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 处理信令消息失败:', error);
|
||||
console.error('[SharedWebRTC] ❌ 处理信令消息失败:', error);
|
||||
updateState({ error: '信令处理失败: ' + error, isConnecting: false });
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('[SharedWebRTC] WebSocket 错误:', error);
|
||||
updateState({ error: 'WebSocket连接失败,请检查网络连接', isConnecting: false });
|
||||
console.error('[SharedWebRTC] ❌ WebSocket 错误:', error);
|
||||
updateState({ error: 'WebSocket连接失败,请检查服务器是否运行在8080端口', isConnecting: false });
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('[SharedWebRTC] WebSocket 连接已关闭');
|
||||
ws.onclose = (event) => {
|
||||
console.log('[SharedWebRTC] 🔌 WebSocket 连接已关闭, 代码:', event.code, '原因:', event.reason);
|
||||
updateState({ isWebSocketConnected: false });
|
||||
if (event.code !== 1000 && event.code !== 1001) { // 非正常关闭
|
||||
updateState({ error: `WebSocket异常关闭 (${event.code}): ${event.reason || '未知原因'}`, isConnecting: false });
|
||||
}
|
||||
};
|
||||
|
||||
// PeerConnection 事件处理
|
||||
@@ -320,32 +347,63 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
type: 'ice-candidate',
|
||||
payload: event.candidate
|
||||
}));
|
||||
console.log('[SharedWebRTC] 发送 ICE 候选');
|
||||
console.log('[SharedWebRTC] 📤 发送 ICE 候选:', event.candidate.candidate.substring(0, 50) + '...');
|
||||
} else if (!event.candidate) {
|
||||
console.log('[SharedWebRTC] 🏁 ICE 收集完成');
|
||||
}
|
||||
};
|
||||
|
||||
pc.oniceconnectionstatechange = () => {
|
||||
console.log('[SharedWebRTC] 🧊 ICE连接状态变化:', pc.iceConnectionState);
|
||||
switch (pc.iceConnectionState) {
|
||||
case 'checking':
|
||||
console.log('[SharedWebRTC] 🔍 正在检查ICE连接...');
|
||||
break;
|
||||
case 'connected':
|
||||
case 'completed':
|
||||
console.log('[SharedWebRTC] ✅ ICE连接成功');
|
||||
break;
|
||||
case 'failed':
|
||||
console.error('[SharedWebRTC] ❌ ICE连接失败');
|
||||
updateState({ error: 'ICE连接失败,可能是网络防火墙阻止了连接', isConnecting: false });
|
||||
break;
|
||||
case 'disconnected':
|
||||
console.log('[SharedWebRTC] 🔌 ICE连接断开');
|
||||
break;
|
||||
case 'closed':
|
||||
console.log('[SharedWebRTC] 🚫 ICE连接已关闭');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
pc.onconnectionstatechange = () => {
|
||||
console.log('[SharedWebRTC] 连接状态变化:', pc.connectionState);
|
||||
console.log('[SharedWebRTC] 🔗 WebRTC连接状态变化:', pc.connectionState);
|
||||
switch (pc.connectionState) {
|
||||
case 'connecting':
|
||||
console.log('[SharedWebRTC] 🔄 WebRTC正在连接中...');
|
||||
updateState({ isPeerConnected: false });
|
||||
break;
|
||||
case 'connected':
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
updateState({ isConnected: true, isConnecting: false, error: null });
|
||||
console.log('[SharedWebRTC] 🎉 WebRTC P2P连接已完全建立,可以进行媒体传输');
|
||||
updateState({ isPeerConnected: true, error: null });
|
||||
break;
|
||||
case 'failed':
|
||||
updateState({ error: 'WebRTC连接失败,可能是网络防火墙阻止了连接', isConnecting: false, isConnected: false });
|
||||
break;
|
||||
case 'disconnected':
|
||||
updateState({ isConnected: false });
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
// 只有在数据通道也未打开的情况下才认为连接真正失败
|
||||
const currentDc = dcRef.current;
|
||||
if (!currentDc || currentDc.readyState !== 'open') {
|
||||
console.error('[SharedWebRTC] ❌ WebRTC连接失败,数据通道未建立');
|
||||
updateState({ error: 'WebRTC连接失败,请检查网络设置或重试', isPeerConnected: false });
|
||||
} else {
|
||||
console.log('[SharedWebRTC] ⚠️ WebRTC连接状态为failed,但数据通道正常,忽略此状态');
|
||||
}
|
||||
break;
|
||||
case 'disconnected':
|
||||
console.log('[SharedWebRTC] 🔌 WebRTC连接已断开');
|
||||
updateState({ isPeerConnected: false });
|
||||
break;
|
||||
case 'closed':
|
||||
updateState({ isConnected: false, isConnecting: false });
|
||||
console.log('[SharedWebRTC] 🚫 WebRTC连接已关闭');
|
||||
updateState({ isPeerConnected: false });
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -360,6 +418,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log('[SharedWebRTC] 数据通道已打开 (发送方)');
|
||||
updateState({ isPeerConnected: true, error: null, isConnecting: false });
|
||||
};
|
||||
|
||||
dataChannel.onmessage = handleDataChannelMessage;
|
||||
@@ -375,6 +434,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log('[SharedWebRTC] 数据通道已打开 (接收方)');
|
||||
updateState({ isPeerConnected: true, error: null, isConnecting: false });
|
||||
};
|
||||
|
||||
dataChannel.onmessage = handleDataChannelMessage;
|
||||
@@ -386,6 +446,19 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
};
|
||||
}
|
||||
|
||||
// 设置轨道接收处理(对于接收方)
|
||||
pc.ontrack = (event) => {
|
||||
console.log('[SharedWebRTC] 🎥 PeerConnection收到轨道:', event.track.kind, event.track.id);
|
||||
console.log('[SharedWebRTC] 关联的流数量:', event.streams.length);
|
||||
|
||||
if (event.streams.length > 0) {
|
||||
console.log('[SharedWebRTC] 🎬 轨道关联到流:', event.streams[0].id);
|
||||
}
|
||||
|
||||
// 这里不处理,让具体的业务逻辑处理
|
||||
// onTrack会被业务逻辑重新设置
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 连接失败:', error);
|
||||
updateState({
|
||||
@@ -403,6 +476,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isWebSocketConnected: false,
|
||||
isPeerConnected: false,
|
||||
error: null,
|
||||
});
|
||||
}, [cleanup]);
|
||||
@@ -478,11 +552,90 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
state.isConnected;
|
||||
}, [state.isConnected]);
|
||||
|
||||
// 添加媒体轨道
|
||||
const addTrack = useCallback((track: MediaStreamTrack, stream: MediaStream) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.error('[SharedWebRTC] PeerConnection 不可用');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return pc.addTrack(track, stream);
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 添加轨道失败:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 移除媒体轨道
|
||||
const removeTrack = useCallback((sender: RTCRtpSender) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.error('[SharedWebRTC] PeerConnection 不可用');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pc.removeTrack(sender);
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 移除轨道失败:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 设置轨道处理器
|
||||
const onTrack = useCallback((handler: (event: RTCTrackEvent) => void) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.warn('[SharedWebRTC] PeerConnection 尚未准备就绪,将在连接建立后设置onTrack');
|
||||
// 延迟设置,等待PeerConnection准备就绪
|
||||
const checkAndSetTrackHandler = () => {
|
||||
const currentPc = pcRef.current;
|
||||
if (currentPc) {
|
||||
console.log('[SharedWebRTC] ✅ PeerConnection 已准备就绪,设置onTrack处理器');
|
||||
currentPc.ontrack = handler;
|
||||
} else {
|
||||
console.log('[SharedWebRTC] ⏳ 等待PeerConnection准备就绪...');
|
||||
setTimeout(checkAndSetTrackHandler, 100);
|
||||
}
|
||||
};
|
||||
checkAndSetTrackHandler();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[SharedWebRTC] ✅ 立即设置onTrack处理器');
|
||||
pc.ontrack = handler;
|
||||
}, []);
|
||||
|
||||
// 获取PeerConnection实例
|
||||
const getPeerConnection = useCallback(() => {
|
||||
return pcRef.current;
|
||||
}, []);
|
||||
|
||||
// 立即创建offer(用于媒体轨道添加后的重新协商)
|
||||
const createOfferNow = useCallback(async () => {
|
||||
const pc = pcRef.current;
|
||||
const ws = wsRef.current;
|
||||
if (!pc || !ws) {
|
||||
console.error('[SharedWebRTC] PeerConnection 或 WebSocket 不可用');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await createOffer(pc, ws);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[SharedWebRTC] 创建 offer 失败:', error);
|
||||
return false;
|
||||
}
|
||||
}, [createOffer]);
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isConnected: state.isConnected,
|
||||
isConnecting: state.isConnecting,
|
||||
isWebSocketConnected: state.isWebSocketConnected,
|
||||
isPeerConnected: state.isPeerConnected,
|
||||
error: state.error,
|
||||
|
||||
// 操作方法
|
||||
@@ -499,6 +652,13 @@ export function useSharedWebRTCManager(): WebRTCConnection {
|
||||
getChannelState,
|
||||
isConnectedToRoom,
|
||||
|
||||
// 媒体轨道方法
|
||||
addTrack,
|
||||
removeTrack,
|
||||
onTrack,
|
||||
getPeerConnection,
|
||||
createOfferNow,
|
||||
|
||||
// 当前房间信息
|
||||
currentRoom: currentRoom.current,
|
||||
};
|
||||
|
||||
@@ -84,9 +84,14 @@ export function useTextTransferBusiness(connection: WebRTCConnection) {
|
||||
|
||||
// 监听连接状态变化 (直接使用 connection 的状态)
|
||||
useEffect(() => {
|
||||
// 这里我们直接依赖 connection 的状态变化
|
||||
// 由于我们使用共享连接,状态会自动同步
|
||||
}, []);
|
||||
// 同步连接状态
|
||||
updateState({
|
||||
isConnecting: connection.isConnecting,
|
||||
isConnected: connection.isConnected,
|
||||
isWebSocketConnected: connection.isWebSocketConnected,
|
||||
connectionError: connection.error
|
||||
});
|
||||
}, [connection.isConnecting, connection.isConnected, connection.isWebSocketConnected, connection.error, updateState]);
|
||||
|
||||
// 连接
|
||||
const connect = useCallback((roomCode: string, role: 'sender' | 'receiver') => {
|
||||
@@ -100,28 +105,32 @@ export function useTextTransferBusiness(connection: WebRTCConnection) {
|
||||
|
||||
// 发送实时文本同步 (替代原来的 sendMessage)
|
||||
const sendTextSync = useCallback((text: string) => {
|
||||
if (!connection) return;
|
||||
if (!connection || !connection.isPeerConnected) return;
|
||||
|
||||
const message = {
|
||||
type: 'text-sync',
|
||||
payload: { text }
|
||||
};
|
||||
|
||||
connection.sendMessage(message, CHANNEL_NAME);
|
||||
console.log('发送实时文本同步:', text.length, '字符');
|
||||
const success = connection.sendMessage(message, CHANNEL_NAME);
|
||||
if (success) {
|
||||
console.log('发送实时文本同步:', text.length, '字符');
|
||||
}
|
||||
}, [connection]);
|
||||
|
||||
// 发送打字状态
|
||||
const sendTypingStatus = useCallback((isTyping: boolean) => {
|
||||
if (!connection) return;
|
||||
if (!connection || !connection.isPeerConnected) return;
|
||||
|
||||
const message = {
|
||||
type: 'text-typing',
|
||||
payload: { typing: isTyping }
|
||||
};
|
||||
|
||||
connection.sendMessage(message, CHANNEL_NAME);
|
||||
console.log('发送打字状态:', isTyping);
|
||||
const success = connection.sendMessage(message, CHANNEL_NAME);
|
||||
if (success) {
|
||||
console.log('发送打字状态:', isTyping);
|
||||
}
|
||||
}, [connection]);
|
||||
|
||||
// 设置文本同步回调
|
||||
|
||||
Reference in New Issue
Block a user