react-native-webrtc远程流显示空白/黑屏问题求助
远程视频流显示空白/黑屏问题排查
问题描述
我被这个问题卡了3天,开发一个简单的视频通话应用时,远程流显示空白。给RTCView添加objectFit="cover"属性后,会变成黑屏而非空白。日志显示远程流已被接收,但仍无法正常显示,想问这是STUN服务器的问题还是其他原因?
代码实现
import React, { useState, useEffect } from 'react'; import { View, Text, Button } from 'react-native'; import { RTCPeerConnection, RTCView, mediaDevices } from 'react-native-webrtc'; import socket from './socket'; const STUN_SERVERS = [ { urls: 'stun:stun.l.google.com:19302' }, // Add more STUN servers if needed ]; const VideoCall = () => { const [localStream, setLocalStream] = useState(null); const [remoteStream, setRemoteStream] = useState(null); const [peerConnections, setPeerConnections] = useState([]); const socketInstance = socket() useEffect(() => { // Initialize local stream mediaDevices.getUserMedia({ audio: true, video: true }) .then(stream => setLocalStream(stream)) .catch(error => console.error('Error getting user media:', error)); return () => { socketInstance.disconnect(); // Clean up socketInstance connection on component unmount }; }, []); useEffect(() => { if (localStream) { // Create a peer connection for each user const pc = new RTCPeerConnection({ iceServers: STUN_SERVERS, }); setPeerConnections(prevConnections => [...prevConnections, pc]); // Add local stream to peer connection localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); // Handle ICE candidate events pc.onicecandidate = event => { if (event.candidate) { // Send ICE candidate to all connected clients socketInstance.emit('ice-candidate', { candidate: event.candidate }); } }; // Handle remote stream events pc.ontrack = event => { setRemoteStream(event.streams[0]); }; } }, [localStream]); useEffect(()=>{ if (localStream){ socketInstance.on('offer', data => { handleOffer(data); }); } }, [localStream]) const handleOffer = data => { if (!localStream) { console.error('Local stream is not yet available'); return; } console.log('offerHandle Called') const pc = new RTCPeerConnection({ iceServers: STUN_SERVERS, }); setPeerConnections(prevConnections => [...prevConnections, pc]); // Add local stream to peer connection localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); pc.onicecandidate = event => { if (event.candidate) { socketInstance.emit('ice-candidate', { candidate: event.candidate }); } }; pc.ontrack = event => { console.log("Received tracks:", event.streams[0].getTracks()); // Log the tracks // Check the kind of the track if (event.track.kind === 'video') { // If it's a video track, update remoteStream setRemoteStream(event.streams[0]); } // You can also handle audio tracks if needed }; pc.setRemoteDescription(data.offer) .then(() => pc.createAnswer()) .then(answer => { socketInstance.emit('answer', { answer, senderSocketId: data.senderUserId }); return pc.setLocalDescription(answer); }); }; useEffect(() => { console.log('remote stream inUseeffect: ', remoteStream); }, [remoteStream]); const handleCall = () => { // Send offer to all connected clients peerConnections.forEach(pc => { pc.createOffer() .then(offer => { // Send the offer to all clients socketInstance.emit('offer', { offer }); pc.setLocalDescription(offer); }); }); }; return ( <View> <Text>Video Call Screen</Text> {localStream && <RTCView streamURL={localStream.toURL()} zOrder={1} style={{ width: 200, height: 150 }} />} {remoteStream && <RTCView streamURL={remoteStream.toURL()} zOrder={2} style={{ width: 200, height: 150 }} />} <Button title="Call" onPress={handleCall} /> </View> ); }; export default VideoCall;
日志信息
LOG rn-webrtc:pc:DEBUG 0 ctor +0ms LOG rn-webrtc:pc:DEBUG 0 addTrack +4ms LOG rn-webrtc:pc:DEBUG 0 addTrack +54ms LOG rn-webrtc:pc:DEBUG 0 createOffer +12s LOG rn-webrtc:pc:DEBUG 0 createOffer OK +117ms LOG rn-webrtc:pc:DEBUG 0 setLocalDescription +4ms LOG offerHandle Called LOG rn-webrtc:pc:DEBUG 1 ctor +159ms LOG rn-webrtc:pc:DEBUG 1 addTrack +216ms LOG rn-webrtc:pc:DEBUG 1 addTrack +61ms LOG rn-webrtc:pc:DEBUG 1 setRemoteDescription +52ms LOG rn-webrtc:pc:DEBUG 0 setLocalDescription OK +25ms LOG rn-webrtc:pc:DEBUG 1 ontrack +204ms LOG rn-webrtc:pc:DEBUG 1 ontrack +82ms LOG Received tracks: [{"_constraints": {}, "_enabled": true, "_muted": false, "_peerConnectionId": 1, "_readyState": "live", "_settings": {}, "id": "73d50db6-9154-4fd4-b274-c6c94f3ed3da", "kind": "audio", "label": "", "remote": true}] LOG Received tracks: [{"_constraints": {}, "_enabled": true, "_muted": false, "_peerConnectionId": 1, "_readyState": "live", "_settings": {}, "id": "73d50db6-9154-4fd4-b274-c6c94f3ed3da", "kind": "audio", "label": "", "remote": true}, {"_constraints": {}, "_enabled": true, "_muted": false, "_peerConnectionId": 1, "_readyState": "live", "_settings": {}, "id": "5e19c8a9-23be-48d9-b0c6-3502b8323554", "kind": "video", "label": "", "remote": true}] LOG rn-webrtc:pc:DEBUG 1 setRemoteDescription OK +93ms LOG rn-webrtc:pc:DEBUG 1 createAnswer +2ms LOG remote stream inUseeffect: {"_id": "cdf55a01-3732-474e-a936-02b619d44aed", "_reactTag": "aeefb386-5ce1-44af-8050-77b02b097615", "_tracks": [{"_constraints": [Object], "_enabled": true, "_muted": false, "_peerConnectionId": 1, "_readyState": "live", "_settings": [Object], "id": "73d50db6-9154-4fd4-b274-c6c94f3ed3da", "kind": "audio", "label": "", "remote": true}, {"_constraints": [Object], "_enabled": true, "_muted": false, "_peerConnectionId": 1, "_readyState": "live", "_settings": [Object], "id": "5e19c8a9-23be-48d9-b0c6-3502b8323554", "kind": "video", "label": "", "remote": true}]}
问题分析与解决方案
从日志看,远程音视频track都已接收且状态为live,说明STUN服务器大概率没问题——ICE连接已经建立,track能正常接收。问题出在其他环节:
1. 缺失ICE Candidate处理(核心问题)
代码只实现了发送ICE Candidate,完全没处理对方发来的Candidate!这会导致媒体通道无法完全建立,虽然能收到track,但实际没有视频数据传输。
补充以下代码:
useEffect(() => { if (localStream) { socketInstance.on('ice-candidate', data => { peerConnections.forEach(pc => { pc.addIceCandidate(data.candidate).catch(err => console.error('添加ICE Candidate失败:', err)); }); }); } }, [localStream, peerConnections]);
2. PeerConnection实例管理混乱
一对一通话场景下,你创建了多个PC实例(localStream初始化时创建一个,收到offer又创建一个),会引发流冲突、资源浪费。建议维护单个PC实例,而非数组:
- 把
peerConnections改成单个状态变量peerConnection - 组件卸载时调用
peerConnection?.close()清理资源
3. RTCView显示属性问题
- 尝试给RTCView明确设置
objectFit="contain"或cover,同时确保宽高样式生效 - 可以尝试用
streamId={remoteStream.id}替代streamURL={remoteStream.toURL()},部分RN-WebRTC版本对streamId支持更稳定 - 检查
zOrder是否导致远程流被遮挡(当前设置zOrder=2没问题,但可以临时调整验证)
4. 流状态验证
在ontrack事件里添加视频track的细节打印,确认视频参数正常:
pc.ontrack = event => { if (event.track.kind === 'video') { console.log('视频track参数:', event.track.getSettings()); setRemoteStream(event.streams[0]); } };
内容的提问来源于stack exchange,提问作者gulshan _____
相关产品推荐
相关产品推荐

