实现纯语音互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的纯语音互动 App,适用于语音通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
 - 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
 - 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
 - 观众:可以在频道内订阅音视频,不具备发布音视频权限。
 
更多概念详见关键概念。
下图展示在 App 中实现纯语音互动的基本工作流程:
- 所有用户调用 
joinChannel方法加入频道,并将所有用户角色都设置为主播。 - 加入频道后,所有用户都可以在频道内发布音频流,并订阅对方的音频流。
 
前提条件
- 目标平台为 Android
 - 目标平台为 iOS
 
- 已参考 React Native 官方文档搭建好开发环境。
 - macOS 、Windows 或 Linux 操作系统
 - Android 5.0 或以上设备
 - 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
 - 有效的声网账户和声网项目。请参考开通服务,从声网控制台获取以下信息:
- App ID:声网随机生成的字符串,用于识别你的 App。
 - 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
 
 
- 已参考 React Native 官方文档搭建好开发环境。
 - macOS 操作系统
 - iOS 9.0 或以上设备。如果你使用 React Native 0.63 或以上版本,请确保 iOS 的版本高于 10.0。
 - 已安装 Cocoapods,否则请参考 Getting Started with CocoaPods 进行安装。
 - 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
 - 有效的声网账户和声网项目。请参考开通服务,从声网控制台获取以下信息:
- App ID:声网随机生成的字符串,用于识别你的 App。
 - 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
 
 
创建项目
参考以下步骤,创建一个 React Native 项目。
- 
运行以下命令,在
ProjectName处填入你的项目名称,创建并初始化一个新项目。Shell# 初始化 package.json 文件
npm init
# 安装 React Native
npm install react-native
# 创建 React Native 项目
npx react-native init ProjectName运行完成后,会在执行该命令的路径下自动生成一个简单的示例项目。
 - 
执行
npx react-native run-android运行 Android 应用或执行npx react-native run-ios运行 iOS 应用。 
如果配置没有问题,你可以看到模拟器被打开,App 自动安装到模拟器上并开始运行。你也可以在 Android 或 iOS 真机上运行应用,详见在设备上运行。
现在你已经成功运行了项目,可以开始尝试集成声网 SDK 并修改项目。
集成 SDK
本节介绍如何在 React Native 0.60 或以上版本集成声网 React Native SDK。
根据你的目标开发平台,执行以下步骤:
- 目标平台为 Android
 - 目标平台为 iOS
 
在项目根目录下,使用以下任意一种方法下载最新版的声网 React Native SDK:
- 
方法一:使用 npm 下载
Shellnpm i --save react-native-agora - 
方法二:使用 yarn 下载
Shell# 安装 yarn
npm install -g yarn
# 使用 yarn 下载声网 React Native SDK
yarn add react-native-agora 
React Native 0.60.0 或以上版本支持自动链接原生模块,请勿手动链接。详见 Autolinking。
- 在项目根目录下,使用以下任意一种方法下载最新版的声网 React Native SDK:
 
