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

在 React Native 的應用中,從頭開始添加視頻通話功能是很複雜的。要保證低延遲、負載平衡,還要注意管理用戶事件狀態,很是繁瑣。除此以外,還必須保證跨平臺的兼容性。html

固然有個簡單的方法能夠作到這一點。在本次的教程中,咱們將使用 Agora Video SDK 來構建一個 React Native 視頻通話 App。在深刻探討程序工做以前,咱們將介紹應用的結構、設置和執行。你能夠在幾分鐘內,經過幾個簡單的步驟,讓一個跨平臺的視頻通話應用運行起來。react

咱們將使用 Agora RTC SDK for React Native 來作例子。在這篇文章中,我使用的版本是 v3.1.6。android

建立一個Agora帳戶

登陸聲網後臺.png

  • 找到 "項目管理 "下的 "項目列表 "選項卡,點擊藍色的 "建立 "按鈕,建立一個項目。(當提示使用 App ID+證書時,選擇只使用 App ID。)記住你的 App ID,它將在開發App時用於受權你的請求。
注意:本文沒有采用 Token 鑑權,建議在生產環境中運行的全部RTE App 都採用Token鑑權。有關Agora平臺中基於Token的身份驗證的更多信息,請在聲網文檔中心搜索關鍵詞「Token」,參考相關文檔。

示例項目結構

這就是咱們正在構建的應用程序的結構:ios

.

├── 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 做爲頻道名稱。npm

應用工做原理

App.tsx

這個文件包含了 React Native 視頻通話App中視頻通話的全部核心邏輯。segmentfault

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聲明。接下來,爲應用狀態定義一個接口,包含:react-native

  • 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 能夠在純音頻模式下工做。)session

init函數還爲視頻調用中的各類事件添加了事件監聽器。例如,UserJoined 事件爲咱們提供了用戶加入頻道時的 UID。咱們將這個 UID 存儲在咱們的狀態中,以便在之後渲染他們的視頻時使用。app

注意:若是在咱們加入以前有用戶鏈接到頻道,那麼在他們加入頻道以後,每一個用戶都會被觸發一個 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 開發者

相關文章
相關標籤/搜索