如何免费构建 WebRTC React Native 应用

2025-06-10

如何免费构建 WebRTC React Native 应用

在本教程中,我们将学习 WebRTC 的基础知识,以构建可在 iOS 和 Android 上实现的 React Native 视频通话应用程序。

视频会议是当今环境的重要组成部分。然而,由于其复杂性,大多数开发人员(包括我😅)都难以实现它。

WebRTC React Native 是创建视频会议应用程序的绝佳框架。我们将深入研究这些框架并开发一个应用程序。

如果您迫不及待地想看到结果,这里是适合您项目的整个react-native-webrtc-app repo。

WebRTC React Native

什么是 React Native?

React Native 是一个 JavaScript 框架,用于创建原生渲染的 iOS 和 Android 移动应用。它基于 Facebook 的 JavaScript 用户界面工具包 React 构建,但目标并非浏览器,而是移动平台。换句话说,Web 开发者现在可以使用他们熟悉的 JavaScript 框架,创建外观和体验完全“原生”的移动应用。此外,由于您创建的大部分代码可以在不同平台之间共享,因此React Native让您可以轻松地同时为 Android 和 iOS 平台构建应用。

什么是 WebRTC?

WebRTC(Web 实时通信)是一种开源 P2P 协议,允许 Web 浏览器和设备通过语音、文本和视频进行实时通信。WebRTC软件开发人员提供用 JavaScript 定义的应用程序编程接口 (API)。

P2P 只是意味着两个对等体(例如,您的设备和我的设备)直接相互通信,而不需要中间的服务器。

WebRTC 采用多种技术来实现跨浏览器的实时点对点通信。

  1. SDP(会话描述协议)
  2. ICE(交互连接建立)
  3. RTP(实时协议)

运行 WebRTC 所需的另一个组件是信令服务器。然而,信令服务器的实现尚无统一标准,不同开发者的实现方式也存在差异。本节稍后将详细介绍信令服务器。

让我们快速回顾一下上面提到的一些技术。

SDP(会话描述协议)

  • SDP 是一种简单的协议,用于确定浏览器支持哪些编解码器。假设有两个对等体(客户端 A 和客户端 B)将通过 WebRTC 进行连接。
  • 客户端 A 和 B 生成 SDP 字符串,指定它们支持的编解码器。例如,客户端 A 可能支持 H264、VP8 和 VP9 视频编解码器,以及 Opus 和 PCM 音频编解码器。客户端 B 可能仅支持 H264 视频和 Opus 音频编解码器。
  • 在此场景中,客户端 A 和客户端 B 之间将使用的编解码器是 H264 和 Opus。如果没有共享编解码器,则无法建立点对点通信。

您可能对这些 SDP 字符串如何相互通信有疑问。这时我们将使用信令服务器 (Signaling Server)。

ICE(交互连接建立)

ICE 是一种魔法,即使双方被 NAT 隔开,它也能将双方连接起来。

  • 客户端 A 使用 STUN 服务器确定其本地和公共互联网地址,然后通过信令服务器将这些地址转发给客户端 B。从 STUN 服务器收到的每个地址都称为 ICE 候选地址。

如何免费构建 WebRTC React Native 应用

  • 上图中有两台服务器,一台是STUN服务器,另一台是TURN服务器。

STUN(NAT 会话遍历实用程序)

  • STUN 服务器用于允许客户端 A 发现其所有地址。
  • STUN 服务器会泄露对方的公共 IP 地址和本地 IP 地址。顺便说一句,谷歌提供了一个免费的 STUN 服务器 (stun.l.google.com:19302)。

TURN(使用中继绕过 NAT)

  • 当无法建立点对点连接时,可使用 TURN 服务器。TURN 服务器只是在对等点之间中继数据。

RTP(实时协议)

  • RTP 是传输实时数据的成熟标准,它基于 UDP 协议。在 WebRTC 中,音频和视频都通过 RTP 进行传输。

