# 实现音视频互动 (/zh-CN/realtime-media/rtc/get-started/quick-start/flutter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

本文介绍如何集成声网实时互动 SDK，通过少量代码从 0 开始实现一个简单的实时互动 App，适用于互动直播和视频通话场景。
首先，你需要了解以下有关音视频实时互动的基础概念：

- 声网实时互动 SDK：由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道：用于传输数据的通道，在同一个频道内的用户可以进行实时互动。
- 主播：可以在频道内**发布**音视频，同时也可以**订阅**其他主播发布的音视频。
- 观众：可以在频道内**订阅**音视频，不具备**发布**音视频权限。

更多概念详见[关键概念](/zh-CN/realtime-media/rtc/reference/key-concept)。

下图展示在 App 中实现音视频互动的基本工作流程：

<DocsImage alt="实现音视频互动" src="https://web-cdn.agora.io/docs-portal/img/rtc/quick-start-tech.png" displayWidth="60%" />

1. 所有用户调用 `joinChannel` 方法加入频道，并根据需要设置用户角色：

   - 互动直播：如果用户需要在频道中发流，则设为主播；如果用户只需要收流，则设为观众。
   - 视频通话：将所有用户的角色都设为主播。
2. 加入频道后，不同角色的用户具备不同的行为：

   - 所有用户默认都可以接收频道中的音视频流。
   - 主播可以在频道内发布音视频流。
   - 观众如果需要发流，可在频道内调用 `setClientRole` 方法修改用户角色，使其具备发流权限。

## 前提条件 [#前提条件-9]

- Flutter 2.10.5 或更高版本。
- Dart 2.14.0 或更高版本。
- 根据你的目标平台，准备对应的开发和运行环境：

