Flutter

最近更新时间:2025-07-29 14:30:52

我的收藏

业务流程

本节汇总了电商直播场景中一些常见的业务流程,帮助您更好地理解整个场景的实现流程。
主播开播及关播
主播跨房 PK 连麦
RTC 观众进房连麦
CDN 观众进房连麦
带货商品管理
下图展示了主播(房主)本地预览、创建房间、进房开播、退房关播的流程。



下图展示了主播 A 邀请主播 B 进行跨房 PK 连麦的流程。跨房 PK 过程中,两个房间内的观众都可以看到两个房主 PK 连麦直播的画面。



下图展示了 RTC 实时互动直播间观众进入房间、申请连麦、结束连麦、退出房间的流程。



下图展示了 RTC 旁路直播间 CDN 观众进入房间、申请连麦、结束连麦、退出房间的流程。



下图展示了直播带货场景中主播编辑、上架商品,观众浏览、购买商品的流程。




接入准备

步骤1:开通服务

电商直播场景通常需要依赖腾讯云 实时音视频 TRTC腾讯美颜特效播放器 SDK 等付费 PaaS 服务构建。其中,TRTC 负责提供实时音视频互动能力,腾讯特效负责提供美颜特效能力,播放器负责提供直播和点播播放能力。您可根据实际业务需求自由选择开通上述服务。
开通 TRTC 服务
开通腾讯特效服务
开通播放器服务
1. 首先,您需要登录 实时音视频 TRTC 控制台 > 应用管理 创建应用,您可根据需要选择升级 TRTC 应用版本,例如旗舰版可解锁更多增值功能服务。

说明:
建议创建两个应用分别用于测试环境和生产环境,首次开通 TRTC 服务可前往 试用中心 免费领取 10000 分钟试用时长包。
TRTC 包月套餐(入门版、基础版、尊享版、旗舰版)可以解锁不同的增值功能服务,详情可见 包月套餐说明
2. 应用创建完毕之后,您可以在 应用管理应用概览栏目看到该应用的基本信息,其中需要您保管好 SDKAppIDSDK密钥便于后续使用,同时应避免密钥泄露造成流量盗刷。

1. 您需要登录 腾讯云视立方控制台 > License 管理 > 移动端 License,单击新建测试 License,填写基本信息。
测试版 License 免费测试有效期为14天,可续期一次共28天。根据实际需求填写 App NamePackage NameBundle ID,选择腾讯特效,选择所需测试的能力:高级套餐 S1-07原子能力 X1-01原子能力 X1-02原子能力 X1-03,并补充资质,准确填写公司名称、所属行业类型,上传公司营业执照,单击确定提交审核申请,等待人工审核流程。



2. 测试版 License 成功创建后,页面会显示生成的 License 信息。此时 Key 和 LicenseURL 两个参数暂未生效,等待提交审核通过后方可生效使用。在 SDK 初始化配置时需要传入 License URL 和 License Key 两个参数,请妥善保存以下信息



若您想在已创建的测试应用中申请测试播放器功能,则需选择您想测试的应用,单击测试新功能。


2. 根据实际需求填写 App NamePackage NameBundle ID,选择播放器(高级版),单击确定



3. 测试版 License 成功创建后,页面会显示已生成的 License 信息。在 SDK 初始化配置时需要传入 License Key 和 License URL 两个参数,请妥善保存以下信息。



注意:
同一应用的 License URL 和 Key 唯一,测试版 License 升级为正式版后 License URL 和 Key 不变。
测试版 License 有效期内可单击右侧的编辑,进入修改 Bundle ID 和 Package Name 信息,单击确定即可保存。

步骤2:SDK 集成

该步骤介绍如何完成 Flutter RTC Engine 的接入。

环境准备

Flutter 2.0及以上版本。
Android 端开发:
Android Studio 3.5及以上版本。
App 要求 Android 4.1及以上版本设备。
请确保您的项目支持 CMake 3.13及以上版本
iOS 端开发:
Xcode 11.0及以上版本。
osx 系统版本要求10.11及以上版本。
请确保您的项目已设置有效的开发者签名。

Flutter 端集成

方式一:
远程依赖:在您工程的 pubspec.yaml 文件中添加如下引用,详细版本可以查看 tencent_rtc_sdk
dependencies:
tencent_rtc_sdk: ^12.5.4
live_flutter_plugin: 12.5.0
super_player:
git:
url: https://github.com/LiteAVSDK/Player_Flutter
path: Flutter
ref: release_pro_v12.0.0
tencent_effect_flutter:
git:
url: https://github.com/Tencent-RTC/TencentEffect_Flutter
方式二:
本地依赖:从 tencent_effect_flutter 下载最新版本的 tencent_effect_flutter,然后把文件夹 android、ios、lib 和文件 pubspec.yaml、tencent_effect_flutter.iml 添加到工程目录下,然后在工程的 pubspec.yaml 文件中添加如下引用:(可参考 demo)。
dependencies:
tencent_rtc_sdk: ^12.5.4
live_flutter_plugin: 12.5.0
super_player:
git:
url: https://github.com/LiteAVSDK/Player_Flutter
path: Flutter
ref: release_pro_v12.0.0
tencent_effect_flutter:
path: ../
执行如下命令:
flutter pub get
注意:
tencent_effect_flutter 只是提供一个桥接,美颜功能依赖各个平台提供的美颜SDK,所以如果需要更新美颜 SDK,您可以通过以下步骤进行 SDK 升级。
Android
iOS
Android 在您的工程下的 app 模块下找到 build.gradle 文件,将 implementation 'com.tencent.mediacloud:TencentEffect_S1-04:latest.release' 中的 latest.release 字段修改为最新的版本号。
如果您需要更换套餐,那么需要将 TencentEffect_S1-04字段修改为您的套餐字段 。
需要更换套餐类型和 SDK 版本:
1. 美颜 Flutter SDK 的依赖方式需要修改为本地依赖。
2. 在美颜 Flutter SDK 下找到 ios/tencent_effect_flutter.podspec 文件,打开找到 TencentEffect_All 修改为您需要的套餐,后边的版本号也可以修改为您需要的版本号。
3. 在您工程的 ios 目录下执行 pod update 命令即可。

原生端集成

原生部分集成步骤如下,美颜特效 demo,可以参考 美颜特效 Demo 下载
Android
iOS
1. 在 app 模块下找到 build.gradle 文件,添加您对应套餐的 maven 引用地址,例如您选择的是S1-04套餐,则添加如下:
dependencies {
implementation 'com.tencent.mediacloud:TencentEffect_S1-04:latest.release'
}
各套餐对应的 maven 地址,请参见 文档,SDK 的最新版本号可以在 版本历史 中查看。
2. 在您工程下的 android/app 模块下找到 src/main/assets 文件夹,将 demo 工程中 demo/android/app/src/main/assets 中的 lut 和 MotionRes 复制到您工程的 android/app/ src/main/assets 中,如果您的工程没有 assets 文件夹可以手动创建一个。
3. 如果您使用的 Android 版美颜 SDK 小于3.9版本,您需要在 app 模块下找到 AndroidManifest.xml 文件,在 application 表单内添加如下标签:
<uses-native-library
android:name="libOpenCL.so"
android:required="false" />
//true 表示libOpenCL是当前app必需的。如果没有此库,系统将不允许app安装
//false 表示libOpenCL不是当前app必需的。无论有没有此库,都可以正常安装app。如果设备有此库,美颜特效SDK里的GAN类型特效能正常生效(例如童话脸、国漫脸)。如果设备没有此库,GAN类型不会生效,但也不影响SDK内其他功能的使用。
//关于uses-native-library的说明,请参考Android 官网介绍:https://developer.android.com/guide/topics/manifest/uses-native-library-element
添加后如下图:

美颜特效 Demo 工程中 ios/Runner 目录下的 xmagic 文件夹复制到您工程中 ios/Runner 目录下,添加后如下图:

注意:
上述从 美颜特效 Demo 工程中复制的素材是测试素材,正式素材需要您在购买套餐之后 联系我们 进行获取并重新添加。

步骤3:工程配置

