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

React Native流媒体应用ICE连接断开问题及多端互看流功能实现咨询

React Native流媒体应用ICE连接断开问题及多端互看流功能实现咨询

你好,我看了你的代码和问题描述,目前核心问题是用户发起流时主播看不到,且ICE连接状态显示disconnected,同时你还需要实现主播能看到所有用户流、用户之间互看的功能。下面我来一步步分析并给出解决方案:

一、当前ICE连接断开问题的核心原因

从你的代码逻辑来看,主要问题出在媒体收发方向的判断逻辑和连接触发的时机匹配上:

  • 你依赖!!localStreamRef.current来决定收发方向,但当用户启动流时,可能存在localStreamRef已就绪但连接逻辑触发时状态未同步的情况,导致收发器方向设置错误;
  • ICE连接断开大概率是因为两端的SDP协商不匹配,比如一端设置了recvonly而另一端也未正确设置sendonly,导致ICE候选无法正常连通,最终触发连接断开。

二、修复ICE连接+实现多端互看的具体方案

1. 优化connectToStreamer函数的媒体收发逻辑

我们需要去掉单一的isSendingStream判断,改为支持动态收发,同时增加自动协商和连接清理逻辑,确保两端媒体能力匹配:

const connectToStreamer = async (streamerId) => {
  if (streamerId === socket.id || peerConnections.current[streamerId]) {
    console.log(`Skipping connection to ${streamerId}: Already connected or self`);
    return;
  }
  try {
    const pc = new RTCPeerConnection(iceServers);
    peerConnections.current[streamerId] = pc;

    // 🌟 关键修改:默认支持双向收发,适配多端互看场景
    const hasLocalStream = !!localStreamRef.current;
    if (hasLocalStream) {
      localStreamRef.current.getTracks().forEach(track => {
        pc.addTrack(track, localStreamRef.current);
      });
    } else {
      // 用sendrecv替代recvonly,后续本地流就绪时可自动触发协商
      pc.addTransceiver('video', { direction: 'sendrecv' });
      pc.addTransceiver('audio', { direction: 'sendrecv' });
    }

    pc.ontrack = event => {
      if (event.streams[0]) {
        console.log(`Received stream from ${streamerId}:`, event.streams[0]);
        setRemoteStreams(prev => new Map(prev).set(streamerId, event.streams[0]));
      }
    };

    pc.onicecandidate = event => {
      if (event.candidate) {
        console.log(`Sending ICE candidate to ${streamerId}`);
        socket.emit('ice-candidate', { target: streamerId, candidate: event.candidate });
      }
    };

    pc.oniceconnectionstatechange = () => {
      socket.emit('Errorlogs','pc.oniceconnectionstatechange',`ICE connection state changed for ${streamerId}: ${pc.iceConnectionState}`);
      console.log(`ICE state for ${streamerId}: ${pc.iceConnectionState}`);
      
      if (['failed', 'disconnected'].includes(pc.iceConnectionState)) {
        setTimeout(() => {
          if (pc.iceConnectionState !== 'connected' && peerConnections.current[streamerId]) {
            console.log(`Retrying connection to ${streamerId}`);
            // 重试前清理旧连接,避免资源泄漏
            peerConnections.current[streamerId].close();
            delete peerConnections.current[streamerId];
            connectToStreamer(streamerId);
          }
        }, 5000);
      } else if (pc.iceConnectionState === 'closed') {
        delete peerConnections.current[streamerId];
        setRemoteStreams(prev => {
          const newStreams = new Map(prev);
          newStreams.delete(streamerId);
          return newStreams;
        });
      }
    };

    // 🌟 新增自动协商:媒体轨道变化时自动发起SDP协商
    pc.onnegotiationneeded = async () => {
      try {
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        socket.emit('offer', { target: streamerId, sdp: offer });
      } catch (err) {
        console.error('Error during media negotiation:', err);
      }
    };

    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);
    console.log(`Sending offer to ${streamerId}`);
    socket.emit('offer', { target: streamerId, sdp: offer });
  } catch (err) {
    socket.emit('Errorlogs', err);
    console.error(`Error connecting to streamer ${streamerId}:`, err);
    if (peerConnections.current[streamerId]) {
      peerConnections.current[streamerId].close();
      delete peerConnections.current[streamerId];
    }
  }
};

2. 完善Socket事件处理,实现多端两两连接

多端互看的核心是房间内每个用户和其他所有用户建立PeerConnection,所以需要在Socket层补充连接触发逻辑:

// 新用户加入房间时,当前用户主动发起连接
socket.on('user-joined', async (newUserId) => {
  await connectToStreamer(newUserId);
  // 通知新用户与自己建立连接
  socket.emit('request-connection', { target: newUserId, from: socket.id });
});

// 处理其他用户的连接请求
socket.on('request-connection', async (data) => {
  await connectToStreamer(data.from);
});

// 处理收到的Offer
socket.on('offer', async (data) => {
  const { from, sdp } = data;
  if (!peerConnections.current[from]) await connectToStreamer(from);
  
  const pc = peerConnections.current[from];
  await pc.setRemoteDescription(new RTCSessionDescription(sdp));
  const answer = await pc.createAnswer();
  await pc.setLocalDescription(answer);
  socket.emit('answer', { target: from, sdp: answer });
});

// 处理收到的Answer
socket.on('answer', async (data) => {
  const { from, sdp } = data;
  const pc = peerConnections.current[from];
  if (pc) await pc.setRemoteDescription(new RTCSessionDescription(sdp));
});

// 处理收到的ICE候选
socket.on('ice-candidate', async (data) => {
  const { from, candidate } = data;
  const pc = peerConnections.current[from];
  if (pc) await pc.addIceCandidate(new RTCIceCandidate(candidate));
});

3. 本地流就绪后的同步逻辑

当用户(包括主播)成功获取本地流后,需要同步到所有已建立的PeerConnection:

const startLocalStream = async () => {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    localStreamRef.current = stream;
    // 给所有已连接的PeerConnection添加本地轨道
    peerConnections.current.forEach((pc, userId) => {
      stream.getTracks().forEach(track => pc.addTrack(track, stream));
    });
  } catch (err) {
    console.error('Failed to get local stream:', err);
  }
};

三、关键注意事项

  1. ICE服务器配置:确保使用有效的STUN/TURN服务器,避免NAT环境下连接失败,比如:
const iceServers = [
  { urls: 'stun:stun.l.google.com:19302' },
  // 生产环境建议添加自己的TURN服务器
];
  1. 连接清理:用户离开房间时,要主动关闭对应PeerConnection并移除远程流,防止内存泄漏;
  2. React状态管理:渲染远程流时,遍历remoteStreams的entries即可实现多流展示。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 10:09:33