WebRTC 信令

  • WebRTC 无需服务器即可运行,但需要服务器来创建连接。服务器充当建立点对点连接所需信息的交换通道。
  • 必须传输的数据是Offer、、Answerinformation about the Network Connection

让我们通过例子来理解它。

  • 客户端 A 将作为连接的发起者,创建一个“要约”。该要约随后将通过信令服务器发送给客户端 B。客户端 B 将收到该要约并做出相应响应。随后,客户端 B 将通过信号通道将该信息转发给客户端 A。
  • 一旦完成请求和响应,对等体之间的连接就建立了。ICE 候选节点提供 WebRTC 与远程设备通信所需的协议和路由,以便该对等体交换 RTCIceCandidate。每个对等体都会推荐其最佳候选节点,从最佳到最差。在双方同意仅使用一次后,连接便建立。

构建 Node js WebRTC 信令服务器

  • 现在我们已经介绍了 WebRTC 的基础知识,让我们使用它来构建一个使用 SocketIO 作为信令通道的视频通话应用程序。
  • 如前所述,我们将使用 WebRTC Node js SocketIO 在客户端之间传递信息。

现在我们将创建一个 WebRTC Node js Express 项目,我们的目录结构看起来有点像这样。



server
    └── index.js
    └── socket.js
    └── package.json


Enter fullscreen mode Exit fullscreen mode

步骤 1:index.js文件将如下所示。



const path = require('path');
const { createServer } = require('http');

const express = require('express');
const { getIO, initIO } = require('./socket');

const app = express();

app.use('/', express.static(path.join(__dirname, 'static')));

const httpServer = createServer(app);

let port = process.env.PORT || 3500;

initIO(httpServer);

httpServer.listen(port)
console.log("Server started on ", port);

getIO();


Enter fullscreen mode Exit fullscreen mode

第 2 步:socket.js文件将如下所示。



const { Server } = require("socket.io");
let IO;

module.exports.initIO = (httpServer) => {
  IO = new Server(httpServer);

  IO.use((socket, next) => {
    if (socket.handshake.query) {
      let callerId = socket.handshake.query.callerId;
      socket.user = callerId;
      next();
    }
  });

  IO.on("connection", (socket) => {
    console.log(socket.user, "Connected");
    socket.join(socket.user);

    socket.on("call", (data) => {
      let calleeId = data.calleeId;
      let rtcMessage = data.rtcMessage;

      socket.to(calleeId).emit("newCall", {
        callerId: socket.user,
        rtcMessage: rtcMessage,
      });
    });

    socket.on("answerCall", (data) => {
      let callerId = data.callerId;
      rtcMessage = data.rtcMessage;

      socket.to(callerId).emit("callAnswered", {
        callee: socket.user,
        rtcMessage: rtcMessage,
      });
    });

    socket.on("ICEcandidate", (data) => {
      console.log("ICEcandidate data.calleeId", data.calleeId);
      let calleeId = data.calleeId;
      let rtcMessage = data.rtcMessage;

      socket.to(calleeId).emit("ICEcandidate", {
        sender: socket.user,
        rtcMessage: rtcMessage,
      });
    });
  });
};

module.exports.getIO = () => {
  if (!IO) {
    throw Error("IO not initilized.");
  } else {
    return IO;
  }
};



Enter fullscreen mode Exit fullscreen mode

如前所述,我们需要服务器传递三条信息:OfferAnswerICECandidatecall事件将调用者的请求发送给被调用者,而answerCall事件将被调用者的应答发送给调用者。ICEcandidate事件,交换数据。

这是我们所需的信令服务器的最基本形式。

  1. package.json文件将如下所示。

步骤 3:package.json 文件将如下所示。



{
  "name": "WebRTC",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "socket.io": "^4.0.2" // Socket dependency
  }
}


Enter fullscreen mode Exit fullscreen mode

服务器端编程基本完成了。接下来,让我们用 React Native 和 WebRTC 创建一个客户端应用。


