feat: 重构 WebRTC 轨道管理,支持多监听器注册,优化 SDP Offer 创建流程;更新桌面共享和语音通话业务逻辑,增强连接管理

This commit is contained in:
MatrixSeven
2026-03-05 13:28:29 +08:00
parent da3e4f1067
commit 6d02a9898f
11 changed files with 545 additions and 735 deletions

View File

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