React-Native-WebRTC故障求助:接收Answer时App崩溃(FeathersJS后端)
WebRTC通话崩溃与连接故障排查指南
我仔细梳理了你的React Native代码,发现了几个核心问题,这些问题不仅会导致接收Answer时崩溃,还会阻碍WebRTC建立正常的peer连接。下面逐个分析并给出修复方案:
1. PeerConnection实例生命周期管理错误
你直接在函数组件顶层初始化localPC:
const localPC = new RTCPeerConnection(configuration);
函数组件每次渲染都会重新执行这段代码,导致创建全新的PeerConnection实例,之前的连接状态全部丢失。必须用useRef来持久化这个实例,确保组件渲染时不会重建:
import { useRef } from 'react'; // 替换原来的localPC初始化 const localPC = useRef<RTCPeerConnection | null>(null); // 在需要的时候初始化(比如startCall或接收offer时) const initPeerConnection = () => { if (!localPC.current) { localPC.current = new RTCPeerConnection(configuration); } };
2. sendToPeer参数混乱导致数据错误
你的sendToPeer同时传递了sdp: payload和candidate: payload,这会让后端接收到的字段混淆。比如发送offer时,payload是SessionDescription,却被放到了candidate字段,接收answer时又错误地读取payload.candidate.sdp,这直接导致设置远程描述失败崩溃。
修复sendToPeer,根据消息类型传递正确的字段:
const sendToPeer = (messageType: any, payload: any) => { const data: any = { callType: messageType, caller: authState._id, answerer: userId, }; // 根据消息类型设置对应字段 if (messageType === 'offer' || messageType === 'answer') { data.sdp = payload; } else if (messageType === 'ice-candidate') { data.candidate = payload; } callService.create(data); };
3. 接收Answer时的字段访问错误
原来的recieveAnswer错误地访问payload.candidate.sdp,实际上answer的SessionDescription在payload.sdp字段。同时必须用异步+错误捕获,避免崩溃:
const recieveAnswer = async (payload: any) => { try { console.log('recieved answer: ', payload.sdp) await localPC.current!.setRemoteDescription(new RTCSessionDescription(payload.sdp)); } catch (err) { console.error('Failed to set remote answer:', err); } };
4. ICE Candidate处理的多重错误
- 绑定icecandidate事件重复监听:每次调用
createIceCandidate都会重新绑定onicecandidate,导致重复发送ICE候选。应该只绑定一次,放在PeerConnection初始化后。 - 添加ICE候选时格式错误:
addIceCandidate需要传入RTCIceCandidate对象,不是原始的JSON数据。 - 冗余的
addStream调用:接收ICE时不需要重复添加本地流,应该在获取本地流后只添加一次。
修复ICE相关逻辑:
// 扩展initPeerConnection,添加ICE和流监听 const initPeerConnection = () => { if (!localPC.current) { localPC.current = new RTCPeerConnection(configuration); // 绑定ICE候选事件 localPC.current.onicecandidate = e => { try { console.log('localPC icecandidate:', e.candidate); if (e.candidate) { sendToPeer('ice-candidate', e.candidate); } } catch (err) { console.error(`Error sending ICE candidate: ${err}`); } }; // 用ontrack替代废弃的onaddstream localPC.current.ontrack = e => { if (e.streams[0] && remoteStream !== e.streams[0]) { console.log('Remote stream received', e.streams[0]); setRemoteStream(e.streams[0]); } }; } }; // 获取本地流后添加到PeerConnection const startCall = async () => { console.log('start call function') initPeerConnection(); const newStream = await mediaDevices.getUserMedia({ audio: true, video: false, }); setLocalStream(newStream); // 添加本地流到PeerConnection(用addTrack替代废弃的addStream) newStream.getTracks().forEach(track => { localPC.current!.addTrack(track, newStream); }); await createOffer(); };
5. 流添加时机与API废弃问题
addStream和onaddstream是旧版API,已经被废弃,应该用addTrack和ontrack替代,确保兼容性。- 不能在
recieveCall中依赖localStream状态(useState是异步的),应该在获取到本地流后立即添加到PeerConnection,避免流未就绪的问题。
6. 组件卸载时未清理监听事件
你的useEffect中注释掉了事件移除逻辑,这会导致组件卸载后仍然监听Feathers事件,引发内存泄漏甚至崩溃。必须恢复清理:
useEffect(() => { const handleIceCandidate = (incoming: any) => { try { if (incoming.data.candidate && localPC.current) { localPC.current.addIceCandidate(new RTCIceCandidate(incoming.data.candidate)); } } catch (err) { console.error('Failed to add ICE candidate:', err); } }; const handleOffer = async (payload: any) => { if (payload.data.answerer === authState._id) { setOfferData(payload.data); setCallAlert(true); } }; const handleAnswer = async (payload: any) => { if (payload.data.answerer === authState._id) { await recieveAnswer(payload.data); } }; callService.on('ice-candidate', handleIceCandidate); callService.on('offer', handleOffer); callService.on('answer', handleAnswer); return () => { callService.removeListener('ice-candidate', handleIceCandidate); callService.removeListener('offer', handleOffer); callService.removeListener('answer', handleAnswer); // 关闭PeerConnection和流 if (localPC.current) { localPC.current.close(); localPC.current = null; } localStream?.getTracks().forEach(track => track.stop()); }; }, [callService, authState._id, userId]);
额外建议
- 确保Feathers后端正确转发事件,建议添加
callId标识区分不同通话,避免不同会话的消息混淆。 - 增加更多错误捕获和日志输出,方便排查连接过程中的每一步问题。
- 如果是内网环境测试,仅靠STUN服务器可能无法穿透NAT,需要配置TURN服务器。
内容的提问来源于stack exchange,提问作者Ping Kee Ng
相关产品推荐
相关产品推荐

