使用 Agora SDK 開發 React Native 視頻通話App

在 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賬戶

聲網后臺中的項目管理
  • 找到 "項目管理 "下的 "項目列表 "選項卡,點擊藍色的 "創建 "按鈕,創建一個項目。(當提示使用 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> 組件,需要提供 channelIdrenderMode 。連接到同一 個 channelId 的用戶可以相互通信 ,而 renderMode 用于將視頻放入視圖中或通過縮放來填充視圖。

為了顯示遠程用戶的視頻源,我們使用 SDK 中的 <RtcLocalView.SurfaceView> 組件,它可以獲取遠程用戶的 UID 以及 channelIdrenderMode

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、技術幫助

聲網Agora 開發者
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容