- 
方法一:使用 npm 下载
Shellnpm i --save react-native-agora - 
方法二:使用 yarn 下载
Shell# 安装 yarn
npm install -g yarn
# 使用 yarn 下载声网 React Native SDK
yarn add react-native-agora 
React Native 0.60.0 或以上版本支持自动链接原生模块,请勿手动链接。详见 Autolinking。
- 进入 React Native 项目自动创建的 
ios文件夹,运行以下命令安装 SDK: 
cd ios
npx pod-install
创建用户界面
根据纯语音互动的场景需要,为你的项目创建加入频道和离开频道按钮。复制以下代码即可快速创建场景所需的用户界面。
// 导入 React Hooks
import React, {useRef, useState, useEffect} from 'react';
// 导入用户界面元素
import {
    SafeAreaView,
    ScrollView,
    StyleSheet,
    Text,
    View,
} from 'react-native';
const App = () => {
    const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine 实例
    const [isJoined, setIsJoined] = useState(false); // 本地用户是否已加入频道
    const [remoteUid, setRemoteUid] = useState(0); // 远端用户的 Uid
    const [message, setMessage] = useState(''); // 用户提示信息
    
    // 渲染用户界面
    return (
        <SafeAreaView style={styles.main}>
          <Text style={styles.head}>声网语音通话快速开始</Text>
          <View style={styles.btnContainer}>
            <Text onPress={join} style={styles.button}>
              加入频道
            </Text>
            <Text onPress={leave} style={styles.button}>
              离开频道
            </Text>
          </View>
          <ScrollView
            style={styles.scroll}
            contentContainerStyle={styles.scrollContainer}>
            {isJoined ? (
              <Text>本地用户 uid: {uid}</Text>
            ) : (
              <Text>加入一个频道</Text>
            )}
            {isJoined && remoteUid !== 0 ? (
              <Text>远端用户 uid: {remoteUid}</Text>
            ) : (
              <Text>等待远端用户加入</Text>
            )}
            <Text>{message}</Text>
          </ScrollView>
        </SafeAreaView>
    );
	// 展示信息
    function showMessage(msg: string) {
        setMessage(msg);
    }
};
// 定义用户界面样式
const styles = StyleSheet.create({
    button: {
        paddingHorizontal: 25,
        paddingVertical: 4,
        fontWeight: 'bold',
        color: '#ffffff',
        backgroundColor: '#0055cc',
        margin: 5,
    },
    main: {flex: 1, alignItems: 'center'},
    scroll: {flex: 1, backgroundColor: '#ddeeff', width: '100%'},
    scrollContainer: {alignItems: 'center'},
    videoView: {width: '90%', height: 200},
    btnContainer: {flexDirection: 'row', justifyContent: 'center'},
    head: {fontSize: 20},
});
export default App;
实现步骤
本节介绍如何实现一个纯语音互动 App。
下图展示了使用声网 RTC SDK 实现纯语音互动的基本流程。
下面列出了一段实现纯语音互动基本流程的完整代码以供参考,你可以先复制完整的示例代码到你的项目中,参考测试项目中的步骤快速体验实时音视频互动的基础功能,再按照实现流程了解其中的核心 API 调用。
复制以下代码替换 App.tsx 文件的全部内容,即可快速体验纯语音互动基础功能。
// 导入 React Hooks
import React, { useRef, useState, useEffect } from 'react';
// 导入用户界面元素
import {
    SafeAreaView,
    ScrollView,
    StyleSheet,
    Text,
    View,
} from 'react-native';
// 导入获取 Android 设备权限相关组件
import { PermissionsAndroid, Platform } from 'react-native';
// 导入声网 SDK
import {
    createAgoraRtcEngine,
    ChannelProfileType,
    ClientRoleType,
    IRtcEngine,
    RtcConnection,
    IRtcEngineEventHandler
} from 'react-native-agora';
// 定义基本信息
const appId = '<-- Insert App ID -->';
const token = '<-- Insert Token -->';
const channelName = '<-- Insert Channel Name -->';
const uid = 0; // 本地用户 Uid,无需修改
const App = () => {
    const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine 实例
    const [isJoined, setIsJoined] = useState(false); // 本地用户是否已加入频道
    const [isHost, setIsHost] = useState(true); // 用户角色
    const [remoteUid, setRemoteUid] = useState(0); // 远端用户的 Uid
    const [message, setMessage] = useState(''); // 用户提示信息
    const eventHandles = useRef<IRtcEngineEventHandler>(); // 回调函数
    useEffect(() => {
        // 启动 App 时初始化引擎
        setupVideoSDKEngine();
        // 关闭 App 时释放内存
        return () => {
            agoraEngineRef.current?.unregisterEventHandler(eventHandles.current!);
            agoraEngineRef.current?.release();
        };
    }, []);
    // 定义启动 App 时调用的 setupVideoSDKEngine 方法 
    const setupVideoSDKEngine = async () => {
        try {
            // 获取设备权限后创建 RtcEngine
            if (Platform.OS === 'android') {
                await getPermission()
            };
            agoraEngineRef.current = createAgoraRtcEngine();
            const agoraEngine = agoraEngineRef.current;
            eventHandles.current = {
                onJoinChannelSuccess: () => {
                    showMessage('成功加入频道:' + channelName);
                    setIsJoined(true);
                },
                onUserJoined: (_connection: RtcConnection, uid: number) => {
                    showMessage('远端用户 ' + uid + ' 已加入');
                    setRemoteUid(uid);
                },
                onUserOffline: (_connection: RtcConnection, uid: number) => {
                    showMessage('远端用户 ' + uid + '已离开频道');
                    setRemoteUid(0);
                },
            }
            // 注册回调事件
            agoraEngine.registerEventHandler(eventHandles.current);
            // 初始化引擎
            agoraEngine.initialize({
                appId: appId,
            });
        } catch (e) {
            console.log(e);
        }
    };
    // 定义点击加入频道按钮后调用的 join 方法 
    const join = async () => {
        if (isJoined) {
            return;
        }
        try {
            if (isHost) {
                // 以主播角色加入频道
                agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                    // 设置频道场景为直播场景
                    channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                    // 设置用户角色为主播
                    clientRoleType: ClientRoleType.ClientRoleBroadcaster,
                    // 发布麦克风采集的音频
                    publishMicrophoneTrack: true,
                    // 自动订阅所有音频流
                    autoSubscribeAudio: true,
                });
            } else {
                // 以观众角色加入频道
                agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                    // 设置频道场景为直播场景
                    channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                    // 设置用户角色为观众
                    clientRoleType: ClientRoleType.ClientRoleAudience,
                    // 不发布麦克风采集的音频
                    publishMicrophoneTrack: false,
                    // 自动订阅所有音频流
                    autoSubscribeAudio: true,
                });
            }
        } catch (e) {
            console.log(e);
        }
    };
    // 定义点击离开频道按钮后调用的 leave 方法 
    const leave = () => {
        try {
            // 调用 leaveChannel 方法离开频道
            agoraEngineRef.current?.leaveChannel();
            setRemoteUid(0);
            setIsJoined(false);
            showMessage('已离开频道');
        } catch (e) {
            console.log(e);
        }
    };
    // 渲染用户界面
    return (
        <SafeAreaView style={styles.main}>
            <Text style={styles.head}>声网语音通话快速开始</Text>
            <View style={styles.btnContainer}>
                <Text onPress={join} style={styles.button}>
                    加入频道
                </Text>
                <Text onPress={leave} style={styles.button}>
                    离开频道
                </Text>
            </View>
            <ScrollView
                style={styles.scroll}
                contentContainerStyle={styles.scrollContainer}>
                {isJoined ? (
                    <Text>本地用户 uid: {uid}</Text>
                ) : (
                    <Text>加入一个频道</Text>
                )}
                {isJoined && remoteUid !== 0 ? (
                    <Text>远端用户 uid: {remoteUid}</Text>
                ) : (
                    <Text>等待远端用户加入</Text>
                )}
                <Text>{message}</Text>
            </ScrollView>
        </SafeAreaView>
    );
    // 展示信息
    function showMessage(msg: string) {
        setMessage(msg);
    }
};
// 定义用户界面样式
const styles = StyleSheet.create({
    button: {
        paddingHorizontal: 25,
        paddingVertical: 4,
        fontWeight: 'bold',
        color: '#ffffff',
        backgroundColor: '#0055cc',
        margin: 5,
    },
    main: { flex: 1, alignItems: 'center' },
    scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
    scrollContainer: { alignItems: 'center' },
    videoView: { width: '90%', height: 200 },
    btnContainer: { flexDirection: 'row', justifyContent: 'center' },
    head: { fontSize: 20 },
});
const getPermission = async () => {
    if (Platform.OS === 'android') {
        await PermissionsAndroid.requestMultiple([
            PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
        ]);
    }
};
export default App;
在客户端实现语音通话
本节展示如何使用声网 React Native SDK 为你的 App 添加语音通话功能。
导入声网组件
导入构建 App 所需的声网组件。
// 导入声网 SDK
import {
    createAgoraRtcEngine,
    ChannelProfileType,
    ClientRoleType,
    IRtcEngine,
    RtcConnection,
    IRtcEngineEventHandler
} from 'react-native-agora';
定义基本信息
传入从声网控制台获取的 App ID、临时 Token,以及生成临时 Token 时填入的频道名,用于后续初始化引擎和加入频道。
const appId = '<-- Insert App ID -->';
const token = '<-- Insert Token -->';
const channelName = '<-- Insert Channel Name -->';
const uid = 0; // 本地用户 Uid,无需修改
获取设备权限
根据你的目标平台选择获得麦克风和摄像头的使用权限的方式:
- Android
 - iOS
 