在开始开发之前,我们先来了解一下应用程序的流程。每当用户打开应用程序时,我们都会提供 CallerId(5 位随机数)。

例如,John 和 Michel 各有一个 CallerId,12345John 和67890Michel 各有一个,因此 John 可以使用自己的 CallerId 拨打 Michel 的电话。此时,John 会看到拨出电话屏幕,而 Michel 会看到带有“接听”按钮的来电屏幕。接听电话后,John 和 Michel 即可加入会议。

开发 React Native WebRTC 应用

步骤 1:使用 react-native-cli 设置 React Native 项目

您可以遵循此官方指南 - https://reactnative.dev/docs/environment-setup

步骤 2:成功运行您的演示应用程序后,我们将安装一些 React Native 库

这是我的 package.json 依赖项,您也需要安装。



"dependencies": {
    "react": "17.0.2",
    "react-native": "0.68.2",
    "react-native-svg": "^13.7.0",
    "react-native-webrtc": "^1.94.2",
    "socket.io-client": "^4.5.4"
  }


Enter fullscreen mode Exit fullscreen mode

react-native-webrtc步骤 3:Android软件包设置

从 React Native 0.60 开始,由于新的自动链接功能,您不再需要遵循手动链接步骤,但如果您计划将应用程序发布到生产环境,则需要遵循以下其他步骤。

3.1 声明权限

android/app/main/AndroidManifest.xml该部分之前添加以下权限<application>



<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.audio.output" />
<uses-feature android:name="android.hardware.microphone" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />



Enter fullscreen mode Exit fullscreen mode

3.2 启用 Java 8 支持

android/app/build.gradle该部分中添加以下内容android



compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}


Enter fullscreen mode Exit fullscreen mode

3.3 R8/ProGuard 支持

android/app/proguard-rules.pro新行中添加以下内容。



-keep class org.webrtc.** { *; }



Enter fullscreen mode Exit fullscreen mode

3.4 致命异常:java.lang.UnsatisfiedLinkError



Fatal Exception: java.lang.UnsatisfiedLinkError: No implementation found for void org.webrtc.PeerConnectionFactory.nativeInitializeAndroidGlobals() (tried Java_org_webrtc_PeerConnectionFactory_nativeInitializeAndroidGlobals and Java_org_webrtc_PeerConnectionFactory_nativeInitializeAndroidGlobals__)



Enter fullscreen mode Exit fullscreen mode

如果您遇到上述错误,请android/gradle.properties添加以下内容。



android.enableDexingArtifactTransform.desugaring=false



Enter fullscreen mode Exit fullscreen mode

react-native-webrtc步骤 4:iOS软件包设置

4.1 调整支持的平台版本

重要提示:请确保您使用的是 CocoaPods 1.10 或更高版本。

您可能需要更改platformpodfile 中的字段。

react-native-webrtc不支持 iOS 12 以下版本。请将其设置为“12.0”或更高版本,否则运行时会出错pod install



platform :ios, '12.0'



Enter fullscreen mode Exit fullscreen mode

4.2 声明权限

导航到<ProjectFolder>/ios/<ProjectName>/并编辑Info.plist,添加以下行。



<key>NSCameraUsageDescription</key>
<string>Camera permission description</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone permission description</string>


Enter fullscreen mode Exit fullscreen mode

步骤5:开发UI屏幕

我们的客户端项目的目录结构看起来有点像这样。



client
    └── android
    └── asset
    └── ios
    └── index.js
    └── App.js
    └── components
    └── package.json


Enter fullscreen mode Exit fullscreen mode

现在,我们将开发JoinScreenIncomingCallScreenOutgoingCallScreen

我们将App.js在整个开发过程中使用文件。

加入屏幕



import React, {useEffect, useState, useRef} from 'react';
import {
  Platform,
  KeyboardAvoidingView,
  TouchableWithoutFeedback,
  Keyboard,
  View,
  Text,
  TouchableOpacity,
} from 'react-native';
import TextInputContainer from './src/components/TextInputContainer';

