/** * WebRTC 连接管理模块 * * 功能: * - 管理 RTCPeerConnection 生命周期 * - 处理 SDP 交换 * - 处理 ICE 候选收集 * - 音频流管理 */ export type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed' export interface WebRTCConfig { iceServers?: RTCIceServer[] onLocalStream?: (stream: MediaStream) => void onRemoteStream?: (stream: MediaStream) => void onConnectionStateChange?: (state: ConnectionState) => void onIceCandidate?: (candidate: RTCIceCandidate) => void onError?: (error: Error) => void } // 默认 ICE 服务器配置 const DEFAULT_ICE_SERVERS: RTCIceServer[] = [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }, { urls: 'stun:stun2.l.google.com:19302' } ] export class WebRTCManager { private peerConnection: RTCPeerConnection | null = null private localStream: MediaStream | null = null private remoteStream: MediaStream | null = null private config: WebRTCConfig private connectionState: ConnectionState = 'idle' private pendingIceCandidates: RTCIceCandidate[] = [] constructor(config: WebRTCConfig = {}) { this.config = { iceServers: DEFAULT_ICE_SERVERS, ...config } } /** * 获取当前连接状态 */ getConnectionState(): ConnectionState { return this.connectionState } /** * 获取本地音频流 */ getLocalStream(): MediaStream | null { return this.localStream } /** * 获取远程音频流 */ getRemoteStream(): MediaStream | null { return this.remoteStream } /** * 初始化本地音频流 */ async initLocalStream(): Promise { try { this.localStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }, video: false }) this.config.onLocalStream?.(this.localStream) return this.localStream } catch (error) { const err = error instanceof Error ? error : new Error('获取麦克风权限失败') this.config.onError?.(err) throw err } } /** * 创建 PeerConnection */ private createPeerConnection(): RTCPeerConnection { const pc = new RTCPeerConnection({ iceServers: this.config.iceServers }) // 监听 ICE 候选 pc.onicecandidate = (event) => { if (event.candidate) { console.log('[WebRTC] ICE candidate:', event.candidate.candidate?.substring(0, 50)) this.config.onIceCandidate?.(event.candidate) } } // 监听连接状态变化 pc.onconnectionstatechange = () => { console.log('[WebRTC] Connection state:', pc.connectionState) this.updateConnectionState(pc.connectionState) } // 监听 ICE 连接状态 pc.oniceconnectionstatechange = () => { console.log('[WebRTC] ICE connection state:', pc.iceConnectionState) if (pc.iceConnectionState === 'failed') { this.updateConnectionState('failed') } } // 监听远程流 pc.ontrack = (event) => { console.log('[WebRTC] Remote track received') if (event.streams && event.streams[0]) { this.remoteStream = event.streams[0] this.config.onRemoteStream?.(this.remoteStream) } } return pc } /** * 更新连接状态 */ private updateConnectionState(state: RTCPeerConnectionState | string) { const stateMap: Record = { 'new': 'connecting', 'connecting': 'connecting', 'connected': 'connected', 'disconnected': 'disconnected', 'failed': 'failed', 'closed': 'disconnected' } this.connectionState = stateMap[state] || 'idle' this.config.onConnectionStateChange?.(this.connectionState) } /** * 创建 Offer(发起方调用) */ async createOffer(): Promise { if (!this.localStream) { await this.initLocalStream() } this.peerConnection = this.createPeerConnection() this.updateConnectionState('connecting') // 添加本地音频轨道 this.localStream!.getTracks().forEach(track => { this.peerConnection!.addTrack(track, this.localStream!) }) // 创建 Offer const offer = await this.peerConnection.createOffer() await this.peerConnection.setLocalDescription(offer) console.log('[WebRTC] Offer created') return offer } /** * 处理 Offer(接收方调用) */ async handleOffer(offer: RTCSessionDescriptionInit): Promise { if (!this.localStream) { await this.initLocalStream() } this.peerConnection = this.createPeerConnection() this.updateConnectionState('connecting') // 添加本地音频轨道 this.localStream!.getTracks().forEach(track => { this.peerConnection!.addTrack(track, this.localStream!) }) // 设置远程描述 await this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)) // 处理等待中的 ICE 候选 for (const candidate of this.pendingIceCandidates) { await this.peerConnection.addIceCandidate(candidate) } this.pendingIceCandidates = [] // 创建 Answer const answer = await this.peerConnection.createAnswer() await this.peerConnection.setLocalDescription(answer) console.log('[WebRTC] Answer created') return answer } /** * 处理 Answer(发起方调用) */ async handleAnswer(answer: RTCSessionDescriptionInit): Promise { if (!this.peerConnection) { throw new Error('PeerConnection not initialized') } await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer)) // 处理等待中的 ICE 候选 for (const candidate of this.pendingIceCandidates) { await this.peerConnection.addIceCandidate(candidate) } this.pendingIceCandidates = [] console.log('[WebRTC] Answer handled') } /** * 添加 ICE 候选 */ async addIceCandidate(candidate: RTCIceCandidateInit): Promise { const iceCandidate = new RTCIceCandidate(candidate) if (this.peerConnection && this.peerConnection.remoteDescription) { await this.peerConnection.addIceCandidate(iceCandidate) console.log('[WebRTC] ICE candidate added') } else { // 如果远程描述还没设置,先缓存候选 this.pendingIceCandidates.push(iceCandidate) console.log('[WebRTC] ICE candidate queued') } } /** * 静音/取消静音本地音频 */ setMuted(muted: boolean): void { if (this.localStream) { this.localStream.getAudioTracks().forEach(track => { track.enabled = !muted }) } } /** * 检查是否静音 */ isMuted(): boolean { if (this.localStream) { const audioTrack = this.localStream.getAudioTracks()[0] return audioTrack ? !audioTrack.enabled : true } return true } /** * 获取音频音量级别(用于音量指示器) */ async getAudioLevel(stream: MediaStream): Promise { return new Promise((resolve) => { const audioContext = new AudioContext() const analyser = audioContext.createAnalyser() const source = audioContext.createMediaStreamSource(stream) source.connect(analyser) analyser.fftSize = 256 const dataArray = new Uint8Array(analyser.frequencyBinCount) analyser.getByteFrequencyData(dataArray) // 计算平均音量 const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length audioContext.close() resolve(average / 255) // 归一化到 0-1 }) } /** * 关闭连接 */ close(): void { console.log('[WebRTC] Closing connection') // 停止本地流 if (this.localStream) { this.localStream.getTracks().forEach(track => track.stop()) this.localStream = null } // 停止远程流 if (this.remoteStream) { this.remoteStream.getTracks().forEach(track => track.stop()) this.remoteStream = null } // 关闭 PeerConnection if (this.peerConnection) { this.peerConnection.close() this.peerConnection = null } this.pendingIceCandidates = [] this.updateConnectionState('disconnected') } /** * 重置管理器 */ reset(): void { this.close() this.connectionState = 'idle' } } // 导出单例工厂函数 export function createWebRTCManager(config?: WebRTCConfig): WebRTCManager { return new WebRTCManager(config) }