实现音视频互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
更多概念详见关键概念。
下图以直播场景为例,展示在 App 中实现音视频互动的基本工作流程:

- 所有用户调用
joinChannel
方法加入频道,并根据需要设置用户角色:- 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
- 视频通话:将所有的用户角色都为主播。
- 加入频道后,不同角色的用户具备不同的行为:
- 所有用户默认都可以接收频道中的音视频流。
- 主播可以在频道内发布音视频流。
- 观众如果需要发流,可在频道内调用
setClientRole
方法修改用户角色,使其具备发流权限。
前提条件
-
Flutter 2.10.5 或更高版本。
-
Dart 2.14.0 或更高版本。
-
根据你的目标平台,准备对应的开发和运行环境:
目标平台 环境要求 iOS - macOS 10.15 或更高版本
- 最新版本的 Xcode
- 两台 iOS 设备
Android - macOS 10.15 或更高版本
- Windows 10 或更高版本
- 最新版本的 Android Studio
- 两台 Android 设备
macOS - macOS 10.15 或更高版本
- 最新版本的 Xcode
- 两台 macOS 设备
Windows - Windows 10 或更高版本
- 最新版本的 Visual Studio
- 两台 Windows 设备
信息- 更多环境要求细节,详见 Install Flutter。
- 你可以运行
flutter doctor
命令检查开发和运行环境是否达到要求。
-
可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
-
一个有效的声网账号以及声网项目。请参考开通服务从声网控制台获得以下信息:
- App ID:声网随机生成的字符串,用于识别你的项目。
- 临时 Token:Token 也称为动态密钥,在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
创建项目
根据你选用的 IDE 不同,创建 Flutter 项目有多种方式,详见 Create the app。在本文中,以在终端中运行 flutter create
命令创建 Flutter 项目为例。
# 将 <Project Name> 替换为项目名
flutter create <Project Name>
集成 SDK
-
打开
pubspec.yaml
文件,添加以下依赖:- 添加
agora_rtc_engine
依赖项,集成声网 Flutter SDK。关于agora_rtc_engine
的最新版本可以查询 https://pub.dev/packages/agora_rtc_engine - 添加
permission_handler
依赖项,安装权限处理插件。
YAMLenvironment:
sdk: ">=2.12.0 <3.0.0"
# 依赖项
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
# 声网 Flutter SDK 依赖项,请使用最新版本的 agora_rtc_engine
agora_rtc_engine: ^6.0.0
# 权限处理插件依赖项
permission_handler: ^8.3.0 - 添加
-
添加依赖并保存文件后,打开终端,在项目路径下执行以下命令获取并安装依赖:
Shellflutter pub get
创建用户界面
根据实时音视频互动的场景需要,为你的项目创建两个视图框,分别用于展示本地视频和远端视频。复制以下代码即可快速创建场景所需的用户界面。
// 构建 UI,显示本地视图和远端视图
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
),
)
: const CircularProgressIndicator(),
),
),
),
],
),
),
);
}
实现流程
本小节介绍如何实现一个实时音视频互动 App。你可以先复制完整的示例代码到你的项目中,快速体验实时音视频互动的基础功能,再按照实现步骤了解其中的核心 API 调用。
下图展示了使用声网 RTC SDK 实现音视频互动的基本流程:
下面列出了一段实现实时音视频互动基本流程的完整代码以供参考。复制以下代码替换 /lib/main.dart
文件的全部内容,即可快速体验实时音视频互动基础功能。
将 <-- Insert App Id -->
、<-- Insert Token -->
和 <-- Insert Channel Name -->
分别替换为你在控制台获取到的 App ID、临时 Token,以及生成临时 token 时填入的频道名。
import 'dart:async';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() => runApp(MyApp());
// 填写项目的 App ID,可在声网控制台中生成
const appId = "<-- Insert App Id -->";
// 填写声网控制台中生成的临时 Token
const token = "<-- Insert Token -->";
// 填写频道名
const channel = "<-- Insert Channel Name -->";
// 应用类
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
// 应用状态类
class _MyAppState extends State<MyApp> {
int? _remoteUid; // 用于存储远端用户的 uid
bool _localUserJoined = false; // 表示本地用户是否加入频道,初始值为 false
late RtcEngine _engine; // 用于存储 RtcEngine 实例
void initState() {
super.initState();
initAgora();
}
Future<void> initAgora() async {
// 获取麦克风和摄像头权限
await [Permission.microphone, Permission.camera].request();
// 创建 RtcEngine 对象
_engine = await createAgoraRtcEngine();
// 初始化 RtcEngine,设置频道场景为直播场景
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
// 添加回调事件
_engine.registerEventHandler(
RtcEngineEventHandler(
// 成功加入频道回调
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
debugPrint("local user ${connection.localUid} joined");
setState(() {
_localUserJoined = true;
});
},
// 远端用户加入频道回调
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
debugPrint("remote user $remoteUid joined");
setState(() {
_remoteUid = remoteUid;
});
},
// 远端用户离开频道回调
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
debugPrint("remote user $remoteUid left channel");
setState(() {
_remoteUid = null;
});
},
),
);
// 启用视频模块
await _engine.enableVideo();
// 开启本地预览
await _engine.startPreview();
// 加入频道
await _engine.joinChannel(
token: token,
channelId: channel,
options: const ChannelMediaOptions(
// 设置用户角色为主播
// 如果要将用户角色设置为观众,则修改 clientRoleBroadcaster 为 clientRoleAudience
clientRoleType: ClientRoleType.clientRoleBroadcaster),
uid: 0,
);
}
void dispose() {
super.dispose();
_dispose();
}
Future<void> _dispose() async {
await _engine.leaveChannel(); // 离开频道
await _engine.release(); // 释放资源
}
// 构建 UI,显示本地视图和远端视图
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
),
)
: const CircularProgressIndicator(),
),
),
),
],
),
),
);
}
// 生成远端视频
Widget _remoteVideo() {
if (_remoteUid != null) {
return AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: _remoteUid),
connection: const RtcConnection(channelId: channel),
),
);
} else {
return const Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
导入 package
导入构建 App 所需的 package。
import 'dart:async';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
定义 App ID 和 Token
传入从声网控制台获取的 App ID、临时 Token,以及生成临时 Token 时填入的频道名,用于后续初始化引擎和加入频道。
// 填写项目的 App ID,可在声网控制台中生成
const appId = "<-- Insert App Id -->";
// 填写声网控制台中生成的临时 Token
const token = "<-- Insert Token -->";
// 填写频道名
const channel = "<-- Insert Channel Name -->";
处理权限请求
获取体验实时音视频所需的麦克风和摄像头权限。
await [Permission.microphone, Permission.camera].request();
初始化引擎
调用 createAgoraRtcEngine
方法创建一个 RtcEngine
对象,然后调用 initialize
初始化引擎并设置频道场景为 Broadcasting
直播场景。
// 创建 RtcEngine 对象
_engine = await createAgoraRtcEngine();
// 初始化 RtcEngine,设置频道场景为直播场景
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
启用视频模块
按照以下步骤启用视频模块:
- 调用
enableVideo
方法,启用视频模块。 - 调用
startPreview
方法,开启本地视频预览。
// 启用视频模块
await _engine.enableVideo();
// 开启本地预览
await _engine.startPreview();
加入频道并发布音视频流
调用 joinChannel
加入频道。在 ChannelMediaOptions
中设置用户角色设置为 clientRoleBroadcaster
(主播) 或 clientRoleAudience
(观众)。
// 加入频道
await _engine.joinChannel(
token: token,
channelId: channel,
options: const ChannelMediaOptions(
// 设置用户角色为主播
// 如果要将用户角色设置为观众,则修改 clientRoleBroadcaster 为 clientRoleAudience
clientRoleType: ClientRoleType.clientRoleBroadcaster),
uid: 0,
);
设置远端视图
本小节介绍如何获取远端用户的 uid
并渲染远端视图。
- 调用
registerEventHandler
添加回调事件并处理事件逻辑。
// 添加回调事件
_engine.registerEventHandler(
RtcEngineEventHandler(
// 成功加入频道回调
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
debugPrint("local user ${connection.localUid} joined");
setState(() {
_localUserJoined = true;
});
},
// 远端用户加入频道回调
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
debugPrint("remote user $remoteUid joined");
setState(() {
_remoteUid = remoteUid;
});
},
// 远端用户离开频道回调
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
debugPrint("remote user $remoteUid left channel");
setState(() {
_remoteUid = null;
});
},
),
);
- 当远端用户加入频道时,调用
setupRemoteVideo
传入从onUserJoined
回调中获取到的远端用户的uid
,设置并渲染远端视图。
// 生成远端视频
Widget _remoteVideo() {
if (_remoteUid != null) {
return AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: _remoteUid),
connection: const RtcConnection(channelId: channel),
),
);
} else {
return const Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
离开频道并释放资源
关闭 App 或 App 切换至后台时,调用 leaveChannel
离开当前直播频道。
Future<void> _dispose() async {
await _engine.leaveChannel(); // 离开频道
await _engine.release(); // 释放资源
}
测试 App
按照以下步骤测试直播 App:
-
将目标设备连接到电脑。
-
打开终端,在项目路径下执行以下命令在目标设备上运行示例项目:
Shellflutter run
-
启动 App,授予麦克风和摄像头权限,如果你将用户角色设置为主播,便会在本地视图中看到自己。
-
使用第二台设备,重复以上步骤,在该设备上安装 App、打开 App 加入频道,观察测试结果:
- 如果两台设备均作为主播加入频道,则可以看到对方并且听到对方的声音。
- 如果两台设备分别作为主播和观众加入,则主播可以在本地视频窗口看到自己;观众可以在远端视频窗口看到主播、并听到主播的声音。
以 Android 设备为例,编译成功后的 App 如下图所示:

后续步骤
- 本文的示例使用了临时 Token 加入频道。在测试或生产环境中,为确保通信安全,声网推荐使用 Token 服务器来生成 Token,详见使用 Token 鉴权。
- 如果你想要实现极速直播场景,可以在互动直播的基础上,通过修改观众端的延时级别为低延时 (
audienceLatencyLevelLowLatency
)实现。详见实现极速直播。
相关信息
本节提供了额外的信息供参考。
示例项目
声网在 GitHub 上提供了一个开源的实时音视频互动示例项目 join_channel_video.dart 供你参考。
常见问题
- 直播场景下,如何监听远端观众角色用户加入/离开频道的事件?
- 如何处理视频黑屏问题?
- 为什么我无法打开摄像头?
- 如何处理频道相关常见问题?
- 如何设置日志文件?
- 为什么 Android 9 应用锁屏或切后台后采集音视频无效?
- 编译 Xcode 项目时遇到无法打开 framework 的弹窗警告怎么办?