export default function App({}) {

  const [type, setType] = useState('JOIN');

  const [callerId] = useState(
    Math.floor(100000 + Math.random() * 900000).toString(),
  );

  const otherUserId = useRef(null);


  const JoinScreen = () => {
    return (
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={{
          flex: 1,
          backgroundColor: '#050A0E',
          justifyContent: 'center',
          paddingHorizontal: 42,
        }}>
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <>
            <View
              style={{
                padding: 35,
                backgroundColor: '#1A1C22',
                justifyContent: 'center',
                alignItems: 'center',
                borderRadius: 14,
              }}>
              <Text
                style={{
                  fontSize: 18,
                  color: '#D0D4DD',
                }}>
                Your Caller ID
              </Text>
              <View
                style={{
                  flexDirection: 'row',
                  marginTop: 12,
                  alignItems: 'center',
                }}>
                <Text
                  style={{
                    fontSize: 32,
                    color: '#ffff',
                    letterSpacing: 6,
                  }}>
                  {callerId}
                </Text>
              </View>
            </View>

            <View
              style={{
                backgroundColor: '#1A1C22',
                padding: 40,
                marginTop: 25,
                justifyContent: 'center',
                borderRadius: 14,
              }}>
              <Text
                style={{
                  fontSize: 18,
                  color: '#D0D4DD',
                }}>
                Enter call id of another user
              </Text>
              <TextInputContainer
                placeholder={'Enter Caller ID'}
                value={otherUserId.current}
                setValue={text => {
                  otherUserId.current = text;
                }}
                keyboardType={'number-pad'}
              />
              <TouchableOpacity
                onPress={() => {
                  setType('OUTGOING_CALL');
                }}
                style={{
                  height: 50,
                  backgroundColor: '#5568FE',
                  justifyContent: 'center',
                  alignItems: 'center',
                  borderRadius: 12,
                  marginTop: 16,
                }}>
                <Text
                  style={{
                    fontSize: 16,
                    color: '#FFFFFF',
                  }}>
                  Call Now
                </Text>
              </TouchableOpacity>
            </View>
          </>
        </TouchableWithoutFeedback>
      </KeyboardAvoidingView>
    );
  };

  const OutgoingCallScreen = () => {
    return null
  };

  const IncomingCallScreen = () => {
    return null
  };

  switch (type) {
    case 'JOIN':
      return JoinScreen();
    case 'INCOMING_CALL':
      return IncomingCallScreen();
    case 'OUTGOING_CALL':
      return OutgoingCallScreen();
    default:
      return null;
  }
}



Enter fullscreen mode Exit fullscreen mode

在上面的文件中,我们仅存储一个 5 位数的随机 CallerId,它将代表该用户,并可由另一个连接的用户引用。

TextInputContainer.js组件代码文件。



import React from 'react';
import {View, TextInput} from 'react-native';
const TextInputContainer = ({placeholder, value, setValue, keyboardType}) => {
  return (
    <View
      style={{
        height: 50,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#202427',
        borderRadius: 12,
        marginVertical: 12,
      }}>
      <TextInput
        style={{
          margin: 8,
          padding: 8,
          width: '90%',
          textAlign: 'center',
          fontSize: 16,
          color: '#FFFFFF',
        }}
        multiline={true}
        numberOfLines={1}
        cursorColor={'#5568FE'}
        placeholder={placeholder}
        placeholderTextColor={'#9A9FA5'}
        onChangeText={text => {
          setValue(text);
        }}
        value={value}
        keyboardType={keyboardType}
      />
    </View>
  );
};

export default TextInputContainer;



Enter fullscreen mode Exit fullscreen mode

我们的屏幕将会看起来像这样。

如何免费构建 WebRTC React Native 应用

来电屏幕



import CallAnswer from './asset/CallAnswer';