Android
iOS
1. 权限配置。
在 AndroidManifest.xml 中配置 App 权限,电商直播场景下 LiteAVSDK 及腾讯特效 SDK 需要以下权限。
<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.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />
注意:
请勿设置 android:hardwareAccelerated="false",关闭硬件加速之后,会导致对方的视频流无法渲染。
LiteAVSDK 没有内置权限申请逻辑,需要您自行声明相应的权限,部分权限(如存储、录音、相机等)还需要在运行时动态申请。
若 Android 项目 targetSdkVersion 为 31 或者目标设备涉及到 Android 12 及更高系统版本,官方要求需要在代码中动态申请 android.permission.BLUETOOTH_CONNECT 权限,以正常使用蓝牙功能,具体信息请参见 Android 官方说明
2. 混淆配置。
由于我们在 SDK 内部使用了 Java 的反射特性,需要您在 proguard-rules.pro 文件中将 SDK 相关类加入不混淆名单。
如果您在打 release 包时,启用了编译优化(把 minifyEnabled 设置为 true),会裁掉一些未在 Java 层调用的代码,而这些代码有可能会被 native 层调用,从而引起 no xxx method 的异常。
如果您启用了这样的编译优化,那就要添加这些 keep 规则,防止 xmagic 的代码被裁掉:
-keep class com.tencent.** { *; }
-keep class org.light.** { *;}
-keep class org.libpag.** { *;}
-keep class org.extra.** { *;}
-keep class com.gyailib.**{ *;}
-keep class androidx.exifinterface.** { *;}
1. 权限配置。
电商直播场景下 LiteAVSDK 及腾讯特效 SDK 需要以下权限。在 App 的 Info.plist 中添加以下两项,分别对应麦克风和摄像头在系统弹出授权对话框时的提示信息。
Privacy - Microphone Usage Description,并填入麦克风使用目的提示语。
Privacy - Camera Usage Description,并填入摄像头使用目的提示语。



2. 如需 App 进入后台仍然运行相关功能,可在 XCode 中选中当前工程项目,并在 Capabilities 下将设置项 Background Modes 设定为 ON,并勾选 Audio, AirPlay, and Picture in Picture,如下图所示:




步骤4:鉴权与许可

TRTC 鉴权凭证
腾讯特效鉴权许可
UserSig 是腾讯云设计的一种安全保护签名,目的是为了阻止恶意攻击者盗用您的云服务使用权,TRTC 在进房时校验该鉴权凭证。
调试跑通阶段:可以通过 客户端示例代码控制台 两种方法计算生成 UserSig,仅用于调试测试。
正式运行阶段:推荐安全等级更高的服务端计算 UserSig 方案,防止客户端被逆向破解泄露密钥。
具体实现流程如下:
1. 您的 App 在调用 SDK 的初始化函数之前,首先要向您的服务器请求 UserSig。
2. 您的服务器根据 SDKAppID 和 UserID 计算 UserSig。
3. 服务器将计算好的 UserSig 返回给您的 App。
4. 您的 App 将获得的 UserSig 通过特定 API 传递给 SDK。
5. SDK 将 SDKAppID + UserID + UserSig 提交给腾讯云服务器进行校验。
6. 腾讯云校验 UserSig,确认合法性。
7. 校验通过后,会向 TRTC SDK 提供实时音视频服务。



注意:
调试跑通阶段的本地 UserSig 计算方式不推荐应用到线上环境,容易被逆向破解导致密钥泄露。
我们提供了多个语言版本(Java/GO/PHP/Nodejs/Python/C#/C++)的 UserSig 服务端计算源代码,详见 UserSig 计算源码
使用腾讯美颜特效之前,需要向腾讯云校验许可凭证。设置 License 需要用到 License Key 和 License Url,示例代码如下。
TencentEffectApi.getApi()?.setLicense(licenseKey, licenseUrl, (errorCode, msg) {
TXLog.printlog("打印鉴权结果 errorCode = $errorCode msg = $msg");
if (errorCode == 0) {
//鉴权成功
}
});
注意:
建议在相关业务模块的初始化代码中触发鉴权许可,避免在使用前才临时去下载 License,同时鉴权时应具有网络权限。
实际应用的 Bundle ID/Package Name 必须和创建 License 时绑定的 Bundle ID/Package Name 完全匹配,否则会导致 License 校验失败,鉴权错误码

步骤5:与直播关联

使用TRTC场景,或者直播场景。需要把使用的美颜功能和直播关联起来,需要添加处理原生工程的关联逻辑。
Android
iOS
在应用的 application 类的 oncreate 方法(或 FlutterActivity 的 onCreate 方法)中添加如下代码:
TXLivePluginManager.register(new XmagicProcesserFactory());
TRTCCloudPlugin.register(new XmagicProcesserFactory());
在应用的 AppDelegate 类中的 didFinishLaunchingWithOptions 方法里面添加如下代码:
XmagicProcesserFactory *instance = [[XmagicProcesserFactory alloc] init];
[TXLivePluginManager registerWithCustomBeautyProcesserFactory:instance];
[TencentRTCCloud setBeautyProcesserFactoryWithFactory:instance];

步骤6:初始化 SDK

初始化 TRTC SDK
初始化腾讯特效 SDK
初始化播放器SDK
// 声明成员变量
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;

// 创建 TRTC 实例
trtcCloud = (await TRTCCloud.sharedInstance())!;

// 创建 TRTCCloudListener 实例
TRTCCloudListener listener = TRTCCloudListener(
// 根据需要实现对应的回调
onError: (errCode, errMsg) {
// TODO
}
);

// 注册监听器,将 listener 绑定到 trtcCloud 实例
trtcCloud.registerListener(listener);
说明:
建议监听 SDK 事件通知,对一些常见错误进行日志打印和处理,详见 错误码表
void _initSettings(InitXmagicCallBack callBack) async {
String resourceDir = await ResPathManager.getResManager().getResPath();
TXLog.printlog('$TAG method is _initResource ,xmagic resource dir is $resourceDir');
TencentEffectApi.getApi()?.setResourcePath(resourceDir);
/// 复制资源只需要复制一次,在当前版本中如果成功复制了一次,以后就不需要再复制资源。
/// Copying the resource only needs to be done once. Once it has been successfully copied in the current version, there is no need to copy it again in future versions.
if (await isCopiedRes()) {
callBack.call(true);
return;
} else {
_copyRes(callBack);
}
}


void _copyRes(InitXmagicCallBack callBack) {
_showDialog(context);
TencentEffectApi.getApi()?.initXmagic((result) {
if (result) {
saveResCopied();
}
_dismissDialog(context);
callBack.call(result);
if (!result) {
Fluttertoast.showToast(msg: "initialization failed");
}
});
}
说明:
初始化腾讯特效 SDK 之前,还需进行资源拷贝等准备工作,详细步骤可参考 腾讯特效 SDK 使用流程
// 创建 controller
TXVodPlayerController _controller = TXVodPlayerController();

// 设置监听事件
// 监听视频宽高变化,设置合适的宽高比例,也可自行设置宽高比例,视频纹理也会根据比例进行相应拉伸
playEventSubscription = _controller.onPlayerEventBroadcast.listen((event) async {
// Subscribe to event distribution
final int code = event["event"];
if (code == TXVodPlayEvent.PLAY_EVT_CHANGE_RESOLUTION) {
int? videoWidth = event[TXVodPlayEvent.EVT_PARAM1];
int? videoHeight = event[TXVodPlayEvent.EVT_PARAM2];
}
});

// 添加布局
child: TXPlayerVideo(androidRenderType: _renderType, onRenderViewCreatedListener: (viewId) {
_controller.setPlayerView(viewId);
},
)

// 初始化播放器,分配共享纹理
await _controller.initialize();

// 播放视频资源
String _url =
"http://1400329073.vod2.myqcloud.com/d62d88a7vodtranscq1400329073/59c68fe75285890800381567412/adp.10.m3u8";
await _controller.startVodPlay(_url);
// 创建 Player
TXLivePlayerController controller = TXLivePlayerController();

// 添加布局
child: TXPlayerVideo(onRenderViewCreatedListener: (viewId) {
_controller.setPlayerView(viewId);
},
),

// 3. 启动播放
String flvUrl = "http://liteavapp.qcloud.com/live/liteavdemoplayerstreamid_demo1080p.flv";
await _controller.startLivePlay(flvUrl);
// 如果您使用的是 12.0 的以下版本,需要传入 playType,示例如下:
await _controller.startLivePlay(flvUrl, playType: TXPlayType.LIVE_FLV);

接入过程

API 时序图





步骤1:主播进房推流

1. 主播进房前开启本地视频预览及音频采集。
startPushStream() async {
trtcCloud = (await TRTCCloud.sharedInstance())!;
// 设置视频编码参数,决定远端用户看到的画面质量
trtcCloud.enterRoom(params, TRTCAppScene.videoCall);
TRTCVideoEncParam encParams = new TRTCVideoEncParam();
encParams.videoResolution = TRTCVideoResolution.res_640_360;
encParams.videoBitrate = 550;
encParams.videoFps = 15;
trtcCloud.setVideoEncoderParam(encParams);
// isFrontCamera 可指定使用前置/后置摄像头进行视频采集
trtcCloud.startLocalPreview(isFrontCamera, viewId);

// 这里可指定声音音质,从低到高分别为 SPEECH/DEFAULT/MUSIC
trtcCloud.startLocalAudio(TRTCAudioQuality.speech);
// 设置事件回调
trtcCloud.registerListener(listener);
}
注意:
您可根据业务需求自行设置视频编码参数 TRTCVideoEncParam,各档位最佳分辨率、码率搭配详见 TRTCVideoResolution
enterRoom 之前调用以上接口,SDK 只会开启摄像头预览和音频采集,并一直等到您调用 enterRoom 之后才开始推流。
enterRoom 之后调用以上接口,SDK 会开启摄像头预览和音频采集,并自动开始推流。
2. 主播设置本地画面的渲染参数,以及编码器输出画面模式(可选项)。
setupRenderParams() {
TRTCRenderParams renderParams = new TRTCRenderParams();
// 画面镜像模式
renderParams.mirrorType = TRTCVideoMirrorType.auto;
// 画面填充模式
renderParams.fillMode = TRTCVideoFillMode.fill;
// 画面旋转角度
renderParams.rotation = TRTCVideoRotation.rotation0;
// 设置本地画面的渲染参数
trtcCloud.setLocalRenderParams(renderParams);

// 设置视频编码器输出的画面方向
TRTCVideoEncParam params = new TRTCVideoEncParam();
params.videoResolutionMode = TRTCVideoResolutionMode.portrait;
trtcCloud.setVideoEncoderParam(params);
}
注意:
设置本地画面渲染参数仅影响本地画面的渲染效果。
设置编码器输出模式会影响房间中其他用户所观看到(以及云端录制文件)的画面效果。
3. 主播正式开始直播,进房推流。
enterRoomByAnchorWithUserId(String userId, String roomId) async {
trtcCloud = (await TRTCCloud.sharedInstance())!;
TRTCParams params = new TRTCParams();
// 指定主播角色
params.role = TRTCRoleType.anchor;
params.sdkAppId = GenerateTestUserSig.sdkAppId;
params.roomId = roomId;
params.userId = userId;
params.userSig = "userSig";
// 以互动直播场景进房
trtcCloud.enterRoom(params, TRTCAppScene.live);
}

// 进房结果事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onEnterRoom:(int result) {
if (result > 0) {
// result 代表加入房间所消耗的时间(毫秒)
debugPrint("Enter room sucess! cost time = ${result}");
} else {
// result 代表进房失败的错误码
debugPrint("Enter room failed! error code = ${result}");
}
}
);
}
注意:
TRTC 房间号分为整型 roomId 和字符串类型 strRoomId,两种类型的房间不互通,建议统一房间号类型。
TRTC 用户角色分为主播和听众,只有主播才有推流权限,进房时需指定用户角色,如未指定则默认为主播角色。
电商直播场景下,进房模式建议选用 TRTCAppSceneLIVE

