mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-06-11 13:13:11 +08:00
feat: 重构 WebRTC 轨道管理,支持多监听器注册,优化 SDP Offer 创建流程;更新桌面共享和语音通话业务逻辑,增强连接管理
This commit is contained in:
@@ -1,34 +1,42 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { WebRTCStateManager } from '../ui/webRTCStore';
|
||||
import type { TrackHandler, Unsubscribe } from './types';
|
||||
|
||||
/**
|
||||
* WebRTC 媒体轨道管理器
|
||||
* 负责媒体轨道的添加和移除
|
||||
* WebRTC 媒体轨道管理器接口
|
||||
*/
|
||||
export interface WebRTCTrackManager {
|
||||
// 添加媒体轨道
|
||||
// 添加媒体轨道到 PeerConnection
|
||||
addTrack: (track: MediaStreamTrack, stream: MediaStream) => RTCRtpSender | null;
|
||||
|
||||
// 移除媒体轨道
|
||||
removeTrack: (sender: RTCRtpSender) => void;
|
||||
|
||||
// 设置轨道处理器
|
||||
onTrack: (handler: (event: RTCTrackEvent) => void) => void;
|
||||
/**
|
||||
* 注册轨道事件处理器(多监听器模式)
|
||||
* - 多个消费者可同时注册(桌面共享处理 video,语音通话处理 audio)
|
||||
* - 返回清理函数,组件卸载时务必调用
|
||||
*/
|
||||
registerTrackHandler: (key: string, handler: TrackHandler) => Unsubscribe;
|
||||
|
||||
// 创建 Offer
|
||||
// 创建并发送 SDP Offer(用于初始连接)
|
||||
createOffer: (pc: RTCPeerConnection, ws: WebSocket) => Promise<void>;
|
||||
|
||||
// 立即创建offer(用于媒体轨道添加后的重新协商)
|
||||
// 立即创建 Offer(用于媒体轨道变更后的重新协商)
|
||||
createOfferNow: (pc: RTCPeerConnection, ws: WebSocket) => Promise<boolean>;
|
||||
|
||||
// 内部方法,供核心连接管理器调用
|
||||
// ── 内部方法,仅供 ConnectionCore 调用 ──
|
||||
setPeerConnection: (pc: RTCPeerConnection | null) => void;
|
||||
setWebSocket: (ws: WebSocket | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebRTC 媒体轨道管理 Hook
|
||||
* 负责媒体轨道的添加和移除,处理轨道事件,提供 createOffer 功能
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理 RTCRtpSender(添加 / 移除轨道)
|
||||
* 2. 复合分发 ontrack 事件给多个消费者
|
||||
* 3. 创建 SDP Offer 并通过信令 WebSocket 发送
|
||||
*/
|
||||
export function useWebRTCTrackManager(
|
||||
stateManager: WebRTCStateManager
|
||||
@@ -36,78 +44,82 @@ export function useWebRTCTrackManager(
|
||||
const pcRef = useRef<RTCPeerConnection | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
// 创建 Offer
|
||||
// 多监听器:key → handler(如 'desktop-share' → handler, 'voice-chat' → handler)
|
||||
const trackHandlers = useRef<Map<string, TrackHandler>>(new Map());
|
||||
|
||||
// ── 复合分发:将 ontrack 事件广播给所有已注册的处理器 ──
|
||||
const dispatchTrackEvent = useCallback((event: RTCTrackEvent) => {
|
||||
const handlerCount = trackHandlers.current.size;
|
||||
if (handlerCount === 0) {
|
||||
console.warn('[TrackManager] 收到轨道事件但无处理器注册:', event.track.kind, event.track.id);
|
||||
return;
|
||||
}
|
||||
console.log(`[TrackManager] 📡 分发轨道事件 (${event.track.kind}) 给 ${handlerCount} 个处理器`);
|
||||
trackHandlers.current.forEach((handler, key) => {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (error) {
|
||||
console.error(`[TrackManager] 轨道处理器 "${key}" 执行出错:`, error);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ── SDP Offer 创建 ──
|
||||
const createOffer = useCallback(async (pc: RTCPeerConnection, ws: WebSocket) => {
|
||||
try {
|
||||
console.log('[TrackManager] 🎬 开始创建offer,当前轨道数量:', pc.getSenders().length);
|
||||
|
||||
// 确保连接状态稳定
|
||||
if (pc.connectionState !== 'connecting' && pc.connectionState !== 'new') {
|
||||
console.warn('[TrackManager] ⚠️ PeerConnection状态异常:', pc.connectionState);
|
||||
}
|
||||
console.log('[TrackManager] 🎬 开始创建 Offer,当前轨道数:', pc.getSenders().length);
|
||||
|
||||
const offer = await pc.createOffer({
|
||||
offerToReceiveAudio: true, // 改为true以支持音频接收
|
||||
offerToReceiveVideo: true, // 改为true以支持视频接收
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true,
|
||||
});
|
||||
|
||||
console.log('[TrackManager] 📝 Offer创建成功,设置本地描述...');
|
||||
await pc.setLocalDescription(offer);
|
||||
console.log('[TrackManager] ✅ 本地描述设置完成');
|
||||
|
||||
// 增加超时时间到5秒,给ICE候选收集更多时间
|
||||
// ICE 收集超时保护
|
||||
const iceTimeout = setTimeout(() => {
|
||||
console.log('[TrackManager] ⏱️ ICE收集超时,发送当前offer');
|
||||
if (ws.readyState === WebSocket.OPEN && pc.localDescription) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[TrackManager] 📤 发送 offer (超时发送)');
|
||||
console.log('[TrackManager] 📤 发送 Offer (ICE 收集超时)');
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// 如果ICE收集已经完成,立即发送
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
clearTimeout(iceTimeout);
|
||||
if (ws.readyState === WebSocket.OPEN && pc.localDescription) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[TrackManager] 📤 发送 offer (ICE收集完成)');
|
||||
console.log('[TrackManager] 📤 发送 Offer (ICE 已完成)');
|
||||
}
|
||||
} else {
|
||||
console.log('[TrackManager] 🧊 等待ICE候选收集...');
|
||||
// 监听ICE收集状态变化
|
||||
pc.onicegatheringstatechange = () => {
|
||||
console.log('[TrackManager] 🧊 ICE收集状态变化:', pc.iceGatheringState);
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
clearTimeout(iceTimeout);
|
||||
if (ws.readyState === WebSocket.OPEN && pc.localDescription) {
|
||||
ws.send(JSON.stringify({ type: 'offer', payload: pc.localDescription }));
|
||||
console.log('[TrackManager] 📤 发送 offer (ICE收集完成)');
|
||||
console.log('[TrackManager] 📤 发送 Offer (ICE 收集完成)');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 同时监听ICE候选事件,用于调试
|
||||
pc.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
console.log('[TrackManager] 🧊 收到ICE候选:', event.candidate.candidate.substring(0, 50) + '...');
|
||||
} else {
|
||||
console.log('[TrackManager] 🏁 ICE候选收集完成');
|
||||
console.log('[TrackManager] 🧊 ICE 候选:', event.candidate.candidate.substring(0, 50) + '...');
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TrackManager] ❌ 创建 offer 失败:', error);
|
||||
console.error('[TrackManager] ❌ 创建 Offer 失败:', error);
|
||||
stateManager.updateState({ error: '创建连接失败', isConnecting: false, canRetry: true });
|
||||
}
|
||||
}, [stateManager]);
|
||||
|
||||
// 添加媒体轨道
|
||||
// ── 轨道操作 ──
|
||||
|
||||
const addTrack = useCallback((track: MediaStreamTrack, stream: MediaStream) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.error('[TrackManager] PeerConnection 不可用');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return pc.addTrack(track, stream);
|
||||
} catch (error) {
|
||||
@@ -116,14 +128,12 @@ export function useWebRTCTrackManager(
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 移除媒体轨道
|
||||
const removeTrack = useCallback((sender: RTCRtpSender) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.error('[TrackManager] PeerConnection 不可用');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pc.removeTrack(sender);
|
||||
} catch (error) {
|
||||
@@ -131,88 +141,44 @@ export function useWebRTCTrackManager(
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 设置轨道处理器
|
||||
const onTrack = useCallback((handler: (event: RTCTrackEvent) => void) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) {
|
||||
console.warn('[TrackManager] PeerConnection 尚未准备就绪,将在连接建立后设置onTrack');
|
||||
// 检查WebSocket连接状态,只有连接后才尝试设置
|
||||
const state = stateManager.getState();
|
||||
if (!state.isWebSocketConnected) {
|
||||
console.log('[TrackManager] WebSocket未连接,等待连接建立...');
|
||||
return;
|
||||
}
|
||||
|
||||
// 延迟设置,等待PeerConnection准备就绪
|
||||
let retryCount = 0;
|
||||
const maxRetries = 50; // 增加重试次数到50次,即5秒
|
||||
|
||||
const checkAndSetTrackHandler = () => {
|
||||
const currentPc = pcRef.current;
|
||||
if (currentPc) {
|
||||
console.log('[TrackManager] ✅ PeerConnection 已准备就绪,设置onTrack处理器');
|
||||
currentPc.ontrack = handler;
|
||||
|
||||
// 如果已经有远程轨道,立即触发处理
|
||||
const receivers = currentPc.getReceivers();
|
||||
console.log(`[TrackManager] 📡 当前有 ${receivers.length} 个接收器`);
|
||||
receivers.forEach(receiver => {
|
||||
if (receiver.track) {
|
||||
console.log(`[TrackManager] 🎥 发现现有轨道: ${receiver.track.kind}, ${receiver.track.id}, 状态: ${receiver.track.readyState}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
retryCount++;
|
||||
if (retryCount < maxRetries) {
|
||||
// 每5次重试输出一次日志,减少日志数量
|
||||
if (retryCount % 5 === 0) {
|
||||
console.log(`[TrackManager] ⏳ 等待PeerConnection准备就绪... (尝试: ${retryCount}/${maxRetries})`);
|
||||
}
|
||||
setTimeout(checkAndSetTrackHandler, 100);
|
||||
} else {
|
||||
console.error('[TrackManager] ❌ PeerConnection 长时间未准备就绪,停止重试');
|
||||
}
|
||||
}
|
||||
};
|
||||
checkAndSetTrackHandler();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[TrackManager] ✅ 立即设置onTrack处理器');
|
||||
pc.ontrack = handler;
|
||||
|
||||
// 检查是否已有轨道
|
||||
const receivers = pc.getReceivers();
|
||||
console.log(`[TrackManager] 📡 当前有 ${receivers.length} 个接收器`);
|
||||
receivers.forEach(receiver => {
|
||||
if (receiver.track) {
|
||||
console.log(`[TrackManager] 🎥 发现现有轨道: ${receiver.track.kind}, ${receiver.track.id}, 状态: ${receiver.track.readyState}`);
|
||||
}
|
||||
});
|
||||
}, [stateManager]);
|
||||
// ── 多监听器注册 ──
|
||||
|
||||
const registerTrackHandler = useCallback((key: string, handler: TrackHandler): Unsubscribe => {
|
||||
console.log('[TrackManager] 注册轨道处理器:', key);
|
||||
trackHandlers.current.set(key, handler);
|
||||
|
||||
return () => {
|
||||
console.log('[TrackManager] 取消注册轨道处理器:', key);
|
||||
trackHandlers.current.delete(key);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ── 重新协商 ──
|
||||
|
||||
// 立即创建offer(用于媒体轨道添加后的重新协商)
|
||||
const createOfferNow = useCallback(async (pc: RTCPeerConnection, ws: WebSocket) => {
|
||||
if (!pc || !ws) {
|
||||
console.error('[TrackManager] PeerConnection 或 WebSocket 不可用');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await createOffer(pc, ws);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[TrackManager] 创建 offer 失败:', error);
|
||||
console.error('[TrackManager] 创建 Offer 失败:', error);
|
||||
return false;
|
||||
}
|
||||
}, [createOffer]);
|
||||
|
||||
// 设置 PeerConnection 引用
|
||||
// ── 内部引用设置(仅供 ConnectionCore 调用)──
|
||||
|
||||
const setPeerConnection = useCallback((pc: RTCPeerConnection | null) => {
|
||||
pcRef.current = pc;
|
||||
}, []);
|
||||
// 新 PeerConnection 创建时,挂载复合轨道分发器
|
||||
if (pc) {
|
||||
pc.ontrack = dispatchTrackEvent;
|
||||
}
|
||||
}, [dispatchTrackEvent]);
|
||||
|
||||
// 设置 WebSocket 引用
|
||||
const setWebSocket = useCallback((ws: WebSocket | null) => {
|
||||
wsRef.current = ws;
|
||||
}, []);
|
||||
@@ -220,11 +186,10 @@ export function useWebRTCTrackManager(
|
||||
return {
|
||||
addTrack,
|
||||
removeTrack,
|
||||
onTrack,
|
||||
registerTrackHandler,
|
||||
createOffer,
|
||||
createOfferNow,
|
||||
// 内部方法,供核心连接管理器调用
|
||||
setPeerConnection,
|
||||
setWebSocket,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user