export default function App({}) {
    //
  const IncomingCallScreen = () => {
    return (
      <View
        style={{
          flex: 1,
          justifyContent: 'space-around',
          backgroundColor: '#050A0E',
        }}>
        <View
          style={{
            padding: 35,
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: 14,
          }}>
          <Text
            style={{
              fontSize: 36,
              marginTop: 12,
              color: '#ffff',
            }}>
            {otherUserId.current} is calling..
          </Text>
        </View>
        <View
          style={{
            justifyContent: 'center',
            alignItems: 'center',
          }}>
          <TouchableOpacity
            onPress={() => {
              setType('WEBRTC_ROOM');
            }}
            style={{
              backgroundColor: 'green',
              borderRadius: 30,
              height: 60,
              aspectRatio: 1,
              justifyContent: 'center',
              alignItems: 'center',
            }}>
            <CallAnswer height={28} fill={'#fff'} />
          </TouchableOpacity>
        </View>
      </View>
    );
  };
    //
  }


Enter fullscreen mode Exit fullscreen mode

CallAnswer您可以在此处资产中获取图标的 SVG

我们的屏幕将会看起来像这样。

如何免费构建 WebRTC React Native 应用

拨出电话屏幕



import CallEnd from './asset/CallEnd';

export default function App({}) {
    //
const OutgoingCallScreen = () => {
    return (
      <View
        style={{
          flex: 1,
          justifyContent: 'space-around',
          backgroundColor: '#050A0E',
        }}>
        <View
          style={{
            padding: 35,
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: 14,
          }}>
          <Text
            style={{
              fontSize: 16,
              color: '#D0D4DD',
            }}>
            Calling to...
          </Text>

          <Text
            style={{
              fontSize: 36,
              marginTop: 12,
              color: '#ffff',
              letterSpacing: 6,
            }}>
            {otherUserId.current}
          </Text>
        </View>
        <View
          style={{
            justifyContent: 'center',
            alignItems: 'center',
          }}>
          <TouchableOpacity
            onPress={() => {
              setType('JOIN');
              otherUserId.current = null;
            }}
            style={{
              backgroundColor: '#FF5D5D',
              borderRadius: 30,
              height: 60,
              aspectRatio: 1,
              justifyContent: 'center',
              alignItems: 'center',
            }}>
            <CallEnd width={50} height={12} />
          </TouchableOpacity>
        </View>
      </View>
    );
  };
    //
  }


Enter fullscreen mode Exit fullscreen mode

CallEnd您可以在此处资产中获取图标的 SVG

我们的屏幕将会看起来像这样。

如何免费构建 WebRTC React Native 应用

步骤 6:设置 WebSocket 和 WebRTC

创建 UI 后,让我们在同一个文件 (App.js) 中设置 Socket 和 WebRTC 对等连接



import SocketIOClient from 'socket.io-client'; // import socket io
// import WebRTC 
import {
  mediaDevices,
  RTCPeerConnection,
  RTCView,
  RTCIceCandidate,
  RTCSessionDescription,
} from 'react-native-webrtc';