步骤2:观众进房拉流

1. 观众进入 TRTC 房间。
enterRoomByAudienceWithUserId(String userId, String roomId) async {
trtcCloud = (await TRTCCloud.sharedInstance())!;
TRTCParams params = new TRTCParams();
// 指定观众角色
params.role = TRTCRoleType.audience;
params.sdkAppId = GenerateTestUserSig.sdkAppId;
params.roomId = roomId;
params.userId = userId;
params.userSig = "userSig";
// 以互动直播场景进房
trtcCloud.enterRoom(params, TRTCAppScene.live);
}

// 进房结果事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onEnterRoom:(int result) {
if (result > 0) {
// result 代表加入房间所消耗的时间(毫秒)
debugPrint("Enter room sucess! cost time = ${result}");
} else {
// result 代表进房失败的错误码
debugPrint("Enter room failed! error code = ${result}");
}
}
);
}
2. 观众订阅主播音视频流。
child: TRTCCloudVideoView(
key: ValueKey('RemoteView_$userId'),
onViewCreated: (viewId) {
trtcCloud.startRemoteView(userId,
TRTCVideoStreamType.small, viewId);
},
),

TRTCCloudListener getListener() {
return TRTCCloudListener(
onRemoteUserLeaveRoom(String userId, int reason) {
setState(() {
if (remoteUidSet.containsKey(userId)) {
remoteUidSet.remove(userId);
}
});
},
onUserAudioAvailable: (String userId, bool available) {
// 某远端用户发布/取消了自己的音频
// 在自动订阅模式下,您无需做任何操作,SDK 会自动播放远端用户音频
},
onUserVideoAvailable: (String userId, bool available) {
// 某远端用户发布/取消了主路视频画面
if (available) {
// 订阅远端用户的视频流,并绑定视频渲染控件
setState(() {
remoteUidSet[userId] = userId;
});
}
if (!available && remoteUidSet.containsKey(userId)) {
// 停止订阅远端用户的视频流,并释放渲染控件
setState(() {
remoteUidSet.remove(userId);
});
}
}
);
}
3. 观众设置远端画面的渲染模式(可选项)。
setupRemoteRenderParams() {
TRTCRenderParams renderParams = new TRTCRenderParams();
// 画面镜像模式
renderParams.mirrorType = TRTCVideoMirrorType.auto;
// 画面填充模式
renderParams.fillMode = TRTCVideoFillMode.fill;
// 画面旋转角度
renderParams.rotation = TRTCVideoRotation.rotation0;
// 设置远端画面的渲染模式
trtcCloud.setRemoteRenderParams(userId, TRTCVideoStreamType.big, renderParams);
}

步骤3:观众连麦互动

1. 观众切换为主播角色。
switchToAnchor() {
// 切换为主播角色
trtcCloud.switchRole(TRTCRoleType.anchor);
}


TRTCCloudListener getListener() {
return TRTCCloudListener(
// 切换角色事件回调
onSwitchRole: (errCode, errMsg) => {
if (errCode == 0) {
// 切换角色成功
}
},
);
}
2. 观众开始本地音视频采集和推流。
setupTRTC() {
// 设置视频编码参数,决定远端用户看到的画面质量
TRTCVideoEncParam encParams = new TRTCVideoEncParam();
encParams.videoResolution = TRTCVideoResolution.res_640_360;
encParams.videoBitrate = 550;
encParams.videoFps = 15;
encParams.videoResolutionMode = TRTCVideoResolutionMode.portrait;
trtcCloud.setVideoEncoderParam(encParams);
// 这里可指定声音音质,从低到高分别为 SPEECH/DEFAULT/MUSIC
trtcCloud.startLocalAudio(TRTCAudioQuality.speech);
// isFrontCamera 可指定使用前置/后置摄像头进行视频采集
trtcCloud.startLocalPreview(isFrontCamera, viewId);
}
注意:
您可根据业务需求自行设置视频编码参数 TRTCVideoEncParam,各档位最佳分辨率、码率搭配详见 TRTCVideoResolution
3. 观众下麦停止推流。
switchToAudience() {
// 切换为观众角色
trtcCloud.switchRole(TRTCRoleType.audience);
}

// 切换角色事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
// 切换角色事件回调
onSwitchRole: (errCode, errMsg) => {
if (errCode == 0) {
// 停止摄像头采集推流
trtcCloud.stopLocalPreview();
// 停止麦克风采集推流
trtcCloud.stopLocalAudio();
}
},
);
}

步骤4:退出与解散房间

1. 退出房间。
exitRoom() {
// 停止摄像头采集推流
trtcCloud.stopLocalPreview();
// 停止麦克风采集推流
trtcCloud.stopLocalAudio();
trtcCloud.exitRoom();
}

// 离开房间事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onExitRoom: (int reason) {
if (reason == 0) {
//主动调用 exitRoom 退出房间
} else if (reason == 1) {
//被服务器踢出当前房间
} else if (reason == 2) {
//当前房间整个被解散
}
},
);
}
注意:
待 SDK 占用的所有资源释放完毕后,SDK 会抛出 onExitRoom 回调通知到您。
如果您要再次调用 enterRoom 或者切换到其他音视频 SDK,请等待 onExitRoom 回调之后再执行相关操作。否则可能会遇到例如摄像头、麦克风设备被强占等各种异常问题。
2. 解散房间。
服务端解散房间
TRTC 提供了服务端解散数字类型房间 API DismissRoom,以及解散字符串类型房间 API DismissRoomByStrRoomId,您可以通过调用服务端解散房间接口把房间内所有用户从房间移出,并解散房间。
客户端解散房间
客户端没有直接解散房间的 API,需要各个客户端调用 exitRoom 退出房间,当房间内的所有主播和观众完成退房后,根据 TRTC 房间生命周期规则,房间将会自动解散,详情请参见 TRTC 退出房间
注意:
当一次直播结束之后,建议您调用服务端解散房间 API 确保房间被解散,防止因部分用户未如期退房导致房间存续,从而产生非期望的费用。

备选方案

API 时序图





步骤1:主播旁路推流

