在 React Native 的應用中,從頭開始添加視頻通話功能是很復雜的。要保證低延遲、負載平衡,還要注意管理用戶事件狀態,非常繁瑣。除此之外,還必須保證跨平臺的兼容性。
當然有個簡單的方法可以做到這一點。在本次的教程中,我們將使用 Agora Video SDK 來構建一個 React Native 視頻通話 App。在深入探討程序工作之前,我們將介紹應用的結構、設置和執行。你可以在幾分鐘內,通過幾個簡單的步驟,讓一個跨平臺的視頻通話應用運行起來。
我們將使用 Agora RTC SDK for React Native來做例子。在這篇文章中,我使用的版本是 v3.1.6。下載最新 SDK,可訪問聲網官網:https://docs.agora.io/cn/All/downloads?platform=All%20Platforms
創建一個Agora賬戶
- 在聲網官網注冊,并登錄后臺。注冊登錄及獲取 AppID 方法可參考教程:https://www.agora.io/cn/community/blog/best/21344
- 找到 "項目管理 "下的 "項目列表 "選項卡,點擊藍色的 "創建 "按鈕,創建一個項目。(當提示使用 App ID+證書時,選擇只使用 App ID。)記住你的 App ID,它將在開發App時用于授權你的請求。
注意:本文沒有采用 Token 鑒權,建議在生產環境中運行的所有RTE App 都采用Token鑒權。有關Agora平臺中基于Token的身份驗證的更多信息,請在聲網文檔中心搜索關鍵詞「Token」,參考相關文檔。
示例項目結構
這就是我們正在構建的應用程序的結構:
.
├── android
├── components
│ └── Permission.ts
│ └── Style.ts
├── ios
├── App.tsx
.
讓我們來運行這個應用
需要安裝 LTS 版本的 Node.js 和 NPM。
確保你有一個 Agora 賬戶,設置一個項目,并生成 App ID。
從主分支下載并解壓 ZIP 文件。
運行
npm install
來安裝解壓目錄中的 App 依賴項。導航到
./App.tsx
,將我們之前生成的 App ID 填入appId: "<YourAppId>"
如果你是為 iOS 構建,打開終端,執行
cd ios && pod install
連接你的設備,并運行
npx react-native run-android
/npx react-native run-ios
來啟動應用程序。等待幾分鐘來構建和啟動應用程序。一旦你看到手機(或模擬器)上的主屏幕,點擊設備上的開始通話按鈕。(iOS模擬器不支持攝像頭,所以要用實體設備代替)。
通過以上操作,你應該可以在兩個設備之間進行視頻聊天通話。該應用默認使用 channel-x 作為頻道名稱。
應用工作原理
App.tsx
這個文件包含了 React Native 視頻通話App中視頻通話的所有核心邏輯。
import React, {Component} from 'react'
import {Platform, ScrollView, Text, TouchableOpacity, View} from 'react-native'
import RtcEngine, {RtcLocalView, RtcRemoteView, VideoRenderMode} from 'react-native-agora'
import requestCameraAndAudioPermission from './components/Permission'
import styles from './components/Style'
/**
* @property peerIds Array for storing connected peers
* @property appId
* @property channelName Channel Name for the current session
* @property joinSucceed State variable for storing success
*/
interface State {
appId: string,
token: string,
channelName: string,
joinSucceed: boolean,
peerIds: number[],
}
...
我們開始先寫import聲明。接下來,為應用狀態定義一個接口,包含:
appId:Agora App ID
token:為加入頻道而生成的Token。
channelName:頻道名稱(同一頻道的用戶可以通話)。
joinSucceed:存儲是否連接成功的布爾值。
peerIds:一個數組,用于存儲通道中其他用戶的UID。
...
export default class App extends Component<Props, State> {
_engine?: RtcEngine
constructor(props) {
super(props)
this.state = {
appId: YourAppId,
token: YourToken,
channelName: 'channel-x',
joinSucceed: false,
peerIds: [],
}
if (Platform.OS === 'android') {
// Request required permissions from Android
requestCameraAndAudioPermission().then(() => {
console.log('requested!')
})
}
}
componentDidMount() {
this.init()
}
/**
* @name init
* @description Function to initialize the Rtc Engine, attach event listeners and actions
*/
init = async () => {
const {appId} = this.state
this._engine = await RtcEngine.create(appId)
await this._engine.enableVideo()
this._engine.addListener('Warning', (warn) => {
console.log('Warning', warn)
})
this._engine.addListener('Error', (err) => {
console.log('Error', err)
})
this._engine.addListener('UserJoined', (uid, elapsed) => {
console.log('UserJoined', uid, elapsed)
// Get current peer IDs
const {peerIds} = this.state
// If new user
if (peerIds.indexOf(uid) === -1) {
this.setState({
// Add peer ID to state array
peerIds: [...peerIds, uid]
})
}
})
this._engine.addListener('UserOffline', (uid, reason) => {
console.log('UserOffline', uid, reason)
const {peerIds} = this.state
this.setState({
// Remove peer ID from state array
peerIds: peerIds.filter(id => id !== uid)
})
})
// If Local user joins RTC channel
this._engine.addListener('JoinChannelSuccess', (channel, uid, elapsed) => {
console.log('JoinChannelSuccess', channel, uid, elapsed)
// Set state variable to true
this.setState({
joinSucceed: true
})
})
}
...
我們定義了一個基于類的組件:變量 _engine
將存儲從 Agora SDK 導入的 RtcEngine
類實例。這個實例提供了主要的方法,我們的應用程序可以調用這些方法來使用SDK的功能。
在構造函數中,設置狀態變量,并為 Android 上的攝像頭和麥克風獲取權限。(我們使用了下文所述的 permission.ts
的幫助函數)當組件被掛載時,我們調用 init
函數 ,使用 App ID 初始化 RTC 引擎。它還可以通過調用 engine 實例上的 enableVideo
方法來啟用視頻。(如果省略這一步,SDK 可以在純音頻模式下工作。)
init函數還為視頻調用中的各種事件添加了事件監聽器。例如,UserJoined
事件為我們提供了用戶加入頻道時的 UID。我們將這個 UID 存儲在我們的狀態中,以便在以后渲染他們的視頻時使用。
注意:如果在我們加入之前有用戶連接到頻道,那么在他們加入頻道之后,每個用戶都會被觸發一個
UserJoined
事件。
...
/**
* @name startCall
* @description Function to start the call
*/
startCall = async () => {
// Join Channel using null token and channel name
await this._engine?.joinChannel(this.state.token, this.state.channelName, null, 0)
}
/**
* @name endCall
* @description Function to end the call
*/
endCall = async () => {
await this._engine?.leaveChannel()
this.setState({peerIds: [], joinSucceed: false})
}
render() {
return (
<View style={styles.max}>
<View style={styles.max}>
<View style={styles.buttonHolder}>
<TouchableOpacity
onPress={this.startCall}
style={styles.button}>
<Text style={styles.buttonText}> Start Call </Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.endCall}
style={styles.button}>
<Text style={styles.buttonText}> End Call </Text>
</TouchableOpacity>
</View>
{this._renderVideos()}
</View>
</View>
)
}
_renderVideos = () => {
const {joinSucceed} = this.state
return joinSucceed ? (
<View style={styles.fullView}>
<RtcLocalView.SurfaceView
style={styles.max}
channelId={this.state.channelName}
renderMode={VideoRenderMode.Hidden}/>
{this._renderRemoteVideos()}
</View>
) : null
}
_renderRemoteVideos = () => {
const {peerIds} = this.state
return (
<ScrollView
style={styles.remoteContainer}
contentContainerStyle={{paddingHorizontal: 2.5}}
horizontal={true}>
{peerIds.map((value, index, array) => {
return (
<RtcRemoteView.SurfaceView
style={styles.remote}
uid={value}
channelId={this.state.channelName}
renderMode={VideoRenderMode.Hidden}
zOrderMediaOverlay={true}/>
)
})}
</ScrollView>
)
}
}
接下來,還有開始和結束視頻聊天通話的方法。 joinChannel
方法接收 Token、頻道名、其他可選信息和一個可選的 UID
(如果你將 UID
設置為 0,系統會自動為本地用戶分配 UID
)。
我們還定義了渲染方法,用于顯示開始和結束通話的按鈕,以及顯示本地視頻源和遠程用戶的視頻源。我們定義了 _renderVideos
方法 來渲染我們的視頻源,使用 peerIds
數組在滾動視圖中渲染。
為了顯示本地用戶的視頻源,我們使用 <RtcLocalView.SurfaceView>
組件,需要提供 channelId
和 renderMode
。連接到同一 個 channelId
的用戶可以相互通信 ,而 renderMode
用于將視頻放入視圖中或通過縮放來填充視圖。
為了顯示遠程用戶的視頻源,我們使用 SDK 中的 <RtcLocalView.SurfaceView>
組件,它可以獲取遠程用戶的 UID
以及 channelId
和 renderMode
。
Permission.ts
import {PermissionsAndroid} from 'react-native'
/**
* @name requestCameraAndAudioPermission
* @description Function to request permission for Audio and Camera
*/
export default async function requestCameraAndAudioPermission() {
try {
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
])
if (
granted['android.permission.RECORD_AUDIO'] === PermissionsAndroid.RESULTS.GRANTED
&& granted['android.permission.CAMERA'] === PermissionsAndroid.RESULTS.GRANTED
) {
console.log('You can use the cameras & mic')
} else {
console.log('Permission denied')
}
} catch (err) {
console.warn(err)
}
}
導出一個函數,向Android上的操作系統申請攝像頭和麥克風的權限。
Style.ts
import {Dimensions, StyleSheet} from 'react-native'
const dimensions = {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
}
export default StyleSheet.create({
max: {
flex: 1,
},
buttonHolder: {
height: 100,
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
},
button: {
paddingHorizontal: 20,
paddingVertical: 10,
backgroundColor: '#0093E9',
borderRadius: 25,
},
buttonText: {
color: '#fff',
},
fullView: {
width: dimensions.width,
height: dimensions.height - 100,
},
remoteContainer: {
width: '100%',
height: 150,
position: 'absolute',
top: 5
},
remote: {
width: 150,
height: 150,
marginHorizontal: 2.5
},
noUserText: {
paddingHorizontal: 10,
paddingVertical: 5,
color: '#0093E9',
},
})
Style.ts
文件包含了組件的 樣式。
這就是快速開發一個 React Native 視頻聊天通話 App 的方法。你可以參考 Agora React Native API Reference 去查看可以幫助你快速添加更多功能的方法,比如將攝像頭和麥克風靜音,設置視頻配置文件和音頻混合等等。
獲取更多文檔、Demo、技術幫助
- 獲取 SDK 開發文檔,可訪問聲網文檔中心。
- 如需參考各類場景 Demo,可訪問下載中心獲取。
- 如遇開發疑難,可訪問論壇發帖提問。
- 了解更多教程、RTE 技術干貨與技術活動,可訪問聲網開發者社區。
- 歡迎掃碼關注我們。