export default function App({}) {

// Stream of local user
const [localStream, setlocalStream] = useState(null);

/* When a call is connected, the video stream from the receiver is appended to this state in the stream*/
const [remoteStream, setRemoteStream] = useState(null);

// This establishes your WebSocket connection
const socket = SocketIOClient('http://192.168.1.10:3500', {
    transports: ['websocket'],
    query: {
        callerId, 
    /* We have generated this `callerId` in `JoinScreen` implementation */
    },
  });

 /* This creates an WebRTC Peer Connection, which will be used to set local/remote descriptions and offers. */
 const peerConnection = useRef(
    new RTCPeerConnection({
      iceServers: [
        {
          urls: 'stun:stun.l.google.com:19302',
        },
        {
          urls: 'stun:stun1.l.google.com:19302',
        },
        {
          urls: 'stun:stun2.l.google.com:19302',
        },
      ],
    }),
  );

    useEffect(() => {
    socket.on('newCall', data => {
     /* This event occurs whenever any peer wishes to establish a call with you. */
    });

    socket.on('callAnswered', data => {
      /* This event occurs whenever remote peer accept the call. */
    });

    socket.on('ICEcandidate', data => {
      /* This event is for exchangin Candidates. */

    });

    let isFront = false;

/*The MediaDevices interface allows you to access connected media inputs such as cameras and microphones. We ask the user for permission to access those media inputs by invoking the mediaDevices.getUserMedia() method. */
    mediaDevices.enumerateDevices().then(sourceInfos => {
      let videoSourceId;
      for (let i = 0; i < sourceInfos.length; i++) {
        const sourceInfo = sourceInfos[i];
        if (
          sourceInfo.kind == 'videoinput' &&
          sourceInfo.facing == (isFront ? 'user' : 'environment')
        ) {
          videoSourceId = sourceInfo.deviceId;
        }
      }


      mediaDevices
        .getUserMedia({
          audio: true,
          video: {
            mandatory: {
              minWidth: 500, // Provide your own width, height and frame rate here
              minHeight: 300,
              minFrameRate: 30,
            },
            facingMode: isFront ? 'user' : 'environment',
            optional: videoSourceId ? [{sourceId: videoSourceId}] : [],
          },
        })
        .then(stream => {
          // Get local stream!
          setlocalStream(stream);

          // setup stream listening
          peerConnection.current.addStream(stream);
        })
        .catch(error => {
          // Log error
        });
    });

    peerConnection.current.onaddstream = event => {
      setRemoteStream(event.stream);
    };

    // Setup ice handling
    peerConnection.current.onicecandidate = event => {

    };

    return () => {
      socket.off('newCall');
      socket.off('callAnswered');
      socket.off('ICEcandidate');
    };
  }, []);

}


Enter fullscreen mode Exit fullscreen mode

步骤 7:建立 WebRTC 通话

此阶段将解释如何在对等体之间建立 WebRTC 呼叫。



let remoteRTCMessage = useRef(null);

useEffect(() => {
  socket.on("newCall", (data) => {
    remoteRTCMessage.current = data.rtcMessage;
    otherUserId.current = data.callerId;
    setType("INCOMING_CALL");
  });

  socket.on("callAnswered", (data) => {
    // 7. When Alice gets Bob's session description, she sets that as the remote description with `setRemoteDescription` method.

    remoteRTCMessage.current = data.rtcMessage;
    peerConnection.current.setRemoteDescription(
      new RTCSessionDescription(remoteRTCMessage.current)
    );
    setType("WEBRTC_ROOM");
  });

  socket.on("ICEcandidate", (data) => {
    let message = data.rtcMessage;

    // When Bob gets a candidate message from Alice, he calls `addIceCandidate` to add the candidate to the remote peer description.

    if (peerConnection.current) {
      peerConnection?.current
        .addIceCandidate(new RTCIceCandidate(message.candidate))
        .then((data) => {
          console.log("SUCCESS");
        })
        .catch((err) => {
          console.log("Error", err);
        });
    }
  });

  // Alice creates an RTCPeerConnection object with an `onicecandidate` handler, which runs when network candidates become available.
  peerConnection.current.onicecandidate = (event) => {
    if (event.candidate) {
      // Alice sends serialized candidate data to Bob using Socket
      sendICEcandidate({
        calleeId: otherUserId.current,
        rtcMessage: {
          label: event.candidate.sdpMLineIndex,
          id: event.candidate.sdpMid,
          candidate: event.candidate.candidate,
        },
      });
    } else {
      console.log("End of candidates.");
    }
  };
}, []);

async function processCall() {
  // 1. Alice runs the `createOffer` method for getting SDP.
  const sessionDescription = await peerConnection.current.createOffer();

  // 2. Alice sets the local description using `setLocalDescription`.
  await peerConnection.current.setLocalDescription(sessionDescription);

  // 3. Send this session description to Bob uisng socket
  sendCall({
    calleeId: otherUserId.current,
    rtcMessage: sessionDescription,
  });
}