1. 前置条件:打开 TRTC 控制台,在应用管理页选择目标应用,点击配置进入基础功能页面,点击开启旁路转推,同时建议选择指定流旁路。旁路转推域名可在 云直播控制台 添加,也可使用系统默认的推流域名。



2. 主播进房前开启本地视频预览及音频采集。
// 获取用于展示主播本地画面预览的视频渲染控件
setupTRTC() {
// 设置视频编码参数,决定远端用户看到的画面质量
TRTCVideoEncParam encParams = new TRTCVideoEncParam();
encParams.videoResolution = TRTCVideoResolution.res_640_360;
encParams.videoBitrate = 550;
encParams.videoFps = 15;
encParams.videoResolutionMode = TRTCVideoResolutionMode.portrait;
trtcCloud.setVideoEncoderParam(encParams);

// isFrontCamera 可指定使用前置/后置摄像头进行视频采集
trtcCloud.startLocalPreview(isFrontCamera, viewId);
// 这里可指定声音音质,从低到高分别为 SPEECH/DEFAULT/MUSIC
trtcCloud.startLocalAudio(TRTCAudioQuality.defaultMode);
}
注意:
您可根据业务需求自行设置视频编码参数 TRTCVideoEncParam,各档位最佳分辨率、码率搭配详见 TRTCVideoResolution
enterRoom 之前调用以上接口,SDK 只会开启摄像头预览和音频采集,并一直等到您调用 enterRoom 之后才开始推流。
enterRoom 之后调用以上接口,SDK 会开启摄像头预览和音频采集,并自动开始推流。
3. 主播设置本地画面的渲染参数,以及编码器输出画面模式。
setupRenderParams() {
TRTCRenderParams renderParams = new TRTCRenderParams();
// 画面镜像模式
renderParams.mirrorType = TRTCVideoMirrorType.auto;
// 画面填充模式
renderParams.fillMode = selectedRemoteFillMode;
// 画面旋转角度
renderParams.rotation = selectedRemoteRotation;
// 设置本地画面的渲染参数
trtcCloud.setLocalRenderParams(renderParams);
// 设置视频编码器输出的画面方向
TRTCVideoEncParam params = new TRTCVideoEncParam();
params.videoResolutionMode = TRTCVideoResolutionMode.portrait;
trtcCloud.setVideoEncoderParam(params);
}
注意:
设置本地画面渲染参数仅影响本地画面的渲染效果。
设置编码器输出模式会影响房间中其他用户所观看到(以及云端录制文件)的画面效果。
4. 主播正式开始直播,进房推流。
enterRoomByAnchorWithUserId(String userId, String roomId) async {
trtcCloud = (await TRTCCloud.sharedInstance())!;
TRTCParams params = new TRTCParams();
// 指定主播角色
params.role = TRTCRoleType.anchor;
params.sdkAppId = GenerateTestUserSig.sdkAppId;
params.roomId = roomId;
params.userId = userId;
params.userSig = "userSig";
// 以互动直播场景进房
trtcCloud.enterRoom(params, TRTCAppScene.live);
}

// 进房结果事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onEnterRoom:(int result) {
if (result > 0) {
// result 代表加入房间所消耗的时间(毫秒)
debugPrint("Enter room sucess! cost time = ${result}");
} else {
// result 代表进房失败的错误码
debugPrint("Enter room failed! error code = ${result}");
}
}
);
}
注意:
TRTC 房间号分为整型 roomId 和字符串类型 strRoomId,两种类型的房间不互通,建议统一房间号类型。
TRTC 用户角色分为主播和听众,只有主播才有推流权限,进房时需指定用户角色,如未指定则默认为主播角色。
电商直播场景下,进房模式建议选用 TRTCAppSceneLIVE
5. 主播将音视频流转推到直播 CDN。
startPublishMediaToCDN(String streamName) {
// 设定推流地址过期时间
DateTime now = DateTime.now();
DateTime expiration = now.add(Duration(days: 1));
int time = expiration.millisecondsSinceEpoch ~/ 1000; // 转换为秒

// 生成鉴权信息,getSafeUrl 方法可在云直播控制台-域名管理-推流配置-推流地址示例代码获取
String secretParam = getSafeUrl(LIVE_URL_KEY, streamName, time);
// 媒体流发布的目标地址
TRTCPublishTarget target = TRTCPublishTarget();
// 目标地址设定为旁路转推到 CDN
target.mode = TRTCPublishMode.bigStreamToCdn;
TRTCPublishCdnUrl cdnUrl = new TRTCPublishCdnUrl();
// 拼接发布到直播服务商的推流地址(RTMP 格式)
cdnUrl.rtmpUrl = "rtmp://$PUSH_DOMAIN/live/$streamName?$secretParam";
// 腾讯云直播推流地址为 true,第三方为 false
cdnUrl.isInternalLine = true;
NSMutableArray* cdnUrlList = [NSMutableArray array];
// 可以添加多个 CDN 推流地址
target.cdnUrlList?.add(cdnUrl);
// 设置媒体流编码输出参数(可根据业务需求自定义)
TRTCStreamEncoderParam encoderParam = TRTCStreamEncoderParam();
encoderParam.audioEncodedSampleRate = 48000;
encoderParam.audioEncodedChannelNum = 1;
encoderParam.audioEncodedKbps = 50;
encoderParam.audioEncodedCodecType = 0;
encoderParam.videoEncodedWidth = 540;
encoderParam.videoEncodedHeight = 960;
encoderParam.videoEncodedFPS = 15;
encoderParam.videoEncodedGOP = 2;
encoderParam.videoEncodedKbps = 1300;
// 设置布局配置
TRTCStreamMixingConfig config = TRTCStreamMixingConfig();
TRTCUser selfUser = TRTCUser();
selfUser.userId = localUserId;
selfUser.intRoomId = localRoomId;

TRTCVideoLayout selfVideoLayout = TRTCVideoLayout();
selfVideoLayout.fixedVideoStreamType = TRTCVideoStreamType.big;
selfVideoLayout.rect = TRTCRect(left: 0, top: 0, right: 1080, bottom: 1920);
selfVideoLayout.zOrder = 0;
selfVideoLayout.fixedVideoUser = selfUser;
selfVideoLayout.fillMode = TRTCVideoFillMode.fit;

config.videoLayoutList.add(selfVideoLayout);

TRTCUser remoteUser = TRTCUser();
remoteUser.userId = remoteUserId;
remoteUser.intRoomId = remoteRoomId;

TRTCVideoLayout remoteVideoLayout = TRTCVideoLayout();
remoteVideoLayout.fixedVideoStreamType = TRTCVideoStreamType.big;
remoteVideoLayout.rect = TRTCRect(left: 100, top: 50, right: 316, bottom: 384);
remoteVideoLayout.zOrder = 1;
remoteVideoLayout.fixedVideoUser = remoteUser;
remoteVideoLayout.fillMode = TRTCVideoFillMode.fit;

config.videoLayoutList.add(remoteVideoLayout);

// 开始发布媒体流
trtcCloud.startPublishMediaStream(target, encoderParam, config);
}
注意:
单主播直播时仅需发起旁路转推任务,待有观众连麦或主播 PK 时再将此任务更新为混流转码任务。
推流鉴权 KEY LIVE_URL_KEY 及推流域名 PUSH_DOMAIN 等信息需在 云直播控制台-域名管理 获取。
国际站用户、旧版架构用户如调用 startPublishMediaStream 报错,请 联系我们 协助发布配置。
发布媒体流后,SDK 会通过回调 onStartPublishMediaStream 带给您后台启动的任务标识(即 taskId)。
TRTCCloudListener getListener() {
return TRTCCloudListener(
onStartPublishMediaStream: (taskId, errCode, errMsg, extraInfo) {
// taskId: 当请求成功时,TRTC 后台会在回调中提供给您这项任务的 taskId,后续您可以通过该 taskId 结合 updatePublishMediaStream 和 stopPublishMediaStream 进行更新和停止
// code: 回调结果,0 表示成功,其余值表示失败
debugPrint("TRTCCloudListener.onStartPublishMediaStream: taskId:$taskId, code:$errCode, msg:$errMsg");
publishMediaStreamTaskId = taskId;
}
);
}

步骤2:观众拉流播放

CDN 观众无需进入 TRTC 房间,可直接拉取主播旁路 CDN 流播放。
// 初始化播放器
var livePlayer = new V2TXLivePlayer();
// 设置播放器回调监听
livePlayer.addListener(onPlayerObserver);

// 设置播放器的视频渲染控件
var code = await livePlayer?.setRenderViewID(_localViewId!);
if (code != V2TXLIVE_OK) {
debugPrint("StartPlay error: please check remoteView load");
}

