在本教程中,我们将学习WEBRTC的基础知识,以构建可以在iOS&Android上实施的本机视频呼叫应用程序。
视频会议是当今环境的重要组成部分。但是,由于其复杂性,大多数开发人员(我也是我也很难实施)。
WebRTC React本机是创建视频会议应用程序的绝佳框架。我们将深入研究这些框架并开发一个应用程序。
如果您不耐烦地看到结果,这是您项目的整体 react-native-webrtc-app repo。
什么是反应天然?
React Native是一个JavaScript框架,用于为iOS和Android创建本质上渲染的移动应用程序。它建立在Facebook的JavaScript工具包上,用于创建用户界面,但它不是针对浏览器,而是针对移动平台。换句话说,Web开发人员现在可以创建看起来和感觉完全“本地”的移动应用程序,同时使用他们已经熟悉的JavaScript框架。此外,由于您创建的许多代码都可以在平台之间共享,因此React Native使同时为Android和iOS构建变得易于构建。
什么是webrtc?
WEBRTC(Web实时通信)是一种开源P2P协议,允许Web浏览器和设备通过语音,文本和视频实时通信。 WebRTC在JavaScript中提供了应用程序编程接口(API)为软件开发人员。
p2p只是暗示两个对等方(例如,您的设备和我的设备和我)彼此直接通信,而无需中间的服务器。
WEBRTC采用许多技术来启用跨浏览器的实时点对点通信。
- SDP(会话描述协议)
- ICE(交互连接建立)
- 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候选人。
- 上图中有两个服务器。其中一个是昏迷服务器,另一个是转弯服务器。
Stun(NAT的会话遍历公用事业)
- 昏迷服务器用于允许客户端发现其所有地址。
- 眩晕服务器揭示了他们的同行公共和本地IP地址。顺便说一句,Google提供了免费的Stun服务器(stun.l.google.com:19302)。
转(使用NAT周围的继电器遍历)
- 当无法形成对等连接时,使用转向服务器。转动服务器只需在同行之间中继数据。
RTP(实时协议)
- RTP是传输实时数据的完善标准。它是在UDP上构建的。在WEBRTC中,使用RTP传输音频和视频。
WEBRTC信号传导
- WEBRTC可以在没有服务器的情况下运行,但是它需要服务器来创建连接。该服务器是交换建立对等连接所需信息的渠道。
- 必须传输的数据是
Offer
,Answer
和information 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;
}
};
如前所述,我们要求服务器传递三个信息:Offer
,Answer
和ICECandidate
。 call
事件将呼叫者的报价发送给Callee,而answerCall
事件将Callee的答案发送给呼叫者。 ICEcandidate
事件,交换数据。
这是我们需要的信号服务器的最基本形式。
-
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
现在,我们将开发JoinScreen
,IncomingCallScreen
和OutgoingCallScreen
。
我们将在整个开发过程中使用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;
我们的屏幕看起来像这样。
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。
我们的屏幕看起来像这样。
爆发屏幕
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->
步骤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>
);
};
}
您可以在这里获得CameraSwitch
,VideoOn
和MicOn
的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;
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开始。