| 目标平台 | 环境要求 |
| --- | --- |
| iOS | [quick-start-5923-2-1 details](#table-slot-quick-start-5923-2-1) |
| Android | [quick-start-5923-3-1 details](#table-slot-quick-start-5923-3-1) |
| macOS | [quick-start-5923-4-1 details](#table-slot-quick-start-5923-4-1) |
| Windows | [quick-start-5923-5-1 details](#table-slot-quick-start-5923-5-1) |

<a id="table-slot-quick-start-5923-2-1"></a>

**quick-start-5923-2-1 details**

- macOS 10.15 或更高版本
- 最新版本的 Xcode
- 两台 iOS 设备

<a id="table-slot-quick-start-5923-3-1"></a>

**quick-start-5923-3-1 details**

- macOS 10.15 或更高版本
- Windows 10 或更高版本
- 最新版本的 Android Studio
- 两台 Android 设备

<a id="table-slot-quick-start-5923-4-1"></a>

**quick-start-5923-4-1 details**

- macOS 10.15 或更高版本
- 最新版本的 Xcode
- 两台 macOS 设备

<a id="table-slot-quick-start-5923-5-1"></a>

**quick-start-5923-5-1 details**

- Windows 10 或更高版本
- 最新版本的 Visual Studio
- 两台 Windows 设备

:::info[信息]
- 更多环境要求细节，详见 [Install Flutter](https://docs.flutter.dev/get-started/install)。
- 你可以运行 `flutter doctor` 命令检查开发和运行环境是否达到要求。
:::

- 可以访问互联网的计算机。如果你的网络环境部署了防火墙，参考[应对防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。
- 一个有效的声网账号以及声网项目。请参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)从声网控制台获得以下信息：
    - App ID：声网随机生成的字符串，用于识别你的项目。
    - 临时 Token：Token 也称为动态密钥，在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。

## 创建项目 [#创建项目-8]

根据你选用的 IDE 不同，创建 Flutter 项目有多种方式，详见 [Create the app](https://docs.flutter.dev/get-started/test-drive?tab=vscode#create-app)。在本文中，以在终端中运行 `flutter create` 命令创建 Flutter 项目为例。

  ```shell
  # 将 <Project Name> 替换为项目名
  flutter create <Project Name>
  ```

## 集成 SDK [#集成-sdk-8]

1. 打开 `pubspec.yaml` 文件，添加以下依赖：

    - 添加 `agora_rtc_engine` 依赖项，集成声网 Flutter SDK。关于 `agora_rtc_engine` 的最新版本可以查询 [https://pub.dev/packages/agora_rtc_engine](https://pub.dev/packages/agora_rtc_engine)
    - 添加 `permission_handler` 依赖项，安装权限处理插件。

    ```yaml
    environment:
      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
    ```

2. 添加依赖并保存文件后，打开终端，在项目路径下执行以下命令获取并安装依赖：

    ```shell
    flutter pub get
    ```

## 创建用户界面 [#创建用户界面-7]

根据实时音视频互动的场景需要，为你的项目创建两个视图框，分别用于展示本地视频和远端视频。复制以下代码即可快速创建场景所需的用户界面。

<Accordions>
<Accordion title="创建用户界面示例代码">

```dart
// 构建 UI，显示本地视图和远端视图
@override
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(),
              ),
            ),
          ),
        ],
      ),
    ),
  );
}
```

</Accordion>
</Accordions>

## 实现步骤 [#实现步骤-9]

本小节介绍如何实现一个实时音视频互动 App。你可以先复制完整的示例代码到你的项目中，快速体验实时音视频互动的基础功能，再按照实现步骤了解其中的核心 API 调用。

下图展示了使用声网 RTC SDK 实现音视频互动的基本流程：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/quick-start-sequence.svg" displayWidth="70%" />

下面列出了一段实现实时音视频互动基本流程的完整代码以供参考。复制以下代码替换 `/lib/main.dart` 文件的全部内容，即可快速体验实时音视频互动基础功能。

:::info[信息]
将 `<-- Insert App Id -->`、`<-- Insert Token -->` 和 `<-- Insert Channel Name -->` 分别替换为你在控制台获取到的 App ID、临时 Token，以及生成临时 Token 时填入的频道名。
:::

<Accordions>
<Accordion title="实现实时音视频互动示例代码">

```dart
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);

  @override
  _MyAppState createState() => _MyAppState();
}

// 应用状态类
class _MyAppState extends State<MyApp> {
  int? _remoteUid; // 用于存储远端用户的 uid
  bool _localUserJoined = false; // 表示本地用户是否加入频道，初始值为 false
  late RtcEngine _engine; // 用于存储 RtcEngine 实例

  @override
  void initState() {
    super.initState();
    initAgora();
  }

  Future<void> initAgora() async {
    // 获取麦克风和摄像头权限
    await [Permission.microphone, Permission.camera].request();

    // 创建 RtcEngine 对象
    _engine = await createAgoraRtcEngine();

    // 初始化 RtcEngine，设置频道场景为 channelProfileLiveBroadcasting（直播场景）
    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(
        // 自动订阅所有视频流
        autoSubscribeVideo: true,
        // 自动订阅所有音频流
        autoSubscribeAudio: true,
        // 发布摄像头采集的视频
        publishCameraTrack: true,
        // 发布麦克风采集的音频
        publishMicrophoneTrack: true,
        // 设置用户角色为 clientRoleBroadcaster（主播）或 clientRoleAudience（观众）
        clientRoleType: ClientRoleType.clientRoleBroadcaster),
      uid: 0,
    );
  }

  @override
  void dispose() {
    super.dispose();
    _dispose();
  }

  Future<void> _dispose() async {
    await _engine.leaveChannel(); // 离开频道
    await _engine.release(); // 释放资源
  }

  // 构建 UI，显示本地视图和远端视图
  @override
  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,
      );
    }
  }
}
```

</Accordion>
</Accordions>

### 导入 package [#导入-package]

导入构建 App 所需的 package。

  ```dart
  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-2]

传入从声网控制台获取的 App ID、临时 Token，以及生成临时 Token 时填入的频道名，用于后续初始化引擎和加入频道。

```dart
// 填写声网控制台中获取的 App ID
const appId = "<-- Insert App Id -->";
// 填写声网控制台中生成的临时 Token
const token = "<-- Insert Token -->";
// 填写频道名
const channel = "<-- Insert Channel Name -->";
```

### 处理权限请求 [#处理权限请求-3]

获取体验实时音视频所需的麦克风和摄像头权限。

```dart
await [Permission.microphone, Permission.camera].request();
```

:::info[信息]
如果你的目标平台是 iOS 或 macOS，需要在 [`Info.plist`](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6) 中添加实时互动所需的权限声明如下：

- 麦克风：`key` 为 `Privacy - Microphone Usage Description`，`value` 填写使用目的，如：for a video call。
- 摄像头：`key` 为 `Privacy - Camera Usage Description`，`value` 填写使用目的，如：for a video call。
:::

### 初始化引擎 [#初始化引擎-8]

调用 `createAgoraRtcEngine` 方法创建一个 `RtcEngine` 对象，然后调用 `initialize` 初始化引擎并设置频道场景为 `channelProfileLiveBroadcasting`（直播场景）。

:::warning[注意]
在初始化 SDK 前，需确保终端用户已经充分了解并同意相关的隐私政策。
:::

```dart
// 创建 RtcEngine 对象
_engine = await createAgoraRtcEngine();

// 初始化 RtcEngine，设置频道场景为 channelProfileLiveBroadcasting（直播场景）
await _engine.initialize(const RtcEngineContext(
  appId: appId,
  channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
```

### 启用视频模块 [#启用视频模块-8]

按照以下步骤启用视频模块：

1. 调用 `enableVideo` 方法，启用视频模块。
2. 调用 `startPreview` 方法，开启本地视频预览。

```dart
// 启用视频模块
await _engine.enableVideo();
// 开启本地预览
await _engine.startPreview();
```

### 加入频道并发布音视频流 [#加入频道并发布音视频流-8]

调用 `joinChannel` 加入频道。在 `ChannelMediaOptions` 中进行如下配置：

- 设置用户角色为 `clientRoleBroadcaster`（主播）或 `clientRoleAudience`（观众）。
- 将 `publishMicrophoneTrack` 和 `publishCameraTrack` 设置为 `true`，发布麦克风采集的音频和摄像头采集的视频。
- 将 `autoSubscribeAudio` 和 `autoSubscribeVideo` 设置为 `true`，自动订阅所有音视频流。

```dart
await _engine.joinChannel(
  // 使用临时 Token 和频道名加入频道
  token: token,
  channelId: channel,
  options: const ChannelMediaOptions(
    // 自动订阅所有视频流
    autoSubscribeVideo: true,
    // 自动订阅所有音频流
    autoSubscribeAudio: true,
    // 发布摄像头采集的视频
    publishCameraTrack: true,
    // 发布麦克风采集的音频
    publishMicrophoneTrack: true,
    // 设置用户角色为 clientRoleBroadcaster（主播）或 clientRoleAudience（观众）
    clientRoleType: ClientRoleType.clientRoleBroadcaster),
  // uid 为 0 表示由引擎内部随机生成用户 ID
  uid: 0,
);
```

### 实现常用回调 [#实现常用回调-8]

根据使用场景，定义必要的回调。以下示例代码展示如何调用 `registerEventHandler` 添加并实现 `onJoinChannelSuccess`、 `onUserJoined` 和 `onUserOffline` 回调。

```dart
// 添加回调事件
_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;
      });
    },
  ),
);
```

### 设置远端视图 [#设置远端视图-4]

当远端用户加入频道时，触发 `onUserJoined` 回调。通过该回调获取远端用户的 `uid` 后，创建一个 `AgoraVideoView` 控件，用于显示对应远端用户的视频流。

```dart
// 生成远端视频
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,
    );
  }
}
```

### 开始音视频互动 [#开始音视频互动-6]

在 `initState()` 方法中初始化声网音视频功能并开始实时音视频互动。

```dart
@override
void initState() {
  super.initState();
  initAgora();
}
```

### 结束音视频互动 [#结束音视频互动-6]

按照以下步骤结束音视频互动：

1. 调用 `leaveChannel` 离开当前频道，释放所有会话相关的资源。
2. 调用 `release` 销毁引擎，并释放声网 SDK 中使用的所有资源。
    :::warning[注意]
    - 调用 `release` 后，你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能，你必须重新创建一个新的引擎。详见[初始化引擎](#初始化引擎-8)。
        - 该方法为同步调用。需要等待引擎资源释放后才能执行其他操作，因此建议在子线程中调用该方法，避免主线程阻塞。
    :::

```dart
Future<void> _dispose() async {
  await _engine.leaveChannel(); // 离开频道
  await _engine.release(); // 释放资源
}
```

## 调试 App [#调试-app-4]

按照以下步骤测试直播 App：

1. 将目标设备连接到电脑。

2. 打开终端，在项目路径下执行以下命令在目标设备上运行示例项目：

    ```shell
    flutter run
    ```

3. 启动 App，授予麦克风和摄像头权限，如果你将用户角色设置为主播，便会在本地视图中看到自己。

4. 使用第二台设备，重复以上步骤，在该设备上安装 App、打开 App 加入频道，观察测试结果：
    - 如果两台设备均作为主播加入频道，则可以看到对方并且听到对方的声音。
    - 如果两台设备分别作为主播和观众加入，则主播可以在本地视频窗口看到自己；观众可以在远端视频窗口看到主播、并听到主播的声音。

  以 Android 设备为例，编译成功后的 App 如下图所示：

<DocsImage alt="Flutter 加入频道后的音视频互动界面" src="https://web-cdn.agora.io/docs-portal/img/rtc/flutter-quick-start-inchannel.png" displayWidth="30%" />

## 后续步骤 [#后续步骤-7]

- 本文的示例使用了临时 Token 加入频道。在测试或生产环境中，为确保通信安全，声网推荐使用 Token 服务器来生成 Token，详见[使用 Token 鉴权](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)。
- 如果你想要实现极速直播场景，可以在互动直播的基础上，通过修改观众端的延时级别为低延时 (`audienceLatencyLevelLowLatency`)实现。详见[实现极速直播](/zh-CN/realtime-media/rtc/build/advanced-channel/ls-quick-start)。

## 参考信息 [#参考信息-9]

### 示例项目 [#示例项目-9]

声网提供了开源的实时音视频互动示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">join_channel_video</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/Agora-Flutter-SDK/tree/main/example/lib/examples/basic/join_channel_video" />
  <Card title={<RepositoryCardTitle provider="github">join_channel_video</RepositoryCardTitle>} href="https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples/basic/join_channel_video" />
</Cards>

### 常见问题 [#常见问题-8]

- [如何解决 Flutter 上 iOS release 包初始化时报 symbol not found 的问题？](/zh-CN/reference/faq/integration/flutter_ios_build)
- [直播场景下，如何监听远端观众角色用户加入/离开频道的事件？](/zh-CN/reference/faq/integration/audience_event)
- [如何处理视频黑屏问题？](/zh-CN/reference/faq/quality/video_blank)
- [为什么我无法打开摄像头？](/zh-CN/reference/faq/quality/video_camera)
- [如何处理频道相关常见问题？](/zh-CN/reference/faq/integration/channel)
- [如何设置日志文件？](/zh-CN/reference/faq/integration/set_log_file)
- [为什么部分 Android 版本应用锁屏或切后台后采集音视频无效？](/zh-CN/reference/faq/quality/android_background)
- [编译 Xcode 项目时遇到无法打开 framework 的弹窗警告怎么办？](/zh-CN/reference/faq/integration/framework_cannot_be_opened)
- [为什么 SDK 中使用的是动态库而不是静态库？](/zh-CN/reference/faq/integration/dynamic_or_static_library)

### 相关文档 [#相关文档-8]

- [SDK 错误码](/zh-CN/realtime-media/rtc/reference/error-code)
- [频道连接状态管理](/zh-CN/realtime-media/rtc/build/initialize-and-channel/channel-connection)








