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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 00:18:24