// 设置延迟调节模式(可选项)
livePlayer.setCacheParams(1.0, 5.0);// 自动模式
livePlayer.setCacheParams(1.0, 1.0);// 极速模式
livePlayer.setCacheParams(5.0, 5.0);// 流畅模式

// 拼接拉流播放地址
String flvUrl = "http://$PLAY_DOMAIN/live/$streamName.flv";
String hlsUrl = "http://$PLAY_DOMAIN/live/$streamName.m3u8";
String rtmpUrl = "rtmp://$PLAY_DOMAIN/live/$streamName";
String webrtcUrl = "webrtc://$PLAY_DOMAIN/live/$streamName";
// 启动播放
livePlayer.startLivePlay(flvUrl);

// 自定义设置填充模式(可选项)
livePlayer.setRenderFillMode(V2TXLiveFillMode.v2TXLiveFillModeFit);

// 自定义设置画面渲染方向(可选项)
livePlayer.setRenderRotation(V2TXLiveRotation.v2TXLiveRotation0);
注意:
播放域名 PLAY_DOMAIN 需要您在 云直播控制台 添加自有已备案域名进行直播播放,并需 配置域名 CNAME
使用直播播放功能需要提前配置播放器 License 授权,否则将播放失败(黑屏),详见 鉴权与许可

步骤3:观众连麦互动

1. 连麦观众需要进入 TRTC 房间与主播实时互动。
// 进入 TRTC 房间并开始推流
enterRoomWithUserId(String userId, String roomId) async {
trtcCloud = (await TRTCCloud.sharedInstance())!;
TRTCParams params = new TRTCParams();
// 指定主播角色
params.role = TRTCRoleType.anchor;
params.sdkAppId = GenerateTestUserSig.sdkAppId;
params.roomId = roomId;
params.userId = userId;
params.userSig = "userSig";
// 开启本地音视频采集
startLocalMedia();
// 以互动直播场景进房
trtcCloud.enterRoom(params, TRTCAppScene.live);
}

// 开启本地视频预览及音频采集
startLocalMedia() {
// 设置视频编码参数,决定远端用户看到的画面质量
TRTCVideoEncParam encParams = new TRTCVideoEncParam();
encParams.videoResolution = TRTCVideoResolution.res_640_360;
encParams.videoBitrate = 550;
encParams.videoFps = 15;
trtcCloud.setVideoEncoderParam(encParams);
// isFrontCamera 可指定使用前置/后置摄像头进行视频采集
trtcCloud.startLocalPreview(isFrontCamera, viewId);

// 这里可指定声音音质,从低到高分别为 SPEECH/DEFAULT/MUSIC
trtcCloud.startLocalAudio(TRTCAudioQuality.speech);
}

// 进房结果事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onEnterRoom:(int result) {
if (result > 0) {
// result 代表加入房间所消耗的时间(毫秒)
debugPrint("Enter room sucess! cost time = ${result}");
} else {
// result 代表进房失败的错误码
debugPrint("Enter room failed! error code = ${result}");
}
}
);
}
注意:
您可根据业务需求自行设置视频编码参数 TRTCVideoEncParam,各档位最佳分辨率、码率搭配详见 TRTCVideoResolution
2. 连麦观众进房成功后开始订阅主播音视频流。
TRTCCloudListener getListener() {
return TRTCCloudListener(
onUserAudioAvailable: (userId, available) {
// 某远端用户发布/取消了自己的音频
// 在自动订阅模式下,您无需做任何操作,SDK 会自动播放远端用户音频
},

onUserVideoAvailable: (userId, available) {
// 某远端用户发布/取消了主路视频画面
if (available) {
// 订阅远端用户的视频流,并绑定视频渲染控件
trtcCloud.startRemoteView(userId, TRTCVideoStreamType.big, viewId);
} else {
// 停止订阅远端用户的视频流,并释放渲染控件
trtcCloud.stopRemoteView(userId, TRTCVideoStreamType.big);
}
},

onFirstVideoFrame: (userId, streamType, width, height) {
// SDK 开始渲染本地或远端用户的首帧画面
if (userId.isNotEmpty) {
// 收到主播首帧画面,停止播放 CDN 流
livePlayer.stopPlay();
}
},
);
}
注意:
TRTC 拉流 startRemoteView 可直接复用之前 CDN 拉流 setRenderView 的视频渲染控件。
为避免在切换拉流器时画面中断,建议等待收到 TRTC 首帧回调 onFirstVideoFrame 后再停止 CDN 拉流。
3. 主播更新发布混合媒体流。
// 连麦观众进房事件回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onRemoteUserEnterRoom:(userId) {
if (!mixUserList.contains(userId)) {
mixUserList.add(userId);
}
updatePublishMediaToCDN();
},

// 更新媒体流的事件回调
onUpdatePublishMediaStream: (taskId, errCode, errMsg, extraInfo) {
// 您调用媒体流发布接口 (updatePublishMediaStream) 时传入的 taskId,会通过此回调再带回给您,用于标识该回调属于哪一次更新请求
// code: 回调结果,0 表示成功,其余值表示失败
},
);
}

// 更新发布混合媒体流到直播 CDN
updatePublishMediaToCDN() {
// 设定推流地址过期时间
DateTime now = DateTime.now();
DateTime expiration = now.add(Duration(days: 1));
int time = expiration.millisecondsSinceEpoch ~/ 1000; // 转换为秒

// 生成鉴权信息,getSafeUrl 方法可在云直播控制台-域名管理-推流配置-推流地址示例代码获取
String secretParam = getSafeUrl(LIVE_URL_KEY, streamName, time);
// 媒体流发布的目标地址
TRTCPublishTarget target = TRTCPublishTarget();
// 目标地址设定为旁路转推到 CDN
target.mode = TRTCPublishMode.bigStreamToCdn;
TRTCPublishCdnUrl cdnUrl = new TRTCPublishCdnUrl();
// 拼接发布到直播服务商的推流地址(RTMP 格式)
cdnUrl.rtmpUrl = "rtmp://$PUSH_DOMAIN/live/$streamName?$secretParam";
// 腾讯云直播推流地址为 true,第三方为 false
cdnUrl.isInternalLine = true;
NSMutableArray* cdnUrlList = [NSMutableArray array];
// 可以添加多个 CDN 推流地址
target.cdnUrlList?.add(cdnUrl);
// 设置媒体流编码输出参数(可根据业务需求自定义)
TRTCStreamEncoderParam encoderParam = TRTCStreamEncoderParam();
encoderParam.audioEncodedSampleRate = 48000;
encoderParam.audioEncodedChannelNum = 1;
encoderParam.audioEncodedKbps = 50;
encoderParam.audioEncodedCodecType = 0;
encoderParam.videoEncodedWidth = 540;
encoderParam.videoEncodedHeight = 960;
encoderParam.videoEncodedFPS = 15;
encoderParam.videoEncodedGOP = 2;
encoderParam.videoEncodedKbps = 1300;
// 设置布局配置
TRTCStreamMixingConfig config = TRTCStreamMixingConfig();
TRTCUser selfUser = TRTCUser();
selfUser.userId = localUserId;
selfUser.intRoomId = localRoomId;

TRTCVideoLayout selfVideoLayout = TRTCVideoLayout();
selfVideoLayout.fixedVideoStreamType = TRTCVideoStreamType.big;
selfVideoLayout.rect = TRTCRect(left: 0, top: 0, right: 1080, bottom: 1920);
selfVideoLayout.zOrder = 0;
selfVideoLayout.fixedVideoUser = selfUser;
selfVideoLayout.fillMode = TRTCVideoFillMode.fit;

config.videoLayoutList.add(selfVideoLayout);

TRTCUser remoteUser = TRTCUser();
remoteUser.userId = remoteUserId;
remoteUser.intRoomId = remoteRoomId;

TRTCVideoLayout remoteVideoLayout = TRTCVideoLayout();
remoteVideoLayout.fixedVideoStreamType = TRTCVideoStreamType.big;
remoteVideoLayout.rect = TRTCRect(left: 100, top: 50, right: 316, bottom: 384);
remoteVideoLayout.zOrder = 1;
remoteVideoLayout.fixedVideoUser = remoteUser;
remoteVideoLayout.fillMode = TRTCVideoFillMode.fit;

config.videoLayoutList.add(remoteVideoLayout);

// 开始发布媒体流
trtcCloud.startPublishMediaStream(target, encoderParam, config);
}
注意:
为了保证 CDN 播放连续并且不会发生断流,您需要保持媒体流编码输出参数 encoderParam 以及流名称 streamName 不变。
媒体流编码输出参数、混合画面布局参数可根据业务需求自定义;当前仅支持最高16路音视频输入,如果用户只有音频也会被算作一路。
同一个任务不支持纯音频、音视频、纯视频之间的切换。
4. 观众下麦退房,主播更新混流任务。
// 设置播放器回调监听
var livePlayer = new V2TXLivePlayer();
// 可复用 TRTC 视频渲染控件
livePlayer.addListener(onPlayerObserver);
// 重新开始播放 CDN 媒体流
livePlayer.startLivePlay(flvUrl);