async function processAccept() {
  // 4. Bob sets the description, Alice sent him as the remote description using `setRemoteDescription()`
  peerConnection.current.setRemoteDescription(
    new RTCSessionDescription(remoteRTCMessage.current)
  );

  // 5. Bob runs the `createAnswer` method
  const sessionDescription = await peerConnection.current.createAnswer();

  // 6. Bob sets that as the local description and sends it to Alice
  await peerConnection.current.setLocalDescription(sessionDescription);
  answerCall({
    callerId: otherUserId.current,
    rtcMessage: sessionDescription,
  });
}

function answerCall(data) {
  socket.emit("answerCall", data);
}

function sendCall(data) {
  socket.emit("call", data);
}

const JoinScreen = () => {
  return (
    /*
      ...
      ...
      ...
      */
    <TouchableOpacity
      onPress={() => {
        processCall();
        setType("OUTGOING_CALL");
      }}
      style={{
        height: 50,
        backgroundColor: "#5568FE",
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 12,
        marginTop: 16,
      }}
    >
      <Text
        style={{
          fontSize: 16,
          color: "#FFFFFF",
        }}
      >
        Call Now
      </Text>
    </TouchableOpacity>
    /*
      ...
      ...
      ...
      */
  );
};

const IncomingCallScreen = () => {
    return (
      /*
      ...
      ...
      ...
      */
      <TouchableOpacity
        onPress={() => {
          processAccept();
          setType('WEBRTC_ROOM');
        }}
        style={{
          backgroundColor: 'green',
          borderRadius: 30,
          height: 60,
          aspectRatio: 1,
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <CallAnswer height={28} fill={'#fff'} />
      </TouchableOpacity>
      /*
      ...
      ...
      ...
      */
    );
  };



Enter fullscreen mode Exit fullscreen mode

步骤 8:渲染本地和远程 MediaStream



import MicOn from "./asset/MicOn";
import MicOff from "./asset/MicOff";
import VideoOn from "./asset/VideoOn";
import VideoOff from "./asset/VideoOff";
import CameraSwitch from "./asset/CameraSwitch";
import IconContainer from "./src/components/IconContainer";

export default function App({}) {
  // Handling Mic status
  const [localMicOn, setlocalMicOn] = useState(true);

  // Handling Camera status
  const [localWebcamOn, setlocalWebcamOn] = useState(true);

  // Switch Camera
  function switchCamera() {
    localStream.getVideoTracks().forEach((track) => {
      track._switchCamera();
    });
  }

  // Enable/Disable Camera
  function toggleCamera() {
    localWebcamOn ? setlocalWebcamOn(false) : setlocalWebcamOn(true);
    localStream.getVideoTracks().forEach((track) => {
      localWebcamOn ? (track.enabled = false) : (track.enabled = true);
    });
  }

  // Enable/Disable Mic
  function toggleMic() {
    localMicOn ? setlocalMicOn(false) : setlocalMicOn(true);
    localStream.getAudioTracks().forEach((track) => {
      localMicOn ? (track.enabled = false) : (track.enabled = true);
    });
  }

  // Destroy WebRTC Connection
  function leave() {
    peerConnection.current.close();
    setlocalStream(null);
    setType("JOIN");
  }

  const WebrtcRoomScreen = () => {
    return (
      <View
        style={{
          flex: 1,
          backgroundColor: "#050A0E",
          paddingHorizontal: 12,
          paddingVertical: 12,
        }}
      >
        {localStream ? (
          <RTCView
            objectFit={"cover"}
            style={{ flex: 1, backgroundColor: "#050A0E" }}
            streamURL={localStream.toURL()}
          />
        ) : null}
        {remoteStream ? (
          <RTCView
            objectFit={"cover"}
            style={{
              flex: 1,
              backgroundColor: "#050A0E",
              marginTop: 8,
            }}
            streamURL={remoteStream.toURL()}
          />
        ) : null}
        <View
          style={{
            marginVertical: 12,
            flexDirection: "row",
            justifyContent: "space-evenly",
          }}
        >
          <IconContainer
            backgroundColor={"red"}
            onPress={() => {
              leave();
              setlocalStream(null);
            }}
            Icon={() => {
              return <CallEnd height={26} width={26} fill="#FFF" />;
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={!localMicOn ? "#fff" : "transparent"}
            onPress={() => {
              toggleMic();
            }}
            Icon={() => {
              return localMicOn ? (
                <MicOn height={24} width={24} fill="#FFF" />
              ) : (
                <MicOff height={28} width={28} fill="#1D2939" />
              );
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={!localWebcamOn ? "#fff" : "transparent"}
            onPress={() => {
              toggleCamera();
            }}
            Icon={() => {
              return localWebcamOn ? (
                <VideoOn height={24} width={24} fill="#FFF" />
              ) : (
                <VideoOff height={36} width={36} fill="#1D2939" />
              );
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={"transparent"}
            onPress={() => {
              switchCamera();
            }}
            Icon={() => {
              return <CameraSwitch height={24} width={24} fill="#FFF" />;
            }}
          />
        </View>
      </View>
    );
  };
}



Enter fullscreen mode Exit fullscreen mode

您可以在此处获取SVGCameraSwitch和图标VideoOn资产MicOn

IconContainer.js组件代码文件。



import React from 'react';
import {TouchableOpacity} from 'react-native';

const buttonStyle = {
  height: 50,
  aspectRatio: 1,
  justifyContent: 'center',
  alignItems: 'center',
};
const IconContainer = ({backgroundColor, onPress, Icon, style}) => {
  return (
    <TouchableOpacity
      onPress={onPress}
      style={{
        ...style,
        backgroundColor: backgroundColor ? backgroundColor : 'transparent',
        borderRadius: 30,
        height: 60,
        aspectRatio: 1,
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Icon />
    </TouchableOpacity>
  );
};
export default IconContainer;



Enter fullscreen mode Exit fullscreen mode
如何免费构建 WebRTC React Native 应用

哇噢!!我们终于成功了。

步骤 9:处理 WebRTC 中的音频路由

我们将使用第三方库 React Native Incall-Manager(https://github.com/react-native-webrtc/react-native-incall-manager)来处理视频会议期间所有与音频相关的边缘情况。



 useEffect(() => {
    InCallManager.start();
    InCallManager.setKeepScreenOn(true);
    InCallManager.setForceSpeakerphoneOn(true);

    return () => {
      InCallManager.stop();
    };
  }, []);


Enter fullscreen mode Exit fullscreen mode

您可以在此处获取完整的源代码。我们借助本博客创建了一个带有信令服务器的 WebRTC 应用。我们可以在一个房间/会议中与 2-3 人进行点对点通信。

使用视频 SDK 将 WebRTC 与 React Native 集成

Video SDK是开发者最友好的实时视频和音频 SDK 平台。Video SDK 让您能够更轻松、更快速地将实时视频和音频集成到您的 React Native 项目中。只需几行代码,即可立即创建并运行品牌化、定制化且可编程的通话。

此外,视频 SDK 提供一流的修改功能,让您完全掌控布局和权限。您可以使用插件来提升体验,并且可以直接从视频 SDK 仪表板或通过REST API访问端到端通话日志和质量数据。如此丰富的数据使开发人员能够调试对话过程中出现的任何问题,并改进集成,从而提供最佳的客户体验。

或者,您可以按照本快速入门指南使用 Video SDK 创建演示 React Native 项目。或者从代码示例开始

资源

鏂囩珷鏉ユ簮锛�https://dev.to/video-sdk/react-native-webrtc-lm9
PREV
Awesome-AI:人工智能掌握指南
NEXT
使用 Javascript Nodejs 的 NFT 图像生成器(800,000 个加密朋克)