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

ReactJS+Socket.io+WebRTC视频通话远端流无法显示问题

解决WebRTC视频通话灰色屏幕与ICE连接状态停滞问题

你的问题核心在于ICE候选者未通过信令服务器交换,导致PeerConnection无法建立实际的媒体传输连接,同时还有几处代码细节错误需要修正。以下是具体问题和修复方案:

1. 未处理ICE候选者交换(核心问题)

你的代码完全没有处理ICE候选者的收集与传递,这是WebRTC建立P2P连接的关键步骤。ICE连接状态一直停留在new,就是因为没有任何ICE候选者被交换,PeerConnection无法找到可用的传输路径。

修复步骤:

前端(React)添加ICE候选监听与发送

在React组件的useEffect中补充ICE候选的处理逻辑:

useEffect(() => {
  // 原有代码...
  
  // 监听ICE候选生成并发送给对等端
  PC.onicecandidate = (event) => {
    if (event.candidate && currentCallTarget) {
      socket.emit('ice-candidate', {
        candidate: event.candidate,
        to: currentCallTarget
      });
    }
  };

  // 接收远端ICE候选并添加到PeerConnection
  socket.on('ice-candidate-received', (data) => {
    PC.addIceCandidate(new RTCIceCandidate(data.candidate));
  });

  // 原有清理代码...
}, [currentCallTarget]);

信令服务器添加ICE候选转发逻辑

在NodeJS服务器中添加ICE候选的转发处理:

io.on('connection', socket => {
  // 原有代码...

  socket.on('ice-candidate', (data) => {
    socket.to(data.to).emit('ice-candidate-received', {
      candidate: data.candidate,
      from: socket.id
    });
  });

  // 原有代码...
});

2. 远端视频流赋值错误

在onAddStream函数中,你错误地使用vid.src = obj.stream,正确的做法是设置srcObject属性:

const onAddStream = obj => {
  let vid = document.createElement('video');
  vid.setAttribute('class', 'video-small');
  vid.setAttribute('autoplay', 'autoplay');
  vid.setAttribute('muted', 'muted');
  vid.setAttribute('crossorigin', 'anonymous')
  vid.setAttribute('id', 'video-small');

  // 修正流赋值方式
  vid.srcObject = obj.stream;
  document.getElementById('users-container').appendChild(vid);
}

3. PeerConnection实例全局化问题

你将PC定义为组件外部的全局变量,会导致多组件实例共享同一个PeerConnection,在多用户场景下出现冲突。应将PeerConnection的创建移到组件内部:

export function VideoPlayer () {
  const localVideoRef = useRef(null)
  const videoRef = useRef(null)
  const [users, setUsers] = useState([])
  const [currentCallTarget, setCurrentCallTarget] = useState(null);
  // 组件内部创建独立的PeerConnection实例
  const PC = useRef(new RTCPeerConnection({ iceServers: [{urls: 'stun:stun.l.google.com:19302'}] })).current;

  // 原有代码...
}

4. 维护当前通话的目标用户ID

需要在创建Offer时记录当前通话的目标用户ID,确保ICE候选能发送到正确的对等端:

const createOffer = async id => {
  setCurrentCallTarget(id); // 记录目标用户ID
  const offer = await PC.createOffer()
  await PC.setLocalDescription(offer)
  socket.emit('make-offer', {
    offer: offer,
    to: id
  })
}

5. 信令服务器客户端列表初始化顺序错误

服务器在connection事件中先发送客户端列表,再将当前socket.id加入数组,导致新连接的客户端无法获取完整用户列表。调整顺序:

io.on('connection', socket => {
  // 先加入列表,再发送用户列表
  clients.push(socket.id)
  
  socket.emit('add-users', {
    users: clients
  })

  socket.broadcast.emit('add-users', {
    users: [socket.id]
  })

  // 原有代码...
})

完成以上修复后,ICE连接状态会逐步从new过渡到checking、connected,远端视频也能正常显示。

内容的提问来源于stack exchange,提问作者Usman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:54:53