onPlayerObserver(V2TXLivePlayerListenerType type, params) {
if (type == V2TXLivePlayerListenerType.onVideoLoading) {
// 视频加载事件
} else if (type == V2TXLivePlayerListenerType.onVideoPlaying) {
// 视频播放事件
if (params.firstPlay) {
trtcCloud.stopAllRemoteView();
trtcCloud.stopLocalAudio();
trtcCloud.stopLocalPreview();
trtcCloud.exitRoom();
}
}
}
注意:
为避免在切换拉流器时画面中断,建议等待收到播放器视频播放事件 onVideoPlaying 后再退出 TRTC 房间。
TRTCCloudListener getListener() {
return TRTCCloudListener(
// 连麦观众退房事件回调
onRemoteUserLeaveRoom: (userId, reason) {
if (mixUserList.contains(userId)) {
mixUserList.remove(userId);
}
// 主播更新混流任务
updatePublishMediaToCDN();
},
onUpdatePublishMediaStream: (taskId, errCode, errMsg, extraInfo) {
// 您调用媒体流发布接口 (updatePublishMediaStream) 时传入的 taskId,会通过此回调再带回给您,用于标识该回调属于哪一次更新请求
// code: 回调结果,0 表示成功,其余值表示失败
},
);
}

步骤4:主播关播与退房

exitRoom() {
// 停止所有发布的媒体流
trtcCloud.stopAllRemoteView();
trtcCloud.stopLocalAudio();
trtcCloud.stopLocalPreview();
trtcCloud.exitRoom();
}

TRTCCloudListener getListener() {
return TRTCCloudListener(
// 停止媒体流的事件回调
onStopPublishMediaStream: (taskId, errCode, errMsg, extraInfo) {
// 您调用停止发布媒体流 (stopPublishMediaStream) 时传入的 taskId,会通过此回调再带回给您,用于标识该回调属于哪一次停止请求
// code: 回调结果,0 表示成功,其余值表示失败
},

onExitRoom: (int reason) {
if (reason == 0) {
//主动调用 exitRoom 退出房间
} else if (reason == 1) {
//被服务器踢出当前房间
} else if (reason == 2) {
//当前房间整个被解散
}
},
);
}
注意:
停止发布媒体流传参 taskId 填写空字符串,将会停止所有您发布的媒体流。
待 SDK 占用的所有资源释放完毕后,SDK 会抛出 onExitRoom 回调通知到您。

高级功能

商品信息弹窗

商品信息弹窗功能可以通过 IM 自定义消息 实现,也可以通过 SEI 信息 实现,下面将分别介绍这两种实现方式。

自定义消息

自定义消息需要依赖腾讯云 即时通信 IM 的能力,您需提前开通服务并导入 IM SDK,详细指引请参见 语聊房接入指引-接入准备
1. 发送自定义消息。
方式一:主播在客户端发送商品弹窗相关的群组自定义消息。
var timManager = TencentImSDKPlugin.v2TIMManager.v2TIMMessageManager;
// 构造商品弹窗消息体
var messageRet = await timManager.createCustomMessage(data: json.encode(
{
"msg": {
"itemNumber": "123",// 商品编号
"itemPrice": 99.99,// 商品价格
"itemTitle": "title",// 商品标题
"itemUrl": "link_url"// 商品图片地址
},
"cmd": "item_popup_msg"
}
));
if (messageRet.code == 0) {
// 创建消息成功
final sendMsgRes = await timManager.sendMessage(message: messageRet.data?.messageInfo, receiver: '', groupID: "groupID");
if (sendMsgRes.code == 0) {
// 发送商品弹窗消息成功
// 本地渲染商品弹窗效果
} else {
// 发送商品弹窗消息失败
}
} else {
// 创建消息失败
}
方式二:后台运营在服务端发送商品弹窗相关的群组自定义消息。
请求 URL 示例:
https://xxxxxx/v4/group_open_http_svc/send_group_msg?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
请求包体示例:
{
"GroupId": "@TGS#12DEVUDHQ",
"Random": 2784275388,
"MsgPriority": "High", // 消息的优先级,商品弹窗消息建议设置为高优先级
"MsgBody": [
{
"MsgType": "TIMCustomElem",
"MsgContent": {
// itemNumber: 商品编号; itemPrice: 商品价格; itemTitel: 商品标题; itemUrl: 商品图片地址
"Data": "{\\"cmd\\": \\"item_popup_msg\\", \\"msg\\": {\\"itemNumber\\": 1, \\"itemPrice\\": 199.0, \\"itemTitle\\": \\"xxx\\", \\"itemUrl\\": \\"xxx\\"}}"
}
}
]
}
2. 接收自定义消息。
房间内其他用户客户端收到群自定义消息回调,然后进行消息解析和商品弹窗效果渲染。
// 收到群自定义消息
V2TimAdvancedMsgListener _advancedMsgListener = V2TimAdvancedMsgListener(
onRecvNewMessage: (message) {
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
if (message.groupID != null && message.groupID!.isNotEmpty) {
// 这里处理 根据商品编号、商品价格、商品标题、商品图片地址渲染商品弹窗效果
}
}
}
);
var timManager = TencentImSDKPlugin.v2TIMManager.v2TIMMessageManager;
timManager.addAdvancedMsgListener(listener: _advancedMsgListener);

SEI 信息

SEI 信息会插入到主播视频流中进行传输,能够实现商品信息弹窗和主播直播画面的精准同步。
1. 发送 SEI 信息。
主播在 TRTC 客户端发送商品弹窗相关的 SEI 消息。
// 发送 SEI 信息
trtcCloud.sendSEIMsg(json.encode(
{
"msg": {
"itemNumber": "123",// 商品编号
"itemPrice": 99.99,// 商品价格
"itemTitle": "title",// 商品标题
"itemUrl": "link_url"// 商品图片地址
},
"cmd": "item_popup_msg"
}
), 1);
2. 接收 SEI 信息。
方式一:观众在 TRTC 客户端接收 SEI 消息,然后进行消息解析和商品弹窗效果渲染。
TRTCCloudListener getListener() {
return TRTCCloudListener(
onRecvSEIMsg: (userId, message) {
// 这里处理 根据商品编号、商品价格、商品标题、商品图片地址渲染商品弹窗效果
},
);
}
方式二:观众在 CDN 流播放器端接收 SEI 消息,然后进行消息解析和商品弹窗效果渲染。
// 设置 TRTC 发送端 SEI 消息的 PayloadType(需在发送前设置)
trtcCloud.callExperimentalAPI("{\\"api\\":\\"setSEIPayloadType\\",\\"params\\":{\\"payloadType\\":5}}");

// 开启播放器端接收 SEI 消息, 同时设置 PayloadType
livePlayer.enableReceiveSeiMessage(true, 5);

// SEI 消息回调与解析
TRTCCloudListener getListener() {
return TRTCCloudListener(
onRecvSEIMsg: (userId, message) {
// 这里处理 根据商品编号、商品价格、商品标题、商品图片地址渲染商品弹窗效果
},
);
}
注意:
需要确保 TRTC 发送端和播放器接收端的 SEI PayloadType 一致,观众端才能顺利接收到经由 TRTC 旁路转推的 SEI 消息。

商品讲解回放

通过播放提前录制好的商品讲解视频,实现商品讲解回放功能。
首先需要进行 播放器初始化,然后开始播放录制视频。TXVodPlayer 支持两种播放模式,您可以根据需要自行选择:
TXVodPlayerController 内部会自动识别播放协议,您只需要将您的播放 URL 传给 startVodPlay 函数即可。
通过 URL 方式
通过 FileId 方式
TXVodPlayerController _controller = TXVodPlayerController();
// 播放视频资源
String _url =
"http://1400329073.vod2.example.com/d62d88a7vodtranscq1400329073/59c68fe75285890800381567412/adp.10.m3u8";
await _controller.startVodPlay(_url);
TXVodPlayerController _controller = TXVodPlayerController();
// psign 即播放器签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
TXPlayInfoParams params = TXPlayInfoParams(appId: 1252463788,
fileId: "4564972819220421305", psign: "psignxxxxxxx");
await _controller.startVodPlayWithParams(params);
播放控制:调整进度、暂停播放、恢复播放、结束播放。
TXVodPlayerController _controller = TXVodPlayerController();

// 调整进度(秒)
_controller.seek(time);

// 暂停播放
_controller.pause();

// 恢复播放
_controller.resume();

// 结束播放
_controller.stop();

跨房 PK 连麦

