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); } };
三、关键注意事项
- ICE服务器配置:确保使用有效的STUN/TURN服务器,避免NAT环境下连接失败,比如:
const iceServers = [ { urls: 'stun:stun.l.google.com:19302' }, // 生产环境建议添加自己的TURN服务器 ];
- 连接清理:用户离开房间时,要主动关闭对应PeerConnection并移除远程流,防止内存泄漏;
- React状态管理:渲染远程流时,遍历
remoteStreams的entries即可实现多流展示。
内容来源于stack exchange
相关产品推荐
相关产品推荐

