如何免费构建WEBRTC React Antive App免费
#react #reactnative #node #webrtc

在本教程中,我们将学习WEBRTC的基础知识,以构建可以在iOS&Android上实施的本机视频呼叫应用程序。

视频会议是当今环境的重要组成部分。但是,由于其复杂性,大多数开发人员(我也是我也很难实施)。

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

如果您不耐烦地看到结果,这是您项目的整体 react-native-webrtc-app repo。

WebRTC React Native

什么是反应天然?

React Native是一个JavaScript框架,用于为iOS和Android创建本质上渲染的移动应用程序。它建立在Facebook的JavaScript工具包上,用于创建用户界面,但它不是针对浏览器,而是针对移动平台。换句话说,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字符串如何相互通信有疑问。这是我们将使用信号服务器的地方。

冰(互动连接建立)

冰是连接对等的魔法,即使他们被纳特分开。

  • 客户端A使用Stun Server确定其本地和公共Internet地址,然后通过信号服务器将其继电给客户端B。从昏迷服务器收到的每个地址都称为ICE候选人。

How to Build a WebRTC React Native App Free

  • 上图中有两个服务器。其中一个是昏迷服务器,另一个是转弯服务器。

Stun(NAT的会话遍历公用事业)

  • 昏迷服务器用于允许客户端发现其所有地址。
  • 眩晕服务器揭示了他们的同行公共和本地IP地址。顺便说一句,Google提供了免费的Stun服务器(stun.l.google.com:19302)。

转(使用NAT周围的继电器遍历)

  • 当无法形成对等连接时,使用转向服务器。转动服务器只需在同行之间中继数据。

RTP(实时协议)

  • RTP是传输实时数据的完善标准。它是在UDP上构建的。在WEBRTC中,使用RTP传输音频和视频。

WEBRTC信号传导

  • WEBRTC可以在没有服务器的情况下运行,但是它需要服务器来创建连接。该服务器是交换建立对等连接所需信息的渠道。
  • 必须传输的数据是OfferAnswerinformation about the Network Connection

让我们以示例理解它。

  • 客户A将成为连接的发起者,将创建要约。然后,此优惠将通过信号服务器发送到客户端B。客户B将获得要约并做出相应的回应。该信息随后将由客户端b。
  • 通过信号通道通过信号通道。
  • 一旦报价和响应完成,就建立了与同行之间的联系。 ICE候选人提供了WEBRTC与该对等交换RTCICECANDIDADE的远程设备进行通信所需的协议和路由。每个同行都建议他们的顶级候选人,从最好到最坏。然后在他们同意只使用一次后形成链接。

构建节点JS WEBRTC信号服务器

  • 现在我们已经涵盖了WebRTC的基本原理,让我们使用它来构建使用socketio作为信号频道的视频调用应用程序。
  • 如前所述,我们将使用webrtc节点JS socketio在客户之间传达信息。

现在我们将制作一个WEBRTC节点JS Express项目,我们的目录结构看起来有点像这样。

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

步骤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();

步骤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;
  }
};

如前所述,我们要求服务器传递三个信息:OfferAnswerICECandidatecall事件将呼叫者的报价发送给Callee,而answerCall事件将Callee的答案发送给呼叫者。 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
  }
}

我们几乎完成了服务器端编程。让我们创建一个用React Native和WebRTC的客户端应用程序。


在开始开发之前,首先让我们掌握应用程序的流程。每当用户打开应用程序(5位随机编号)时,我们都会提供CallerID。

例如,约翰和米歇尔(John)和米歇尔(Michel)有callerid,12345 for john and 67890 for michel,所以约翰(John)用他的Callerid启动了米歇尔(Michel)的电话。现在,约翰将收到传出的呼叫屏幕,而米歇尔将看到带有接受按钮的来电屏幕。接受电话后,约翰和米歇尔将参加会议。

Develope React Native WebRTC应用程序

步骤1:使用React-Native-CLI设置React本机项目

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

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

这是我的软件包。您还需要安装。

"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"
  }

步骤3:react-native-webrtc Pacakge的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" />

3.2启用Java 8支持

android/app/build.gradle中添加以下android部分。

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

3.3 R8/Proguard支持

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

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

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__)

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

android.enableDexingArtifactTransform.desugaring=false

步骤4:react-native-webrtc Pacakge的iOS设置

4.1调整支持的平台版本

重要:确保您使用的是1.10或更高的可可录。


您可能必须更改podfile中的platform字段。

react-native-webrtc不支持iOS <12将其设置为'12 .0'或更高版本,或者在运行pod install时会遇到错误。

platform :ios, '12.0'

4.2声明权限

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

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

步骤5:开发UI屏幕

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

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

现在,我们将开发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;
  }
}

在上面的文件中,我们只是存储一个5位随机CallerID,该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;

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


How to Build a WebRTC React Native App Free

incomingcallscreen

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>
    );
  };
    //
  }

您可以在此处获得CallAnswer图标的SVG Assets

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


How to Build a WebRTC React Native App Free

爆发屏幕
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>
    );
  };
    //
  }

您可以在此处获得CallEnd图标的SVG Assets

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

<! - kg-card-begin:markdown->


How to Build a WebRTC React Native App Free

步骤6:设置Websocket和WebRTC

创建UI后,让我们在同一文件(app.js)
中设置套接字和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');
    };
  }, []);

}

步骤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>
      /*
      ...
      ...
      ...
      */
    );
  };

步骤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>
    );
  };
}

您可以在这里获得CameraSwitchVideoOnMicOn的SVG。

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;


How to Build a WebRTC React Native App Free

wohoo!最后我们做到了。

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

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

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

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

您可以获得完整的源代码here.我们在此博客的帮助下创建了一个带有信号服务器的WebRTC应用程序。我们可以在一个房间/会议上与2-3个人使用点对点交流。

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

Video SDK是实时视频和音频SDK的开发人员最友好的平台。 Video SDK将实时视频和音频集成到您的React本地项目中,速度非常容易,更快。您只需几行代码即

此外,Video SDK还提供一流的修改,提供对布局和权利的全部控制。插件可用于改善体验,并且可以直接从您的视频SDK仪表板或REST APIs访问端到端的呼叫日志和质量数据。这一数量的数据使开发人员能够调试对话期间出现的任何问题,并改善其集成,以获得最佳的客户体验。

另外,您可以遵循此快速启动指南至Create a Demo React Native Project with the Video SDK.或从Code Sample开始。

资源