1. 任意一方发起跨房 PK 连麦。
connectOtherRoom(String roomId ){
Map jsonDict = new Map();
// 字符房间号为 strRoomId
jsonDict['roomId'] = otherRoomId;
jsonDict['userId'] = otherUserId;
trtcCloud.connectOtherRoom(jsonEncode(jsonDict));
}

// 请求跨房连麦的结果回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
onConnectOtherRoom: (userId, errCode, errMsg) {
// 要跨房通话的另一个房间中的主播的用户 ID
// 错误码,ERR_NULL 代表请求成功
// 错误信息
},
);
}
注意:
跨房 PK 连麦的本地用户和对端用户必须都为主播角色,且必须都有音频/视频上行。
可通过多次调用 ConnectOtherRoom() 来实现与多个房间主播跨房连麦,目前限制一个房间最多可以和其他三个房间的主播跨房连麦,一个房间中最多10个主播可与其他房间的主播跨房连麦。
2. 两个房间中的所有用户都会收到来自另一个房间中的 PK 主播的音视频流可用回调。
TRTCCloudListener getListener() {
return TRTCCloudListener(
onUserVideoAvailable: (userId, available) {
// 某远端用户发布/取消了主路视频画面
if (available) {
// 订阅远端用户的视频流,并绑定视频渲染控件
trtcCloud.startRemoteView(otherUserId, TRTCVideoStreamType.big, viewId);
} else {
// 停止订阅远端用户的视频流,并释放渲染控件
trtcCloud.stopRemoteView(otherUserId, TRTCVideoStreamType.big);
}
},
onUserAudioAvailable: (userId, available) {
// 某远端用户发布/取消了自己的音频
// 在自动订阅模式下,您无需做任何操作,SDK 会自动播放远端用户音频
},
);
}
3. 任意一方退出跨房 PK 连麦。
// 退出跨房连麦
trtcCloud.disconnectOtherRoom();

// 退出跨房连麦的结果回调
TRTCCloudListener getListener() {
return TRTCCloudListener(
// 退出跨房连麦的结果回调
onDisconnectOtherRoom: (errCode, errMsg) {
},
);
}
注意:
调用 DisconnectOtherRoom() 后,即退出与所有其他房间主播的跨房 PK 连麦。
跨房 PK 连麦的发起端和接收端任意一端均可调用 DisconnectOtherRoom() 退出跨房 PK 连麦。

第三方美颜接入

由于 Flutter 端的 GL 环境与原生端环境进行了隔离,所以 Flutter 中接入美颜时无法直接建立绑定关系,需要在原生端进行关系的绑定,如下图所示:

流程:
1. 美颜侧抽象一层接口,并在美颜侧实现了接口。
2. 在应用启动时将此接口注册到三方推流端,这样三方推流端就可以通过此接口进行创建、使用、销毁美颜实例。
3. 第三方推流端再将创建和销毁美颜的能力暴露给自己的 Flutter 端供客户使用。
4. 美颜属性设置可通过美颜提供的 Flutter SDK 能力进行处理。

TRTC 支持接入第三方美颜特效产品,下面以腾讯特效为例,展示第三方美颜接入流程。
1. 集成腾讯特效 SDK、申请授权 License,详情请参见 接入准备 步骤实现。
2. 设置 SDK 素材资源路径(如有),细节可以参见 美颜 Demo
String resourceDir = await ResPathManager.getResManager().getResPath();
TXLog.printlog('$TAG method is _initResource ,xmagic resource dir is $resourceDir');
TencentEffectApi.getApi()?.setResourcePath(resourceDir);


/// Copying the resource only needs to be done once. Once it has been successfully copied in the current version, there is no need to copy it again in future versions.
if (await isCopiedRes()) {
callBack.call(true);
return;
} else {
_copyRes(callBack);
}


void _copyRes(InitXmagicCallBack callBack) {
_showDialog(context);
TencentEffectApi.getApi()?.initXmagic((result) {
if (result) {
saveResCopied();
}
_dismissDialog(context);
callBack.call(result);
if (!result) {
Fluttertoast.showToast(msg: "initialization failed");
}
});
}
3. 设置第三方美颜的视频数据回调,将美颜 SDK 处理每帧数据结果传入 TRTC SDK 内部做渲染处理。
开发者只需要实现 ITXCustomBeautyProcesser 方法,在 onProcessVideoFrame 里面处理美颜数据即可。
Android
iOS
1. 美颜预处理。
实现第三方美颜关键就是要实现2个接口:ITXCustomBeautyProcesserFactory 和 ITXCustomBeautyProcesser。第三方美颜处理的具体操作在 ITXCustomBeautyProcesser 的 onProcessVideoFrame 方法实现。
import com.tencent.live.beauty.custom.ITXCustomBeautyProcesser;
import com.tencent.live.beauty.custom.ITXCustomBeautyProcesserFactory;
import com.tencent.live.beauty.custom.TXCustomBeautyDef;

public class TXThirdBeauty implements ITXCustomBeautyProcesserFactory {
private BeatuyProcessor customBeautyProcessor;
@Override
public ITXCustomBeautyProcesser createCustomBeautyProcesser() {
customBeautyProcessor = new BeatuyProcessor();
return customBeautyProcessor;
}

@Override
public void destroyCustomBeautyProcesser() {
if (null != customBeautyProcessor) {
customBeautyProcessor.destory();
customBeautyProcessor = null;
}
}
}

class BeatuyProcessor implements ITXCustomBeautyProcesser {
@Override
public void onProcessVideoFrame(TXCustomBeautyDef.TXCustomBeautyVideoFrame txCustomBeautyVideoFrame, TXCustomBeautyDef.TXCustomBeautyVideoFrame txCustomBeautyVideoFrame1) {
if (mXmagicApi != null) {
// 第三方处理
txCustomBeautyVideoFrame1.texture.textureId = mXmagicApi.process(txCustomBeautyVideoFrame.texture
.textureId,
txCustomBeautyVideoFrame.width, txCustomBeautyVideoFrame.height);
} else {
txCustomBeautyVideoFrame1.texture.textureId = txCustomBeautyVideoFrame.texture.textureId;
}
return txCustomBeautyVideoFrame1.texture.textureId;
}
}
2. 注册第三方美颜对象。
根据自己的业务需求在需要的地方调用 TRTCCloudPlugin 的 register(ITXCustomBeautyProcesserFactory beautyProcesserFactory) 方法来注册美颜实例。
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TRTCPlugin.setBeautyProcesserFactory(new TXThirdBeauty());
}
}
3. 开启美颜。
完成上述操作后,您可以通过 TRTC 的隐藏接口来开启/关闭美颜。
_enableCustomBeautyByNative(bool open) {
trtcCloud.callExperimentalAPI("{\\"api\\": \\"enableVideoProcessByNative\\", \\"params\\": {\\"enable\\": $open}}");
}
1. 美颜预处理。
实现第三方美颜关键就是要实现2个接口:ITXCustomBeautyProcesserFactory 和 ITXCustomBeautyProcesser。第三方美颜处理的具体操作在 ITXCustomBeautyProcesser 的 onProcessVideoFrame 方法实现。
import Foundation
import tencent_rtc_sdk
import TXCustomBeautyProcesserPlugin

class TRTCVideoCustomPreprocessor: ITXCustomBeautyProcesserFactory, ITXCustomBeautyProcesser {

func getSupportedPixelFormat() -> TXCustomBeautyProcesserPlugin.ITXCustomBeautyPixelFormat {
return .Texture2D
}

func getSupportedBufferType() -> TXCustomBeautyProcesserPlugin.ITXCustomBeautyBufferType {
return .Texture
}

func onProcessVideoFrame(srcFrame: TXCustomBeautyProcesserPlugin.ITXCustomBeautyVideoFrame, dstFrame: TXCustomBeautyProcesserPlugin.ITXCustomBeautyVideoFrame) -> TXCustomBeautyProcesserPlugin.ITXCustomBeautyVideoFrame {

let outputTextureId = processTexture(srcFrame.textureId, width: Uint32(srcFrame.width), height: Uint32(srcFrame.height))
dstFrame.textureId = outputTextureId
return desdstFrame
}

func createCustomBeautyProcesser() -> any TXCustomBeautyProcesserPlugin.ITXCustomBeautyProcesser {
return self;
}

func destroyCustomBeautyProcesser() {
invalidateBindedTetxure()
}
}
2. 注册第三方美颜对象。
根据自己的业务需求在需要的地方调用 TencentTRTCCloud 的 register(ITXCustomBeautyProcesserFactory beautyProcesserFactory) 方法来注册美颜实例。
@objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
lazy var customBeautyInstance: TRTCVideoCustomPreprocessor = {
let customBeautyInstance = TRTCVideoCustomPreprocessor()
return customBeautyInstance
}()
TencentRTCCloud.setBeautyProcesserFactory(factory: customBeautyInstance)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
3. 开启美颜。
完成上述操作后,您可以通过 TRTC 的隐藏接口来开启/关闭美颜。
_enableCustomBeautyByNative(bool open) {
trtcCloud.callExperimentalAPI("{\\"api\\": \\"enableVideoProcessByNative\\", \\"params\\": {\\"enable\\": $open}}");
}
注意:
步骤1、步骤2根据不同的第三方美颜产品实现方式有所不同,而步骤3是 TRTC 集成第三方美颜的通用且重要步骤
场景化集成腾讯美颜特效指引详见 TRTC SDK 集成腾讯特效,独立集成腾讯美颜特效指引详见 独立集成腾讯特效