在 Android 设备上,要获得麦克风和摄像头的使用权限,需要弹出提示框供用户选择。
// 导入获取 Android 设备权限相关组件
import {PermissionsAndroid, Platform} from 'react-native';
const getPermission = async () => {
    if (Platform.OS === 'android') {
        await PermissionsAndroid.requestMultiple([
            PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
        ]);
    }
};
在 Xcode 中,打开 info.plist 文件。在右侧列表中添加如下内容,获取相应的设备权限:
| Key | Type | Value | 
|---|---|---|
| Privacy - Microphone Usage Description | String | 使用麦克风的目的,例如:for a call or live interactive streaming。 | 
初始化引擎
调用 createAgoraRtcEngine 方法创建一个 RtcEngine 实例,然后调用 initialize 初始化引擎。
agoraEngineRef.current = createAgoraRtcEngine();
const agoraEngine = agoraEngineRef.current;
agoraEngine.initialize({
    appId: appId,
});
加入频道并发布音频流
调用 joinChannel 方法,填入你在控制台获取的临时 Token,以及获取 Token 时填入的频道名加入频道,并在 options 参数中设置频道场景、用户角色等信息。
if (isHost) {
    // 以主播角色加入频道
    agoraEngineRef.current?.joinChannel(token, channelName, uid, {
        // 设置频道场景为直播场景
        channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
        // 设置用户角色为主播
        clientRoleType: ClientRoleType.ClientRoleBroadcaster,
        // 发布麦克风采集的音频
        publishMicrophoneTrack: true,
        // 自动订阅所有音频流
        autoSubscribeAudio: true,
    });
} else {
    // 以观众角色加入频道
    agoraEngineRef.current?.joinChannel(token, channelName, uid, {
        // 设置频道场景为直播场景
        channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
        // 设置用户角色为观众
        clientRoleType: ClientRoleType.ClientRoleAudience,
        // 不发布麦克风采集的音频
        publishMicrophoneTrack: false,
        // 自动订阅所有音频流
        autoSubscribeAudio: true,
    });
}
实现常用回调
注册以下必要的回调事件可以在触发相应的事件时调用 showMessage 方法提示操作结果,并更新组件状态。
onJoinChannelSuccess:成功加入频道回调。onUserJoined:远端用户加入当前频道回调。onUserOffline:远端用户离开当前频道回调。
const eventHandles = useRef<IRtcEngineEventHandler>(); 
eventHandles.current = {
    onJoinChannelSuccess: () => {
        showMessage('成功加入频道:' + channelName);
        setIsJoined(true);
    },
    onUserJoined: (_connection: RtcConnection, uid: number) => {
        showMessage('远端用户 ' + uid + ' 已加入');
        setRemoteUid(uid);
    },
    onUserOffline: (_connection: RtcConnection, uid: number) => {
        showMessage('远端用户 ' + uid + '已离开频道');
        setRemoteUid(0);
    },
}
// 注册回调事件
agoraEngine.registerEventHandler(eventHandles.current);
离开频道
调用 leaveChannel 方法离开频道、释放资源并显示文字提示。
// 调用 leaveChannel 方法离开频道
agoraEngineRef.current?.leaveChannel();
setRemoteUid(0);
setIsJoined(false);
showMessage('已离开频道');
如果你不再需要互动,调用 release 方法释放引擎资源。
// 取消注册回调
agoraEngineRef.current?.unregisterEventHandler(eventHandles.current!);
// 销毁引擎
agoraEngineRef.current?.release();
- 调用 
release后,你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能,你必须重新创建一个新的引擎。详见初始化引擎。 - 该方法为同步调用。需要等待引擎资源释放后才能执行其他操作,因此建议在子线程中调用该方法,避免主线程阻塞。
 
