本文将介绍如何使用 TRTC Flutter SDK(
tencent_rtc_sdk)快速实现一个基础的音视频通话。支持平台
平台 | Android | iOS | HarmonyOS | Windows | macOS |
是否支持 | | | | | |
前提条件
开通服务
1. 登录 TRTC 控制台,单击创建应用。如果您已经完成创建,可以跳过该操作。
2. 在创建应用成功后,您可以在应用管理中获取到您的 SDKAppID 和 SDK 密钥。


SDKAppID:控制台创建的 TRTC 应用的唯一标识。
SDK 密钥:用于计算
UserSig(用户进入音视频房间的鉴权凭证)。获取 UserSig
调试阶段:可以在控制台辅助工具中的 UserSig 生成工具里输入
userId 生成临时 UserSig,也可以参见 用户鉴权 在本地计算。正式环境:UserSig 必须由您的业务服务端计算并下发给客户端。
注意:
请勿将 SDK 密钥写入客户端代码或打包进 App,否则密钥可能被反编译窃取,导致流量被盗用。
环境准备
Flutter 3.22 及以上版本。
Android 端开发:
Android Studio 最新稳定版本。
App 要求 Android 5.0(API 级别 21)及以上版本设备。
iOS 端开发:
Xcode 15 及以上版本,App 要求 iOS 12.0 及以上版本设备。
请确保您的项目已设置有效的开发者签名。
Windows 端开发:
Visual Studio 2022 及以上版本,并勾选“使用 C++ 的桌面开发”工作负载。
macOS 端开发:
Xcode 15 及以上版本,App 要求 macOS 10.14 及以上版本。
HarmonyOS 端开发:
DevEco Studio 5.0 及以上版本,HarmonyOS SDK API 12(5.0.0)及以上。
鸿蒙版 Flutter SDK:官方 Flutter SDK 不支持 HarmonyOS,需要使用 OpenHarmony 社区维护的 flutter_flutter,克隆后切换到与您 Flutter 版本对应的
oh-* 分支,并按仓库 README 配置 DevEco Studio 相关环境变量,通过 flutter doctor -v 确认已识别 ohos 工具链。普通
flutter create 生成的工程没有 ohos/ 目录,请使用鸿蒙版 Flutter SDK 在工程根目录执行 flutter create --platforms ohos . 生成。步骤1:导入 SDK
1. 通过以下命令安装组件 tencent_rtc_sdk:
flutter pub add tencent_rtc_sdk
2. 在 Android / HarmonyOS 上申请麦克风和相机运行时权限,需要增加依赖 permission_handler:
flutter pub add permission_handler
1. 打开
pubspec.yaml 文件,添加 tencent_rtc_sdk(最新版本可以查询 https://pub.dev/packages/tencent_rtc_sdk)和 permission_handler 依赖:dependencies:# TRTC Flutter SDK 依赖项,请替换为 pub.dev 上的最新版本tencent_rtc_sdk: ^<最新版本># 权限处理插件依赖项permission_handler: ^11.0.0
2. 添加依赖并保存文件后,打开终端,在包含
pubspec.yaml 的 Flutter 工程根目录下执行如下命令:flutter pub get
说明:
HarmonyOS 工程需要使用适配 OpenHarmony 的 permission_handler,请在
pubspec.yaml 中按如下方式引入:permission_handler:git:url: "https://gitcode.com/openharmony-sig/flutter_permission_handler.git"path: "permission_handler"
步骤2:配置项目
增加麦克风和相机权限说明
1. 需要在
ios/Runner/Info.plist 的第一级<dict>目录下加入相机和麦克风的权限说明。SDK 首次访问相机和麦克风时,系统会自动弹出授权框:<key>NSCameraUsageDescription</key><string>授权摄像头权限才能正常视频通话</string><key>NSMicrophoneUsageDescription</key><string>授权麦克风权限才能正常语音通话</string>
2. (可选)如果您希望在 iOS 上也使用 permission_handler 主动申请权限,需要开启对应权限的编译宏,否则
Permission.camera.request() 会直接返回拒绝且不会弹出系统授权弹窗。请将以下内容合并到 ios/Podfile 已有的 post_install 中,不要重复声明 post_install:post_install do |installer|installer.pods_project.targets.each do |target|flutter_additional_ios_build_settings(target)target.build_configurations.each do |config|config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)',## permission_handler: 启用摄像头权限'PERMISSION_CAMERA=1',## permission_handler: 启用麦克风权限'PERMISSION_MICROPHONE=1',]endendend
配置 Release 符号裁剪(必需)
SDK 通过 Dart FFI 在运行时按名称查找 TRTC 的 C 符号。Xcode 打 Release 包时默认会裁掉这些全局符号,导致 Debug 正常、Release 运行时报
symbol not found。请在 Xcode 中选择 Runner > TARGETS > Runner > Build Settings,将 Release 配置下的以下两项修改为:Deployment Postprocessing:
YesStrip Style:
Non-Global Symbols增加麦克风和相机权限申请
1. 打开
android/app/src/main/AndroidManifest.xml 文件。2. 在其中添加如下权限:
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.CAMERA" /><!-- 蓝牙耳机支持:Android 11 及以下使用 BLUETOOTH,Android 12 及以上使用 BLUETOOTH_CONNECT --><uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
解决 so 冲突
如果工程中有多个依赖都包含
libliteavsdk.so,编译时会报 so 重复。请在 android/app/build.gradle(或 build.gradle.kts)中添加:// build.gradle(Groovy)android {packagingOptions {pickFirst 'lib/**/libliteavsdk.so'}}
// build.gradle.kts(Kotlin DSL)android {packaging {jniLibs {pickFirsts += "lib/**/libliteavsdk.so"}}}
混淆配置
如果 release 包开启了混淆(
minifyEnabled true),请在 android/app/proguard-rules.pro 中添加如下规则,并确保 buildTypes.release 的 proguardFiles 引用了该文件:-keep class com.tencent.** { *; }
增加麦克风和相机权限说明
需要在
macos/Runner/Info.plist 的第一级<dict>目录下加入相机和麦克风的权限说明:<key>NSCameraUsageDescription</key><string>授权摄像头权限才能正常视频通话</string><key>NSMicrophoneUsageDescription</key><string>授权麦克风权限才能正常语音通话</string>
配置沙盒权限(必需)
Flutter macOS 工程默认开启 App Sandbox,请在
macos/Runner/DebugProfile.entitlements 和 macos/Runner/Release.entitlements 两个文件中都添加以下内容,否则 App 无法访问摄像头、麦克风和网络:<key>com.apple.security.device.camera</key><true/><key>com.apple.security.device.audio-input</key><true/><key>com.apple.security.network.client</key><true/>
添加屏幕分享系统依赖库(可选,仅使用屏幕分享时需要,要求 macOS 12.3 及以上)
1. 打开
.xcworkspace 工程文件后,在 Xcode 的导航栏中点击左侧的 Project Navigator,点击 Runner 并确保在编辑区域选择正确的 TARGETS。2. 在
General 选项卡的 Frameworks, Libraries, and Embedded Content 部分添加 ScreenCaptureKit.framework。
无需额外配置。
增加麦克风和相机权限申请
在
ohos/entry/src/main/module.json5 的 module 节点下添加 requestPermissions。以下 reason 复用了默认工程 ohos/entry/src/main/resources/base/element/string.json 中已有的 module_desc,可直接编译:"requestPermissions": [{ "name": "ohos.permission.INTERNET" },{"name": "ohos.permission.CAMERA","reason": "$string:module_desc","usedScene": { "abilities": [], "when": "inuse" }},{"name": "ohos.permission.MICROPHONE","reason": "$string:module_desc","usedScene": { "abilities": [], "when": "inuse" }}]
说明:
reason 引用的字符串必须在 string.json 中存在,否则编译失败。如需为相机和麦克风分别展示授权理由,请在 ohos/entry/src/main/resources/base/element/string.json 的 string 数组中新增条目(如 { "name": "camera_reason", "value": "用于视频通话" }),再将上面的 reason 改为 $string:camera_reason。步骤3:创建 TRTC 实例并注册事件监听
1. 引入头文件并声明成员变量:
import 'package:tencent_rtc_sdk/trtc_cloud.dart';import 'package:tencent_rtc_sdk/trtc_cloud_def.dart';import 'package:tencent_rtc_sdk/trtc_cloud_listener.dart';
late TRTCCloud trtcCloud;late TRTCCloudListener listener;
2. 在异步方法中(例如在
initState 中调用的初始化方法)创建 TRTC 实例并注册事件监听。建议在进房前一次性注册所有需要的回调:Future<void> initTRTC() async {// 创建 TRTC 实例(单例)trtcCloud = await TRTCCloud.sharedInstance();// 创建 TRTCCloudListener 实例,根据需要实现对应的回调listener = TRTCCloudListener(// SDK 不可恢复的错误,如进房失败、设备打开失败等onError: (errCode, errMsg) {debugPrint('onError: $errCode, $errMsg');},// 进房结果:result > 0 表示进房成功(值为耗时,单位 ms),result < 0 为错误码onEnterRoom: (result) {debugPrint(result > 0 ? '进房成功' : '进房失败: $result');},// 远端用户开启 / 关闭视频,详见步骤7onUserVideoAvailable: (userId, available) {onRemoteVideoChanged(userId, available);},// 退房完成,详见步骤9onExitRoom: (reason) {releaseTRTC();},);// 注册监听器,将 listener 绑定到 trtcCloud 实例trtcCloud.registerListener(listener);}
步骤4:进入房间
1. 在 Android 和 HarmonyOS 设备上,需要在进房前主动申请 CAMERA 和 MICROPHONE 权限;iOS 和 macOS 会在 SDK 首次访问设备时由系统自动弹出授权框。
import 'dart:io';import 'package:permission_handler/permission_handler.dart';Future<bool> requestPermissions() async {if (!(Platform.isAndroid || TRTCPlatform.isOhos)) return true;final statuses = await [Permission.camera, Permission.microphone].request();return statuses.values.every((status) => status.isGranted);}
说明:
3. 设置进房参数 TRTCParams 后,调用
enterRoom 即可进入房间。参数 | 类型 | 描述 |
sdkAppId | int | |
userId | String | 您指定的用户 ID,同一房间内不能重复。 |
userSig | String | |
strRoomId / roomId | String / int | 房间号,二选一。两者同时填写时以 roomId 为准。roomId 取值范围为 1~4294967294。strRoomId 最长 64 字节,仅支持以下字符:大小写英文字母(a-z、A-Z)、数字(0-9)、空格及 ! # $ % & ( ) + - : ; < = . > ? @ [ ] ^ _ { } | ~ ,,包含中文等其他字符会导致进房失败。 |
scene | TRTCAppScene | 应用场景, enterRoom 的第二个参数。音视频通话请使用 videoCall / audioCall;互动直播请使用 live / voiceChatRoom,并通过 TRTCParams.role 指定主播或观众角色。 |
trtcCloud.enterRoom(TRTCParams(sdkAppId: sdkAppId, // 请替换为您的 SDKAppIDuserId: userId, // 请替换为您的 userIduserSig: userSig, // 请替换为该 userId 对应的 UserSigstrRoomId: roomId, // 请替换为您的房间号),TRTCAppScene.videoCall,);
说明:
多个客户端互通,需要使用相同的
sdkAppId、相同的房间号和不同的 userId(不支持多端使用相同的 userId 同时进入同一个音视频房间)。同一个
sdkAppId 下请统一使用数字房间号或字符串房间号,不要混用:数字 123 和字符串 "123" 是两个不同的房间。enterRoom 必须与 exitRoom 成对调用。步骤5:打开摄像头
1. 在页面的
build 方法中添加本地视频渲染控件 TRTCCloudVideoView,并在 onViewCreated 回调中拿到 viewId 后再调用 startLocalPreview 开启摄像头预览:import 'package:tencent_rtc_sdk/trtc_cloud_video_view.dart';
TRTCCloudVideoView(key: const ValueKey('local'),onViewCreated: (viewId) {// true:前置摄像头;false:后置摄像头(二选一,仅移动端有效)trtcCloud.startLocalPreview(true, viewId);},),
说明:
viewId 为视频渲染控件 TRTCCloudVideoView 的唯一标识符,只有在 onViewCreated 回调之后才可用,因此 startLocalPreview 需在该回调中(或之后)调用。请在
TRTCCloud.sharedInstance() 完成之后再构建 TRTCCloudVideoView。SDK 会在实例创建时确定渲染方式(macOS / Windows 使用纹理渲染),过早创建的控件可能无法正常显示画面。2. (可选)在调用
startLocalPreview 打开摄像头预览之前,您可以通过调用 setLocalRenderParams 设置本地预览的渲染参数:参数 | 参数说明 | 取值 | 取值说明 |
图像的顺时针旋转角度。支持 90、180 以及 270 旋转角度,默认值:TRTCVideoRotation.rotation0。 | 不旋转。 | ||
| | 顺时针旋转90度。 | |
| | 顺时针旋转180度。 | |
| | 顺时针旋转270度。 | |
画面填充模式。填充(画面可能会被拉伸裁剪)或适应(画面可能会有黑边),默认值:TRTCVideoFillMode.fill。 | 填充模式:即将画面内容居中等比缩放以充满整个显示区域,超出显示区域的部分将会被裁剪掉,此模式下画面可能不完整。 | ||
| | 适应模式:即按画面长边进行缩放以适应显示区域,短边部分会被填充为黑色,此模式下图像完整但可能留有黑边。 | |
| | 缩放填充模式:即无论画面的宽高比,都会被拉伸或压缩以完全填充显示区域,此模式下画面宽高比可能会被改变,导致渲染画面变形。 | |
画面镜像模式。默认值:TRTCVideoMirrorType.auto。 | 自动模式:如果正使用前置摄像头则开启镜像,如果是后置摄像头则不开启镜像(仅适用于移动设备)。 | ||
| | 强制开启镜像,不论当前使用的是前置摄像头还是后置摄像头。 | |
| | 强制关闭镜像,不论当前使用的是前置摄像头还是后置摄像头。 | |
// 设置本地预览渲染参数trtcCloud.setLocalRenderParams(TRTCRenderParams(fillMode: TRTCVideoFillMode.fill,mirrorType: TRTCVideoMirrorType.auto,rotation: TRTCVideoRotation.rotation0,),);
调用
stopLocalPreview 关闭摄像头预览并停止推送本地视频。trtcCloud.stopLocalPreview();
3. (仅移动端)您可以通过
TXDeviceManager 类实现“切换前后摄像头”、“设置对焦模式”、“闪光灯”等设备扩展功能。import 'package:tencent_rtc_sdk/tx_device_manager.dart';
// 获取设备管理器实例TXDeviceManager manager = trtcCloud.getDeviceManager();// 切换前后摄像头:当前为前置则切到后置,反之切到前置manager.switchCamera(!manager.isFrontCamera());
// 获取设备管理器实例TXDeviceManager manager = trtcCloud.getDeviceManager();// 如果设备支持自动对焦(人脸自动识别),则开启自动对焦if (manager.isAutoFocusEnabled()) {manager.enableCameraAutoFocus(true);}
// 获取设备管理器实例TXDeviceManager manager = trtcCloud.getDeviceManager();// 开启闪光灯(仅在使用后置摄像头时有效)manager.enableCameraTorch(true);// 关闭闪光灯manager.enableCameraTorch(false);
步骤6:打开麦克风
调用
startLocalAudio 开启麦克风采集,请根据您的需求选择以下的一个音质参数:参数 | 描述 |
人声模式:单声道;编码码率:16kbps;具备几个模式中最强的弱网抗性,适合语音通话为主的场景,例如在线会议、语音通话等。 | |
默认模式:单声道;编码码率:50kbps;介于 speech 和 music 之间的档位,SDK 默认档位,推荐选择。 | |
音乐模式:全频带立体声;编码码率:128kbps;适合需要高保真传输音乐的场景,例如在线 K 歌、音乐直播等。 |
// 开启麦克风采集,使用默认音质(推荐)trtcCloud.startLocalAudio(TRTCAudioQuality.defaultMode);
// 以语音为主的场景(如在线会议),可使用人声模式,获得更强的弱网抗性trtcCloud.startLocalAudio(TRTCAudioQuality.speech);
调用
stopLocalAudio 关闭麦克风采集并停止推送本地音频。trtcCloud.stopLocalAudio();
步骤7:播放/停止视频流
1. 在步骤3注册的监听中处理
onUserVideoAvailable 回调:收到 onUserVideoAvailable(userId, true) 时,表示该用户的画面已有可播放的视频帧到达,此时为该用户添加渲染控件;收到 false 时停止播放并移除控件。final List<String> remoteUsers = [];void onRemoteVideoChanged(String userId, bool available) {if (!mounted) return;setState(() {if (available) {if (!remoteUsers.contains(userId)) remoteUsers.add(userId);} else {remoteUsers.remove(userId);trtcCloud.stopRemoteView(userId, TRTCVideoStreamType.big);}});}
2. 在
build 方法中为每个远端用户创建 TRTCCloudVideoView,并在 onViewCreated 回调中调用 startRemoteView 播放该用户的视频画面:for (final userId in remoteUsers)TRTCCloudVideoView(key: ValueKey('remote_$userId'),onViewCreated: (viewId) {// 播放远端用户的主路(大画面)视频trtcCloud.startRemoteView(userId, TRTCVideoStreamType.big, viewId);},),
3. 您也可以通过
stopAllRemoteView 停止播放所有远端用户的视频。// 停止播放所有远端用户的视频画面trtcCloud.stopAllRemoteView();
步骤8:播放/停止音频流
默认情况下,SDK 会自动播放远端用户的音频,您无需调用任何 API。
如需静音或恢复远端声音,可以调用
muteRemoteAudio / muteAllRemoteAudio:// 静音指定远端用户(remoteUserId 为远端用户的 userId)trtcCloud.muteRemoteAudio(remoteUserId, true);// 静音所有远端用户trtcCloud.muteAllRemoteAudio(true);
// 取消静音指定远端用户trtcCloud.muteRemoteAudio(remoteUserId, false);// 取消静音所有远端用户trtcCloud.muteAllRemoteAudio(false);
步骤9:退出房间
调用
exitRoom 退出当前房间:trtcCloud.stopLocalPreview();trtcCloud.stopLocalAudio();trtcCloud.exitRoom();
TRTC SDK 会在退房结束后通过步骤3中注册的
onExitRoom 回调通知您,您可以在该回调中注销监听并销毁实例:void releaseTRTC() {trtcCloud.unRegisterListener(listener);TRTCCloud.destroySharedInstance();}
注意:
exitRoom 是异步的,SDK 会在释放摄像头、麦克风等设备资源后回调 onExitRoom。如需再次进房,请在收到 onExitRoom 后再调用 enterRoom,否则可能出现设备被占用等问题。步骤10:完整示例
以下是一个可直接运行的单页面示例。请将顶部常量替换为您自己的信息,然后在两台设备上使用不同的
userId 进入同一房间。import 'dart:io';import 'package:flutter/material.dart';import 'package:permission_handler/permission_handler.dart';import 'package:tencent_rtc_sdk/trtc_cloud.dart';import 'package:tencent_rtc_sdk/trtc_cloud_def.dart';import 'package:tencent_rtc_sdk/trtc_cloud_listener.dart';import 'package:tencent_rtc_sdk/trtc_cloud_video_view.dart';const int sdkAppId = 0; // 请替换为您的 SDKAppIDconst String userId = 'user_a'; // 请替换为您的 userIdconst String userSig = ''; // 请替换为该 userId 对应的 UserSigconst String roomId = '123abc'; // 请替换为您的房间号void main() => runApp(const MaterialApp(home: VideoCallPage()));class VideoCallPage extends StatefulWidget {const VideoCallPage({super.key});@overrideState<VideoCallPage> createState() => _VideoCallPageState();}class _VideoCallPageState extends State<VideoCallPage> {TRTCCloud? trtcCloud;late final TRTCCloudListener listener;final List<String> remoteUsers = [];@overridevoid initState() {super.initState();listener = TRTCCloudListener(onError: (errCode, errMsg) => debugPrint('onError: $errCode, $errMsg'),onEnterRoom: (result) =>debugPrint(result > 0 ? '进房成功' : '进房失败: $result'),onUserVideoAvailable: onRemoteVideoChanged,onExitRoom: (reason) {trtcCloud?.unRegisterListener(listener);TRTCCloud.destroySharedInstance();},);start();}Future<void> start() async {if (!await requestPermissions()) {debugPrint('未获得相机或麦克风权限');return;}final cloud = await TRTCCloud.sharedInstance();cloud.registerListener(listener);cloud.enterRoom(TRTCParams(sdkAppId: sdkAppId,userId: userId,userSig: userSig,strRoomId: roomId,),TRTCAppScene.videoCall,);cloud.startLocalAudio(TRTCAudioQuality.defaultMode);if (mounted) setState(() => trtcCloud = cloud);}Future<bool> requestPermissions() async {if (!(Platform.isAndroid || TRTCPlatform.isOhos)) return true;final statuses = await [Permission.camera, Permission.microphone].request();return statuses.values.every((status) => status.isGranted);}void onRemoteVideoChanged(String uid, bool available) {if (!mounted) return;setState(() {if (available) {if (!remoteUsers.contains(uid)) remoteUsers.add(uid);} else {remoteUsers.remove(uid);trtcCloud?.stopRemoteView(uid, TRTCVideoStreamType.big);}});}@overridevoid dispose() {trtcCloud?.stopLocalPreview();trtcCloud?.stopLocalAudio();trtcCloud?.exitRoom();super.dispose();}@overrideWidget build(BuildContext context) {final cloud = trtcCloud;return Scaffold(appBar: AppBar(title: const Text('Room: $roomId')),body: cloud == null? const Center(child: CircularProgressIndicator()): GridView.count(crossAxisCount: 2,children: [TRTCCloudVideoView(key: const ValueKey('local'),onViewCreated: (viewId) =>cloud.startLocalPreview(true, viewId),),for (final uid in remoteUsers)TRTCCloudVideoView(key: ValueKey('remote_$uid'),onViewCreated: (viewId) => cloud.startRemoteView(uid, TRTCVideoStreamType.big, viewId),),],),floatingActionButton: FloatingActionButton(backgroundColor: Colors.red,onPressed: () => Navigator.of(context).maybePop(),child: const Icon(Icons.call_end),),);}}
常见问题
说明: