You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:08:23