Web与React Native安卓电视P2P视频连接报错:不支持安全随机数生成
问题分析
错误Secure random number generation is not supported by this browser的根源是simple-peer库依赖浏览器环境的Web Crypto API,而React Native(尤其是安卓电视设备)没有提供完整的浏览器级Crypto实现,导致P2P连接初始化失败。该问题和RTCView本身无关,核心问题出在P2P连接的创建环节。
修复方案
放弃使用simple-peer,改用react-native-webrtc原生提供的RTCPeerConnection实现P2P连接,同时补全React Native端的媒体流获取逻辑,修正RTCView的使用方式。
1. 修正React Native端Context.js代码
替换simple-peer为react-native-webrtc的RTCPeerConnection,同时添加媒体流获取逻辑:
import React, { createContext, useState, useRef, useEffect } from 'react'; import { io } from 'socket.io-client'; import { RTCPeerConnection, mediaDevices } from 'react-native-webrtc'; const SocketContext = createContext(); const socket = io('http://10.0.2.2:8082'); // 安卓模拟器地址 // const socket = io('http://192.168.178.106:8082'); // 电视设备地址 const configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' } // 公共STUN服务器,用于NAT穿透 ] }; const ContextProvider = ({ children }) => { const [callAccepted, setCallAccepted] = useState(false); const [callEnded, setCallEnded] = useState(false); const [stream, setStream] = useState(null); const [name, setName] = useState(''); const [call, setCall] = useState({}); const [me, setMe] = useState(''); const [remoteStream, setRemoteStream] = useState(null); // 存储远端流 const peerConnectionRef = useRef(null); // 获取本地媒体流(摄像头/麦克风) useEffect(() => { const getMediaStream = async () => { try { const mediaStream = await mediaDevices.getUserMedia({ video: true, audio: true }); setStream(mediaStream); } catch (err) { console.error('获取媒体流失败:', err); } }; getMediaStream(); socket.on('me', (id) => setMe(id)); socket.on('callUser', ({ from, name: callerName, signal }) => { setCall({ isReceivingCall: true, from, name: callerName, signal }); }); }, []); // 接听电话逻辑 const answerCall = async () => { setCallAccepted(true); const peerConnection = new RTCPeerConnection(configuration); peerConnectionRef.current = peerConnection; // 添加本地流到连接 stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); }); // 监听远端流 peerConnection.ontrack = (event) => { setRemoteStream(event.streams[0]); }; // 处理ICE候选 peerConnection.onicecandidate = (event) => { if (event.candidate) { socket.emit('answerCall', { signal: event.candidate, to: call.from }); } }; // 设置远端SDP await peerConnection.setRemoteDescription(new RTCSessionDescription(call.signal)); // 创建本地SDP并发送 const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); socket.emit('answerCall', { signal: answer, to: call.from }); }; const leaveCall = () => { setCallEnded(true); if (peerConnectionRef.current) { peerConnectionRef.current.close(); peerConnectionRef.current = null; } if (stream) { stream.getTracks().forEach(track => track.stop()); } }; return ( <SocketContext.Provider value={{ call, callAccepted, remoteStream, stream, name, setName, callEnded, me, leaveCall, answerCall, }} > {children} </SocketContext.Provider> ); }; export { ContextProvider, SocketContext };
2. 修正React Native端CallScreen组件代码
修正RTCView的streamURL属性,直接使用远端流的toURL()方法:
import React, { useContext } from 'react'; import { View, StyleSheet, Text } from 'react-native'; import { RTCView } from 'react-native-webrtc'; import { SocketContext } from './Context'; import Callnotification from './Callnotifcation'; function CallScreen(props) { const { callAccepted, remoteStream, callEnded, me } = useContext(SocketContext); return ( <> {callAccepted && !callEnded && remoteStream && ( <View style={styles.root}> <View style={[styles.videos, styles.remoteVideos]}> <Text>Video of the caller</Text> <RTCView streamURL={remoteStream.toURL()} style={styles.remoteVideo} objectFit="cover" /> </View> </View> )} <View style={styles.root}> <Callnotification /> <Text>{me}</Text> </View> </> ); } const styles = StyleSheet.create({ root: { flex: 1, justifyContent: 'center', alignItems: 'center', }, videos: { width: '100%', height: '50%', }, remoteVideos: { backgroundColor: '#000', }, remoteVideo: { flex: 1, }, }); export default CallScreen;
3. 调整Web前端Context.js的信令逻辑(适配新的RTCPeerConnection)
因为RN端改用了原生RTCPeerConnection,Web端也需要调整为标准RTCPeerConnection,确保信令格式兼容:
import React, { createContext, useState, useRef, useEffect } from 'react'; import { io } from 'socket.io-client'; const SocketContext = createContext(); const socket = io('http://localhost:8082'); const configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' } ] }; const ContextProvider = ({ children }) => { const [callAccepted, setCallAccepted] = useState(false); const [callEnded, setCallEnded] = useState(false); const [stream, setStream] = useState(null); const [name, setName] = useState(''); const [call, setCall] = useState({}); const [me, setMe] = useState(''); const [remoteStream, setRemoteStream] = useState(null); const myVideo = useRef(); const userVideo = useRef(); const peerConnectionRef = useRef(null); useEffect(() => { const getMediaStream = async () => { try { const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); setStream(mediaStream); myVideo.current.srcObject = mediaStream; } catch (err) { console.error('获取媒体流失败:', err); } }; getMediaStream(); socket.on('me', (id) => setMe(id)); socket.on('callUser', async ({ from, name: callerName, signal }) => { setCall({ isReceivingCall: true, from, name: callerName, signal }); }); socket.on('callAccepted', async (signal) => { setCallAccepted(true); if (signal.type === 'answer') { await peerConnectionRef.current.setRemoteDescription(new RTCSessionDescription(signal)); } else if (signal.candidate) { await peerConnectionRef.current.addIceCandidate(new RTCIceCandidate(signal)); } }); }, []); const callUser = async (id) => { const peerConnection = new RTCPeerConnection(configuration); peerConnectionRef.current = peerConnection; stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); }); peerConnection.ontrack = (event) => { userVideo.current.srcObject = event.streams[0]; setRemoteStream(event.streams[0]); }; peerConnection.onicecandidate = (event) => { if (event.candidate) { socket.emit('callUser', { userToCall: id, signalData: event.candidate, from: me, name }); } }; const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); socket.emit('callUser', { userToCall: id, signalData: offer, from: me, name }); }; const answerCall = async () => { setCallAccepted(true); const peerConnection = new RTCPeerConnection(configuration); peerConnectionRef.current = peerConnection; stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); }); peerConnection.ontrack = (event) => { userVideo.current.srcObject = event.streams[0]; setRemoteStream(event.streams[0]); }; peerConnection.onicecandidate = (event) => { if (event.candidate) { socket.emit('answerCall', { signal: event.candidate, to: call.from }); } }; await peerConnection.setRemoteDescription(new RTCSessionDescription(call.signal)); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); socket.emit('answerCall', { signal: answer, to: call.from }); }; const leaveCall = () => { setCallEnded(true); if (peerConnectionRef.current) { peerConnectionRef.current.close(); peerConnectionRef.current = null; } if (stream) { stream.getTracks().forEach(track => track.stop()); } window.location.reload(); }; return ( <SocketContext.Provider value={{ call, callAccepted, myVideo, userVideo, remoteStream, stream, name, setName, callEnded, me, callUser, leaveCall, answerCall, }} > {children} </SocketContext.Provider> ); }; export { ContextProvider, SocketContext };
关键说明
- 替换simple-peer:react-native-webrtc提供的RTCPeerConnection是原生实现,完美适配RN安卓/电视环境,无需依赖浏览器Crypto API。
- 媒体流获取:RN端必须使用
mediaDevices.getUserMedia(来自react-native-webrtc),而非浏览器的navigator.mediaDevices。 - RTCView使用:必须传递
MediaStream.toURL()的结果作为streamURL,而非ref对象的JSON化。 - STUN服务器:添加公共STUN服务器解决NAT穿透问题,确保不同网络下的P2P连接能正常建立。
内容的提问来源于stack exchange,提问作者Sebastian Meier
相关产品推荐
相关产品推荐