双路编码模式

开启双路编码模式后,当前用户的编码器会同时输出【高清大画面】和【低清小画面】两路视频流(但只有一路音频流)。这样,房间中的其他用户就可以根据自身的网络情况或屏幕大小选择订阅【高清大画面】或是【低清小画面】。
1. 开启大小画面双路编码模式。
enableDualStreamMode(bool enable) {
TRTCVideoEncParam smallVideoEncParam = new TRTCVideoEncParam();
smallVideoEncParam.videoResolution = TRTCVideoResolution.res_480_270;
smallVideoEncParam.videoBitrate = 550;
smallVideoEncParam.videoFps = 15;
smallVideoEncParam.videoResolutionMode = TRTCVideoResolutionMode.portrait;
trtcCloud.enableSmallVideoStream(enable, smallVideoEncParam);
}
注意:
双路编码开启后,会消耗更多的 CPU 和 网络带宽,所以 Mac、Windows 或者高性能 Pad 可以考虑开启,不建议手机端开启。
2. 选择拉取远端用户视频流类型。
// 订阅远端用户视频流时可选视频流类型
trtcCloud.startRemoteView(userId, TRTCVideoStreamType.big, viewId);

// 亦可随时切换指定远端用户的大小画面
trtcCloud.setRemoteVideoStreamType(userId, TRTCVideoStreamType.small);
注意:
双路编码开启后,可通过指定 streamType 视频流类型为 TRTCVideoStreamType.small 来拉取低清小画面观看。

视图渲染控件

如果您业务涉及到切换显示区域的交互场景,可以使用 TRTC SDK 更新本地预览画面、更新远端用户视频渲染控件功能实现。
// 更新本地预览画面渲染控件
trtcCloud.updateLocalView(viewId);

// 更新远端用户视频渲染控件
trtcCloud.updateRemoteView(userId, TRTCVideoStreamType.big, viewId);
注意:
传参 view 为目标视频渲染控件id,streamType 仅支持 TRTCVideoStreamType.big 和 TRTCVideoStreamType.sub。

异常处理

异常错误处理

TRTC SDK 遇到不可恢复的错误会在 onError 回调中抛出,详见 TRTC 错误码表
1. UserSig 相关。UserSig 校验失败会导致进房失败,您可参见 UserSig 生成与校验 进行校验。
枚举
取值
描述
ERR_USER_SIG_INVALID
-3320
进房参数 userSig 不正确
ERR_SERVER_INFO_ECDH_GET_TINYID
-100018
userSig 校验失败,请检查 userSig 是否正确
2. 进退房相关。进房失败请先检查进房参数是否正确,且进退房接口必须成对调用,即便进房失败也需要调用退房接口。
枚举
取值
描述
ERR_ROOM_REQUEST_ENTER_ROOM_TIMEOUT
-3308
请求进房超时,请检查网络
ERR_SDK_APPID_INVALID
-3317
进房参数 sdkAppId 错误
ERR_ROOM_ID_INVALID
-3318
进房参数 roomId 错误
ERR_USER_ID_INVALID
-3319
进房参数 userID 不正确
3. 设备相关。可监听设备相关错误,在出现相关错误时 UI 提示用户。
枚举
取值
描述
ERR_CAMERA_START_FAIL
-1301
打开摄像头失败,例如在 Windows 或 Mac 设备,摄像头的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序
ERR_MIC_START_FAIL
-1302
打开麦克风失败,例如在 Windows 或 Mac 设备,麦克风的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序。
ERR_CAMERA_NOT_AUTHORIZED
-1314
摄像头设备未授权,通常在移动设备出现,可能是权限被用户拒绝了。
ERR_MIC_NOT_AUTHORIZED
-1317
麦克风设备未授权,通常在移动设备出现,可能是权限被用户拒绝了。
ERR_CAMERA_OCCUPY
-1316
摄像头正在被占用中,可尝试打开其他摄像头。
ERR_MIC_OCCUPY
-1319
麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败。

本地和远端镜像模式设置

默认情况下,TRTC 本地和远端画面的镜像模式策略如下:
使用前置摄像头采集:本地预览画面无镜像效果,远端观众观看有镜像效果。
使用后置摄像头采集:本地预览画面无镜像效果,远端观众观看无镜像效果。
当然,您也可以通过 setLocalRenderParamssetVideoEncoderMirror 分别自定义本地预览画面镜像效果,以及视频编码输出画面的镜像效果(远端观众及云端录制的镜像模式)。如果您希望同时设置本地预览的镜像效果和远端观众观看的镜像效果,请按照如下方法进行编码。
// 设置本地画面的渲染参数
TRTCRenderParams renderParams = new TRTCRenderParams();
renderParams.fillMode = TRTCVideoFillMode.fill;
renderParams.rotation = TRTCVideoRotation.rotation0;
renderParams.mirrorType = TRTCVideoMirrorType.enable;
trtcCloud.setLocalRenderParams(renderParams);

采集设备旋转导致观众画面旋转

如果您遇到采集设备(主播端手机)在旋转翻转时,观众端拉流观看的画面渲染方向发生变化,您可以通过关闭 SDK 重力感应来避免这种现象。
trtcCloud.setGravitySensorAdaptiveMode(TRTCGSensorMode.disable);
说明:
该接口只需在采集端(主播端)调用,更多关于横竖屏视频画面旋转的控制方案详见 视频画面旋转

RTMP 与 TRTC 互通问题

为降低客户接入门槛,TRTC 支持 RTMP 标准协议推拉流。您可根据实际情况选择安装 OBSFFmpeg 或其他 RTMP 库进行推流。
本功能目前已结束免费内测,接入的 RTMP 流会作为房间中的虚拟用户产生正常的通话费用。具体接入指引详见 实时音视频 通过 RTMP 与 TRTC 互通
RTMP 属于 TRTC 一个子模块,能与 TRTC 其他端互通,互通延迟正常情况下小于600ms,也可使用 TRTC 录制、转推等已有能力。网络架构如下图所示。




推流地址生成

rtmp://rtmp.rtc.qq.com/push/房间号?sdkappid=应用&userid=用户名&usersig=签名
注意:
本功能自2023年02月15日起结束免费内测,如需使用该功能需要订阅 TRTC 应用包月套餐尊享版或旗舰版来解锁功能。
本功能默认只支持字符串房间号,不超过64个字符;如需使用数字房间号,则需在推拉流 URL 地址中拼接参数 &use_number_room_id=1

摄像头缩放/对焦/切换问题

电商直播场景下,主播可能会对摄像头有自定义调整的需求,TRTC SDK 设备管理类下也有解决此类需求的相关接口。
1. 查询、设置摄像头的缩放倍数。
// 获取摄像头的最大缩放倍数(仅适用于移动端)
double zoomRatio = trtcCloud.getDeviceManager().getCameraZoomMaxRatio();
// 设置摄像头的缩放倍数(仅适用于移动端)
// 取值范围1-5,取值为1表示最远视角(正常镜头),取值为5表示最近视角(放大镜头);最大值推荐为5,若超过5,视频数据会变得模糊不清
trtcCloud.getDeviceManager().setCameraZoomRatio(zoomRatio);
2. 设置摄像头的对焦功能及位置。
// 开启或关闭摄像头的自动对焦功能(仅适用于移动端)
trtcCloud.getDeviceManager().enableCameraAutoFocus(true);
// 设置摄像头的对焦位置(仅适用于移动端)
// 使用该接口的前提是先通过 enableCameraAutoFocus 关闭自动对焦功能
trtcCloud.getDeviceManager().setCameraFocusPosition(x, y);
3. 判断、切换前置或后置摄像头。
// 判断当前是否为前置摄像头(仅适用于移动端)
bool isFrontCamera = trtcCloud.getDeviceManager().isFrontCamera();
// 切换前置或后置摄像头(仅适用于移动端)
// 传入true: 切换为前置;传入false: 切换为后置
trtcCloud.getDeviceManager().switchCamera(!isFrontCamera);