调试项目
- 
确保已经在快速开始代码中传入了有效的
appId,channelName和token。 - 
运行 App。部分模拟机可能无法支持本项目的全部功能,建议你在真机中运行该项目。
- 
在 Android 设备上运行:
a. 开启 Android 设备的开发者选项,通过 USB 连接线将 Android 设备接入电脑。
b. 在项目根目录下执行
npx react-native run-android。 - 
在 iOS 设备上运行:
a. 使用 Xcode 打开
ProjectName/ios/ProjectName.xcworkspace文件夹。b. 通过 USB 线将 iOS 设备接入你的电脑。
c. 在 Xcode 中,点击 Build and run 按钮。
 
信息在 Android 或 iOS 真机上运行 App 的详细步骤,请参考 Running On Device。
 - 
 - 
启动 App,点击按钮加入频道。
 - 
使用第二台设备,重复以上步骤,在该设备上安装 App,并打开 App 加入频道,观察测试结果:双方可以听到对方的声音。
 
后续步骤
本文的示例使用了临时 Token 加入频道。在测试或生产环境中,为确保通信安全,声网推荐使用 Token 服务器来生成 Token,详见使用 Token 鉴权。
参考信息
示例项目
声网提供了开源的纯语音互动示例项目供你参考,你可以前往下载或查看其中的源代码。