React+NestJS开发WebRTC视频聊天时远端视频无法渲染问题排查
问题分析与解决方案
错误原因
InvalidStateError: Called in wrong state: stable 表示你尝试在RTCPeerConnection处于stable状态时设置远端answer SDP。WebRTC的信令状态机规定,stable状态意味着连接已经完成一次成功的协商,此时重复设置answer属于非法操作。
具体问题排查与修复方案
1. 避免重复创建PeerConnection
当前handleOffer逻辑中,每次收到offer都会创建新的PeerConnection并覆盖已有连接。如果同一用户多次发送offer(比如网络重发),旧连接可能已处于stable状态,后续收到对应answer时就会触发错误。
修复代码:
const handleOffer = async (from: string, offer: RTCSessionDescriptionInit) => { // 先检查是否已有该用户的PeerConnection,避免重复创建 let pc = peerConnections.current[from]; if (!pc) { pc = createPeerConnection(from); peerConnections.current[from] = pc; // 仅在新连接时添加本地轨道 localStream?.getTracks().forEach((track) => pc.addTrack(track, localStream)); } try { console.log(`Received offer from ${from}. Setting remote description.`); // 仅在非stable状态下处理信令 if (pc.signalingState !== 'stable') { await pc.setRemoteDescription(new RTCSessionDescription(offer)); console.log(pc.signalingState, 'offer state') const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); socket.emit('answer', { to: from, answer }); } } catch (error) { console.error("Error while setting remote description or creating answer:", error); } finally { console.log('Offer Set') } };
2. 过滤重复的Answer消息
在handleAnswer中,先检查PeerConnection的状态,如果已经是stable,说明之前已经处理过该用户的answer,直接忽略即可:
const handleAnswer = async (from: string, answer: RTCSessionDescriptionInit) => { const pc = peerConnections.current[from]; console.log(pc?.signalingState, 'answer state') // 仅在非stable状态下处理answer if (pc && pc.signalingState !== 'stable') { try { console.log(`Received answer from ${from}. Setting remote description.`); await pc.setRemoteDescription(new RTCSessionDescription(answer)); } catch (error) { console.error("Error while setting remote description for answer:", error); } finally { console.log('Answer Set') } } };
3. 清理Socket事件监听,避免重复处理
虽然useEffect仅执行一次,但Socket库可能存在重连后自动重复订阅的情况,导致同一条消息被多次处理。在组件卸载时需要移除所有事件监听:
return () => { // 关闭所有Peer连接并停止本地轨道 Object.values(peerConnections.current).forEach((pc) => pc.close()); localStream?.getTracks().forEach((track) => track.stop()); // 移除所有Socket事件监听 socket.off('user-joined'); socket.off('offer'); socket.off('answer'); socket.off('ice-candidate'); socket.disconnect(); };
4. 检查服务器端信令逻辑
确认你的NestJS服务器不会重复转发offer或answer消息,比如用户加入房间时是否多次触发user-joined事件,导致客户端重复创建并发送offer,进而收到多个answer。
额外调试建议
在PeerConnection中添加连接状态监听,方便跟踪连接生命周期:
const createPeerConnection = (userId: string) => { const pc = new RTCPeerConnection(); // 新增ICE连接状态监听 pc.oniceconnectionstatechange = () => { console.log(`ICE connection state for ${userId}:`, pc.iceConnectionState); // 连接断开时清理对应资源 if (pc.iceConnectionState === 'disconnected' || pc.iceConnectionState === 'closed') { delete peerConnections.current[userId]; setRemoteStreams(prev => { const newStreams = {...prev}; delete newStreams[userId]; return newStreams; }); } }; // 原有onicecandidate和ontrack逻辑... return pc; };
内容的提问来源于stack exchange,提问作者Asking
相关产品推荐
相关产品推荐

