tencent cloud

云顾问-Tencent RTC 云助手

产品动态
产品简介
产品概述
产品优势
应用场景
购买指南
新手指引
场景化方案
场景化方案概述
社交娱乐
电商直播
音视频通话
远程实时操控
智能客服
AI 面试
模块化方案
模块化方案概述
网络质量监控
移动端应用保活方案
视频画中画方案
直播上下滑
跨房 PK 连麦方案
AI 对话 Chat 信令方案
常见问题
联系我们

iOS

PDF
聚焦模式
字号
最后更新时间: 2025-09-11 10:06:06

业务流程

本节汇总了语聊房中一些常见的业务流程,帮助您更好地理解整个场景的实现流程。
房间管理流程
房主麦位管理流程
听众麦位管理流程
下图展示了房间管理流程,包含创建、加入、退出、解散房间的实现流程。



下图展示了房主麦位管理流程,包含抱人上麦、踢人下麦、麦位禁音的实现流程。



下图展示了听众麦位管理流程,包含主动上麦、主动下麦、麦位移动的实现流程。




接入准备

步骤一:开通服务

语聊房场景通常需要依赖 ChatRTC Engine 两项付费 PaaS 服务构建。
1. 首先您需要登录 控制台 创建应用,创建一个 RTC Engine 应用,再创建一个Chat 应用



说明:
建议创建两个应用分别用于测试环境和生产环境,一年内每个账号(UIN)每月赠送10,000分钟免费时长。
RTC Engine 包月套餐分为体验版(默认)、轻量版标准专业版,可解锁不同的增值功能服务,详情可见 版本功能与包月套餐说明
2. 创建应用完毕之后,您可以在应用管理-应用概览栏目看到该应用的基本信息,其中需要您保管好 SDKAppIDSDKSecretKey 便于后续使用,同时应避免密钥泄露造成流量盗刷。




步骤二:导入 SDK

RTC Engine SDK 和 Chat SDK 已经发布到 CocoaPods,建议您通过 CocoaPods 集成 SDK。
1. 安装 CocoaPods。
在终端窗口中输入如下命令(需要提前在 Mac 中安装 Ruby 环境):
sudo gem install cocoapods
2. 创建 Podfile 文件。
进入项目所在路径,输入以下命令行之后项目路径下会出现一个 Podfile 文件。
pod init
3. 编辑 Podfile 文件。
根据您的项目需要选择合适的版本,并编辑 Podfile 文件。
platform :ios, '8.0'
target 'App' do

# TRTC 精简版
# 安装包体积增量最小,仅支持 RTC Engine 和 直播播放器(TXLivePlayer)两项功能。
pod 'TXLiteAVSDK_TRTC', :podspec => 'https://liteav.sdk.qcloud.com/pod/liteavsdkspec/TXLiteAVSDK_TRTC.podspec'
# Add the Chat SDK
pod 'TXIMSDK_Plus_iOS'
# pod 'TXIMSDK_Plus_iOS_XCFramework'
# pod 'TXIMSDK_Plus_Swift_iOS_XCFramework'
# If you need to add the Quic plugin, please uncomment the next line.
# Note: This plugin must be used with the Objective-C edition or XCFramework edition of the Chat SDK, and the plugin version number must match the Chat SDK version number.
# pod 'TXIMSDK_Plus_QuicPlugin'

end
4. 更新并安装 SDK。
在终端窗口中输入如下命令以更新本地库文件,并安装 SDK。
pod install
或使用以下命令更新本地库版本。
pod update
pod 命令执行完后,会生成集成了 SDK 的 .xcworkspace 后缀的工程文件,双击打开即可。
说明:
若 pod 搜索失败,建议尝试更新 pod 的本地 repo 缓存。更新命令如下。
pod setup
pod repo update
rm ~/Library/Caches/CocoaPods/search_index.json
除了 CocoaPods 集成方式,您还可以选择下载 SDK 并手动导入,详见 手动集成 RTC Engine SDK手动集成 Chat SDK
Quic 插件提供 axp-quic 多路传输协议,弱网抗性更优,网络丢包率达到 70% 的条件下,仍然可以提供服务。专业版、专业版 plus 和企业用户开放,请 购买专业版 、专业版 plus 或企业版 后可使用。为确保功能正常使用,请将终端 SDK 更新至7.7.5282及其以上的版本。

步骤三:工程配置

1. 语聊场景下 RTC Engine SDK 及 Chat SDK 需要 App 授权麦克风权限,在 App 的 Info.plist 中添加以下内容,对应麦克风在系统弹出授权对话框时的提示信息:
Privacy - Microphone Usage Description, 同时填入麦克风使用目的提示语



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




接入过程

步骤一:生成鉴权凭证

UserSig 是腾讯实时通信服务设计的一种安全保护签名,目的是为了阻止恶意攻击者盗用您的云服务使用权。RTC EngineChat 服务都采用了该套安全保护机制,RTC Engine 在进房时鉴权,Chat 在登录时鉴权。
调试跑通阶段:可以通过 客户端示例代码控制台获取 两种方法计算生成 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. 校验通过后,会向 Chat SDK 提供即时通信服务、RTC Engine SDK 提供实时音视频服务。



注意:
调试跑通阶段的本地 UserSig 计算方式不推荐应用到线上环境,容易被逆向破解导致密钥泄露。
我们提供了多个语言版本(Java/Go/PHP/Nodejs/Python/C#/C++)的 UserSig 服务端计算源代码,详见 服务端计算 UserSig

步骤二:初始化与监听

时序图




1. Chat SDK 初始化与添加事件监听器。
// 从Chat 控制台获取应用 SDKAppID。
// 添加 V2TIMSDKListener 的事件监听器,self 是 id<V2TIMSDKListener> 的实现类,如果您不需要监听 Chat SDK 的事件,这个步骤可以忽略。
[[V2TIMManager sharedInstance] addIMSDKListener:self];
// 初始化 Chat SDK,调用这个接口后,可以立即调用登录接口。
[[V2TIMManager sharedInstance] initSDK:sdkAppID config:config];

// SDK 初始化后会抛出一些事件,例如连接状态、登录票据过期等
- (void)onConnecting {
NSLog(@"Chat SDK 正在连接到腾讯云服务器");
}

- (void)onConnectSuccess {
NSLog(@"Chat SDK 已经成功连接到腾讯云服务器");
}

// 移除事件监听器
// self 是 id<V2TIMSDKListener> 的实现类
[[V2TIMManager sharedInstance] removeIMSDKListener:self];
// 反初始化 SDK
[[V2TIMManager sharedInstance] unInitSDK];
说明:
如果您的应用生命周期跟 SDK 生命周期一致,退出应用前可以不进行反初始化。若您只在进入特定界面后才初始化 SDK,退出界面后不再使用,可以对 SDK 进行反初始化。
2. RTC Engine SDK 创建实例与设置事件监听器。
// 创建 RTC Engine SDK 实例(单例模式)
_trtcCloud = [TRTCCloud sharedInstance];
// 设置事件监听器
_trtcCloud.delegate = self;

// 来自 SDK 的各类事件通知(比如:错误码,警告码,音视频状态参数等)
- (void)onError:(TXLiteAVError)errCode errMsg:(nullable NSString *)errMsg extInfo:(nullable NSDictionary *)extInfo {
NSLog(@"%d: %@", errCode, errMsg);
}

- (void)onWarning:(TXLiteAVWarning)warningCode warningMsg:(nullable NSString *)warningMsg extInfo:(nullable NSDictionary *)extInfo {
NSLog(@"%d: %@", warningCode, warningMsg);
}

// 移除事件监听器
_trtcCloud.delegate = nil;
// 销毁 RTC Engine SDK 实例(单例模式)
[TRTCCloud destroySharedIntance];
说明:
建议监听 SDK 事件通知,对一些常见错误进行日志打印和处理,详见 错误码表

步骤三:登录与登出

初始化 Chat SDK 后,您需要调用 SDK 登录接口验证账号身份,获得账号的功能使用权限。因此在使用其他功能之前,请务必确保登录成功,否则可能导致功能异常或不可用。如您仅需使用 RTC Engine 音视频服务,可忽略此步骤。

时序图




1. 登录。
// 登录:userID 可自定义,userSig 参考步骤一生成获取
[[V2TIMManager sharedInstance] login:userID userSig:userSig succ:^{
NSLog(@"success");
} fail:^(int code, NSString *desc) {
// 如果返回以下错误码,表示使用 UserSig 已过期,请您使用新签发的 UserSig 进行再次登录。
// 1. ERR_USER_SIG_EXPIRED(6206)
// 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED(70001)
// 注意:其他的错误码,请不要在这里调用登录接口,避免 Chat SDK 登录进入死循环。
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
2. 登出
// 登出
[[V2TIMManager sharedInstance] logout:^{
NSLog(@"success");
} fail:^(int code, NSString *desc) {
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
说明:
如果您的应用生命周期跟 Chat SDK 生命周期一致,退出应用前可以不登出。若您只在进入特定界面后才使用 Chat SDK,退出界面后不再使用,可以进行登出操作和对 Chat SDK 进行反初始化。

步骤四:房间管理

时序图




1. 创建房间。
主播(房主)开播时需要创建房间,这里的“房间”概念对应 Chat 中的“群组”。本例仅展示客户端创建 Chat 群组的方式,实际也可在 服务端创建群组
// 创建群组
[[V2TIMManager sharedInstance] createGroup:GroupType_AVChatRoom groupID:groupID groupName:groupName succ:^(NSString *groupID) {
// 创建群组成功
} fail:^(int code, NSString *desc) {
// 创建群组失败
}];


// 监听群组创建通知
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupCreated:(NSString *)groupID {
// 群创建回调,groupID 为新创建群组的 ID
}
注意:
语聊房场景创建 Chat 群组需要选用直播群类型:GroupType_AVChatRoom
RTC Engine 没有创建房间的 API,当用户要加入的房间不存在时,后台会自动创建一个房间。
2. 加入房间。
加入 Chat 群组。
// 加入群组
[[V2TIMManager sharedInstance] joinGroup:groupID msg:message succ:^{
// 加入群组成功
} fail:^(int code, NSString *desc) {
// 加入群组失败
}];

// 监听加入群组事件
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberEnter:(NSString *)groupID memberList:(NSArray<V2TIMGroupMemberInfo *>*)memberList {
// 有人加入群组
}
加入 RTC Engine 房间。
- (void)enterRoomWithRoomId:(NSString *)roomId userID:(NSString *)userId {
TRTCParams *params = [[TRTCParams alloc] init];
// 以字符串房间号为例,建议和 Chat 群组号保持一致
params.strRoomId = roomId;
params.userId = userId;
// 从业务后台获取到的 UserSig
params.userSig = getUserSig(userId);
// 替换成您的 SDKAppID
params.sdkAppId = SDKAppID;
// 语聊互动场景进房需指定用户角色
params.role = TRTCRoleAudience;
// 以语聊互动进房场景为例
[self.trtcCloud enterRoom:params appScene:TRTCAppSceneVoiceChatRoom];
}

// 进房结果事件回调
- (void)onEnterRoom:(NSInteger)result {
if (result > 0) {
// result 代表加入房间所消耗的时间(毫秒)
[self toastTip:@"Enter room succeed!"];
} else {
// result 代表进房失败的错误码
[self toastTip:@"Enter room failed!"];
}
}
注意:
RTC Engine 房间号分为整型 roomId 和字符串类型 strRoomId,两种类型的房间不互通,建议统一房间号类型。
语聊互动场景进房时须指定用户角色(主播/观众),只有主播才有推流权限,如未指定则默认为主播角色。
语聊互动进房场景建议选用 TRTCAppSceneVoiceChatRoom
3. 退出房间。
退出 Chat 群组。
[[V2TIMManager sharedInstance] quitGroup:groupID succ:^{
// 退出群组成功
} fail:^(int code, NSString *desc) {
// 退出群组失败
}];

[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberLeave:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member {
// 群成员离开回调
}
注意:
直播群(AVChatRoom)中,群主是不可以退群的,群主只能调用 dismissGroup 解散群组。
退出 RTC Engine 房间。
- (void)exitTrtcRoom {
self.trtcCloud = [TRTCCloud sharedInstance];
[self.trtcCloud stopLocalAudio];
[self.trtcCloud exitRoom];
}

// 监听 onExitRoom 回调即可获知自己的退房原因
- (void)onExitRoom:(NSInteger)reason {
if (reason == 0) {
// 主动调用 exitRoom 退出房间
NSLog(@"Exit current room by calling the 'exitRoom' api of sdk ...");
} else if (reason == 1) {
// 被服务器踢出当前房间
NSLog(@"Kicked out of the current room by server through the restful api...");
} else if (reason == 2) {
// 当前房间整个被解散
NSLog(@"Current room is dissolved by server through the restful api...");
}
}
注意:
待 SDK 占用的所有资源释放完毕后,SDK 会抛出 onExitRoom 回调通知到您。
如果您要再次调用 enterRoom 或者切换到其他的音视频 SDK,请等待 onExitRoom 回调到来后再执行相关操作。否则可能会遇到例如摄像头、麦克风设备被强占等各种异常问题。
4. 解散房间。
解散 Chat 群组。
本例仅展示客户端解散 Chat 群组的方式,实际也可在 服务端解散群组
[[V2TIMManager sharedInstance] dismissGroup:groupID succ:^{
// 解散群组成功
} fail:^(int code, NSString *desc) {
// 解散群组失败
}];

[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
// 群被解散回调
}
解散 RTC Engine 房间。
服务端解散RTC Engine 提供了 服务端解散房间 API DismissRoom(区分数字房间 ID 和字符串房间 ID),您可以调用此接口把房间所有用户从房间移出,并解散房间。
客户端解散:通过各个客户端的退出房间 exitRoom 接口,将房间内的所有主播和听众完成退房,退房后,根据 RTC Engine 房间生命周期规则,房间将会自动解散,详情请参见 退出房间
警告:
建议当您的一次直播任务结束后,可以调用解散房间 API 确保房间解散,防止听众意外进房导致产生非期望的费用。

步骤五:麦位管理

时序图




首先,我们可以创建一个用于保存麦位信息的 model。
#import "JSONModel.h"

typedef NS_ENUM(NSUInteger, SeatInfoStatus) {
SeatInfoStatusUnused = 0,
SeatInfoStatusUsed = 1,
SeatInfoStatusLocked = 2,
};

NS_ASSUME_NONNULL_BEGIN

@interface SeatInfoModel : JSONModel

/// 座位状态,对应三种状态
@property (nonatomic, assign) SeatInfoStatus status;
/// 座位是否禁言
@property (nonatomic, assign) BOOL mute;
/// 座位占用时,存储用户信息
@property (nonatomic, copy) NSString *userId;

@end

NS_ASSUME_NONNULL_END
1. 主动上麦。
主动上麦是指麦下听众向房主或管理员发送上麦申请,待接收到同意信令后上麦。如为自由上麦模式,则可忽略信令请求部分。
听众发送上麦请求
// 听众发送上麦请求,userId 为主播 ID,data 可传入标识信令的 json
- (void)sendInvitationWithUserId:(NSString *)userId data:(NSString *)data {
[[V2TIMManager sharedInstance] invite:userId data:data onlineUserOnly:YES offlinePushInfo:nil timeout:0 succ:^{
NSLog(@"sendInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"sendInvitation error %d", code);
}];
}

// 主播收到上麦请求, inviteID 为该条请求 ID,inviter 为请求者 ID
[[V2TIMManager sharedInstance] addSignalingListener:self];
- (void)onReceiveNewInvitation:(NSString *)inviteID inviter:(NSString *)inviter groupID:(NSString *)groupID inviteeList:(NSArray<NSString *> *)inviteeList data:(NSString * __nullable)data {
NSLog(@"received invitation: %@ from %@", inviteID, inviter);
}
主播处理上麦请求
// 同意上麦请求
- (void)acceptInvitationWithInviteID:(NSString *)inviteID data:(NSString *)data {
[[V2TIMManager sharedInstance] accept:inviteID data:data succ:^{
NSLog(@"acceptInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"acceptInvitation error %d", code);
}];
}

// 拒绝上麦请求
- (void)rejectInvitationWithInviteID:(NSString *)inviteID data:(NSString *)data {
[[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{
NSLog(@"rejectInvitation success");
} fail:^(int code, NSString *desc) {
NSLog(@"rejectInvitation error %d", code);
}];
}
听众上麦
如果主播同意听众的上麦请求,听众可以通过修改群属性的方式添加麦位信息,其他用户会收到群属性变更回调,更新本地麦位信息。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 同意上麦请求的回调
- (void)onInviteeAccepted:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString * __nullable)data {
NSLog(@"received accept invitation: %@ from %@", inviteID, invitee);
NSInteger seatIndex = [self findSeatIndex:inviteID];
[self takeSeatWithIndex:seatIndex];
}

// 听众开始上麦
- (void)takeSeatWithIndex:(NSInteger)seatIndex {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUsed;
seatInfo.mute = localInfo.mute;
seatInfo.userId = self.userId;
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,切换 TRTC 角色并开始推流
[self.trtcCloud switchRole:TRTCRoleAnchor];
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];
} fail:^(int code, NSString *desc) {
// 修改群属性失败,上麦失败
}];
}
2. 抱人上麦。
主播抱人上麦(无需听众同意),直接修改群属性保存的麦位信息,对应听众收到群属性变更回调后匹配 userId 成功即可切换 RTC Engine 角色并开始推流。如为邀请上麦模式,可参照主动上麦的实现逻辑,只需调换信令的发送方与接收方即可。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 主播端调用该接口修改群属性保存的麦位信息
- (void)pickSeatWithUserId:(NSString *)userId seatIndex:(NSInteger)seatIndex {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUsed;
seatInfo.mute = localInfo.mute;
seatInfo.userId = self.userId;
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,触发 onGroupAttributeChanged 回调
} fail:^(int code, NSString *desc) {
// 修改群属性失败,上麦失败
}];
}

// 听众端收到群属性变更回调,匹配自身信息成功后开始推流
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 上一次本地保存的全量麦位信息列表
NSArray *oldSeatArray = self.seatInfoArray;
// 最新从 groupAttributeMap 中解析的全量麦位信息列表
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 遍历全量麦位信息列表,对比新旧麦位信息
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusUsed) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 匹配自身信息成功,切换 TRTC 角色并开始推流
[self.trtcCloud switchRole:TRTCRoleAnchor];
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];
} else {
// 更新本地麦位列表,渲染本地麦位视图
}
}
}
}
3. 主动下麦。
连麦听众可以通过修改群属性的方式重置麦位信息,其他用户会收到群属性变更回调,更新本地麦位信息。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

- (void)leaveSeatWithIndex:(NSInteger)seatIndex {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,切换 TRTC 角色并停止推流
[self.trtcCloud switchRole:TRTCRoleAudience];
[self.trtcCloud stopLocalAudio];
} fail:^(int code, NSString *desc) {
// 修改群属性失败,下麦失败
}];
}
4. 踢人下麦。
主播踢人下麦,直接修改群属性保存的麦位信息,对应连麦听众收到群属性变更回调后匹配 userId 成功即可切换 RTC Engine 角色并停止推流。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 主播端调用该接口修改群属性保存的麦位信息
- (void)kickSeatWithIndex:(NSInteger)seatIndex {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,触发 onGroupAttributeChanged 回调
} fail:^(int code, NSString *desc) {
// 修改群属性失败,踢麦失败
}];
}

// 连麦听众端收到群属性变更回调,匹配自身信息成功后停止推流
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 上一次本地保存的全量麦位信息列表
NSArray *oldSeatArray = self.seatInfoArray;
// 最新从 groupAttributeMap 中解析的全量麦位信息列表
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 遍历全量麦位信息列表,对比新旧麦位信息
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusUnused) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 匹配自身信息成功,切换 TRTC 角色并停止推流
[self.trtcCloud switchRole:TRTCRoleAudience];
[self.trtcCloud stopLocalAudio];
} else {
// 更新本地麦位列表,渲染本地麦位视图
}
}
}
}
5. 麦位禁音。
主播禁音/解禁某个麦位,直接修改群属性保存的麦位信息,对应连麦听众收到群属性变更回调后匹配 userId 成功即可暂停/恢复本地推流。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 主播端调用该接口修改群属性保存的麦位信息
- (void)muteSeatWithIndex:(NSInteger)seatIndex mute:(BOOL)mute {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = localInfo.status;
seatInfo.mute = mute;
seatInfo.userId = localInfo.userId;
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,触发 onGroupAttributeChanged 回调
} fail:^(int code, NSString *desc) {
// 修改群属性失败,禁麦失败
}];
}

// 连麦听众端收到群属性变更回调,匹配自身信息成功后暂停/恢复推流
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 上一次本地保存的全量麦位信息列表
NSArray *oldSeatArray = self.seatInfoArray;
// 最新从 groupAttributeMap 中解析的全量麦位信息列表
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 遍历全量麦位信息列表,对比新旧麦位信息
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.mute != newInfo.mute) {
if ([newInfo.userId isEqualToString:self.userId]) {
// 匹配自身信息成功,暂停/恢复本地推流
[self.trtcCloud muteLocalAudio:newInfo.mute];
} else {
// 更新本地麦位列表,渲染本地麦位视图
}
}
}
}
6. 麦位锁定。
主播锁定/解锁某个麦位,直接修改群属性保存的麦位信息,听众收到群属性变更回调后更新对应麦位视图。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 主播端调用该接口修改群属性保存的麦位信息
- (void)lockSeatWithIndex:(NSInteger)seatIndex isLock:(BOOL)isLock {
// 创建麦位信息实例,存储修改后的麦位信息
SeatInfoModel *localInfo = self.seatInfoArray[seatIndex];
SeatInfoModel *seatInfo = [[SeatInfoModel alloc] init];
seatInfo.status = isLock? SeatInfoStatusLocked : SeatInfoStatusUnused;
seatInfo.mute = localInfo.mute;
seatInfo.userId = @"";
// 将麦位信息对象序列化为 JSON 格式
NSString *jsonStr = seatInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", seatIndex]: jsonStr };
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,触发 onGroupAttributeChanged 回调
} fail:^(int code, NSString *desc) {
// 修改群属性失败,锁麦失败
}];
}

// 听众端收到群属性变更回调,更新对应麦位视图
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupAttributeChanged:(NSString *)groupID attributes:(NSMutableDictionary<NSString *,NSString *> *)attributes {
// 上一次本地保存的全量麦位信息列表
NSArray *oldSeatArray = self.seatInfoArray;
// 最新从 groupAttributeMap 中解析的全量麦位信息列表
NSArray *newSeatArray = [self getSeatListFromAttr:attributes seatSize:self.seatSize];
// 遍历全量麦位信息列表,对比新旧麦位信息
for (int i = 0; i < self.seatSize; i++) {
SeatInfoModel *oldInfo = oldSeatArray[i];
SeatInfoModel *newInfo = newSeatArray[i];
if (oldInfo.status == SeatInfoStatusLocked && newInfo.status == SeatInfoStatusUnused) {
// 解锁麦位
} else if (oldInfo.status != newInfo.status && newInfo.status == SeatInfoStatusLocked) {
// 锁定麦位
}
}
}
7. 麦位移动。
麦上主播移动麦位,需要分别修改群属性保存的源和目标麦位信息,听众收到群属性变更回调后更新对应麦位视图。
// 本地保存的全量麦位信息列表
@property (nonatomic, copy) NSArray<SeatInfoModel *> *seatInfoArray;

// 麦上主播调用该接口修改群属性保存的麦位信息
- (void)moveSeatToIndex:(NSInteger)dstIndex {
// 根据 userId 获取源麦位编号
__block NSInteger srcIndex = -1;
[self.seatInfoArray enumerateObjectsUsingBlock:^(SeatInfoModel * _Nonnull seatInfo, NSUInteger idx, BOOL * _Nonnull stop) {
if ([seatInfo.userId isEqualToString:self.userId]) {
srcIndex = idx;
*stop = YES;
}
}];
if (srcIndex < 0 || dstIndex < 0 || dstIndex >= self.seatInfoArray.count) {
return;
}
// 根据麦位编号获取对应麦位信息
SeatInfoModel *srcSeatInfo = self.seatInfoArray[srcIndex];
SeatInfoModel *dstSeatInfo = self.seatInfoArray[dstIndex];
// 创建麦位信息实例,存储修改后的源麦位信息
SeatInfoModel *srcChangeInfo = [[SeatInfoModel alloc] init];
srcChangeInfo.status = SeatInfoStatusUnused;
srcChangeInfo.mute = srcSeatInfo.mute;
srcChangeInfo.userId = @"";
// 创建麦位信息实例,存储修改后的目标麦位信息
SeatInfoModel *dstChangeInfo = [[SeatInfoModel alloc] init];
dstChangeInfo.status = SeatInfoStatusUsed;
dstChangeInfo.mute = dstSeatInfo.mute;
dstChangeInfo.userId = self.userId;
// 将麦位信息对象序列化为 JSON 格式
NSString *srcJsonStr = srcChangeInfo.toJSONString;
NSString *dstJsonStr = dstChangeInfo.toJSONString;
NSDictionary *dict = @{ [NSString stringWithFormat:@"seat%ld", srcIndex]: srcJsonStr,
[NSString stringWithFormat:@"seat%ld", dstIndex]: dstJsonStr
};
// 设置群属性,已有该群属性则更新其 value 值,没有该群属性则添加该属性
[[V2TIMManager sharedInstance] setGroupAttributes:self.groupId attributes:dict succ:^{
// 修改群属性成功,移麦成功
} fail:^(int code, NSString *desc) {
// 修改群属性失败,移麦失败
}];
}

步骤六:音频管理

时序图




1. 订阅模式。
RTC Engine SDK 默认为自动订阅音频流逻辑,用户进房会自动开始播放远端用户的声音。如有手动订阅音频流的需求,需要额外调用 muteRemoteAudio(userId, mute) 订阅和播放远端用户音频流。
// 自动订阅模式(默认)
[self.trtcCloud setDefaultStreamRecvMode:YES video:YES];

// 手动订阅模式(自定义)
[self.trtcCloud setDefaultStreamRecvMode:NO video:NO];
注意:
设置订阅模式 setDefaultStreamRecvMode 必须在进房 enterRoom 之前调用方可生效。
2. 采集与发布。
// 开启本地音频的采集和发布
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];

// 停止本地音频的采集和发布
[self.trtcCloud stopLocalAudio];
说明:
startLocalAudio 会申请麦克风使用权限,stopLocalAudio 会释放麦克风使用权限。
3. 闭麦与开麦。
// 暂停发布本地音频流(闭麦)
[self.trtcCloud muteLocalAudio:YES];
// 恢复发布本地音频流(开麦)
[self.trtcCloud muteLocalAudio:NO];

// 暂停订阅和播放某远端用户的音频流
[self.trtcCloud muteRemoteAudio:userId mute:YES];
// 恢复订阅和播放某远端用户的音频流
[self.trtcCloud muteRemoteAudio:userId mute:NO];

// 暂停订阅和播放所有远端用户的音频流
[self.trtcCloud muteAllRemoteAudio:YES];
// 恢复订阅和播放所有远端用户的音频流
[self.trtcCloud muteAllRemoteAudio:NO];
说明:
相比之下,muteLocalAudio 只需要在软件层面对数据流进行暂停或者放行即可,因此效率更高更平滑,也更适合需要频繁开闭麦的场景。
4. 音质及音量类型。
音质设置
// 本地音频采集和发布时设置音质
[self.trtcCloud startLocalAudio:TRTCAudioQualityDefault];

// 音频推流过程中动态设置音质
[self.trtcCloud setAudioQuality:TRTCAudioQualityDefault];
说明:
RTC Engine 预设音质共分为三档(Speech/Default/Music)分别对应不同的音频参数,详见 TRTCAudioQuality
音量类型设置
RTC Engine 每档音质都对应有默认的音量类型,如需强制指定音量类型可以使用如下接口。
// 设置音量类型
[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeAuto];
说明:
RTC Engine 音量类型共分为三档(VOIP/Auto/Media)分别对应不同的音量通道,详见 TRTCSystemVolumeType
音频路由设置
手机等移动端设备上通常有扬声器和听筒两个播放位置,如需强制指定音频路由可以使用如下接口。
// 设置音频路由
[self.trtcCloud setAudioRoute:TRTCAudioModeSpeakerphone];
说明:
RTC Engine 音频路由共分为两种(Speaker/Earpiece)分别对应不同的发声位置,详见 TRTCAudioRoute

高级功能

弹幕消息互动

语聊直播间通常会有文本形式的弹幕消息互动,这里可以通过 Chat 的发送及接收群聊普通文本消息来实现。
// 发送公屏弹幕消息
[[V2TIMManager sharedInstance] sendGroupTextMessage:text to:groupID priority:V2TIM_PRIORITY_NORMAL succ:^{
// 发送弹幕消息成功
} fail:^(int code, NSString *desc) {
// 发送弹幕消息失败
}];

// 接收公屏弹幕消息
[[V2TIMManager sharedInstance] addSimpleMsgListener:self];
- (void)onRecvGroupTextMessage:(NSString *)msgID groupID:(NSString *)groupID sender:(V2TIMGroupMemberInfo *)info text:(NSString *)text {
NSLog(@"%@: %@", info.nickName, text);
}

音量大小回调

RTC Engine 可以按照固定频率回调麦上主播的音量大小,通常用于展示音波音浪,提示正在发言的主播。
// 启用音量大小回调,建议在进房成功后即开启
// interval: 回调间隔(ms); enable_vad: 是否开启人声检测
[self.trtcCloud enableAudioVolumeEvaluation:interval enable_vad:enable_vad];
self.trtcCloud.delegate = self;

- (void)onUserVoiceVolume:(NSArray<TRTCVolumeInfo *> *)userVolumes totalVolume:(NSInteger)totalVolume {
// userVolumes 用于承载所有正在说话的用户的音量大小,包括本地用户和远端推流用户
// totalVolume 用于反馈远端推流用户中的最大音量值
...
// 根据音量大小在 UI 上做出相应的音浪展示
...
}
注意:
人声检测仅反馈本地人声检测结果,且自身角色必须为主播,方便提示用户开麦。
userVolumes 为一个数组,对于数组中的每一个元素,当 userId 为空时表示本地麦克风采集的音量大小,当 userId 不为空时代表远端用户的音量大小。

音乐及音效播放

播放背景音乐及音效是语聊房场景中的高频需求,下面将对常用的背景音乐相关接口的使用及注意事项进行说明。
1. 开始/停止/暂停/恢复播放。
// 获取用于对背景音乐、短音效和人声特效进行设置的管理类
self.audioEffectManager = [self.trtcCloud getAudioEffectManager];

TXAudioMusicParam *param = [[TXAudioMusicParam alloc] init];
param.ID = musicID;
param.path = musicPath;
// 是否将音乐发布到远端(否则仅本地播放)
param.publish = YES;
// 播放的是否为短音效文件
param.isShortFile = NO;

// 开始播放背景音乐
__weak typeof(self) weakSelf = self;
[self.audioEffectManager startPlayMusic:param onStart:^(NSInteger errCode) {
__strong typeof(weakSelf) strongSelf = weakSelf;
// 播放开始回调
// -4001: 路径打开失败
// -4002: 解码失败
// -4003: URL地址无效
// -4004: 播放未停止
if (errCode < 0) {
// 播放失败后重新播放前需要先停止播放当前音乐
[strongSelf.audioEffectManager stopPlayMusic:musicID];
}
} onProgress:^(NSInteger progressMs, NSInteger durationMs) {
// 播放进度回调
// progressMs 当前播放时长(毫秒)
// durationMs 当前音乐总时长(毫秒)
} onComplete:^(NSInteger errCode) {
// 播放结束回调
// 播放中途因弱网导致的播放失败也会抛出此回调,此时 errCode < 0
// 中途暂停播放或停止播放不会触发 onComplete 回调
}];
// 停止播放背景音乐
[self.audioEffectManager stopPlayMusic:musicID];
// 暂停播放背景音乐
[self.audioEffectManager pausePlayMusic:musicID];
// 恢复播放背景音乐
[self.audioEffectManager resumePlayMusic:musicID];
注意:
RTC Engine 支持同时播放多首音乐,通过 musicID 唯一标识,若您想要同一时刻只播放一首音乐,需要注意在开始播放前停止播放其他音乐,或者可以使用同一个 musicID 来播放不同的音乐,这样 SDK 会先停止播放旧的音乐,再播放新的音乐。
RTC Engine 支持播放本地和网络音频文件,通过 musicPath 传入本地绝对路径 或 URL 地址,支持 MP3/AAC/M4A/WAV 格式。
2. 调节音乐及人声音量占比。
// 设置某一首背景音乐的本地播放音量的大小
[self.audioEffectManager setMusicPlayoutVolume:musicID volume:volume];
// 设置某一首背景音乐的远端播放音量的大小
[self.audioEffectManager setMusicPublishVolume:musicID volume:volume];
// 设置所有背景音乐的本地音量和远端音量的大小
[self.audioEffectManager setAllMusicVolume:volume];
// 设置人声采集音量的大小
[self.audioEffectManager setVoiceVolume:volume];
注意:
音量值 volume 正常取值范围为0-100,默认值为60,最大可设为150,但有爆音风险。
如果出现背景音乐压制人声的情况,可适当调低音乐播放音量,调高人声采集音量。
闭麦不禁背景音乐:使用 setVoiceVolume(0) 替代 muteLocalAudio(true)
3. 循环播放背景音乐及音效。
方案一:使用 AudioMusicParam 中的 loopCount 参数设置循环播放次数。
取值范围为0 - 任意正整数,默认值:0。0 表示播放音乐一次;1 表示播放音乐两次;以此类推。
- (void)startPlayMusicWithId:(int32_t)musicId path:(NSString *)path loopCount:(NSInteger)loopCount {
TXAudioMusicParam *param = [[TXAudioMusicParam alloc] init];
param.ID = musicId;
param.path = path;
param.publish = YES;
// 播放的是否为短音效文件
param.isShortFile = YES;
// 设定循环播放次数,负数表示无限循环
param.loopCount = loopCount < 0 ? NSIntegerMax : loopCount;
[self.audioEffectManager startPlayMusic:param onStart:nil onProgress:nil onComplete:nil];
}
说明:
方案一每次循环播放完毕并不会触发 onComplete 回调,只有等设置的循环次数全部播放完毕才会触发该回调。
方案二:通过“背景音乐已经播放完毕”的事件回调 onComplete 来实现循环播放,通常用于列表循环或单曲循环。
- (void)repeatPlayMusicWithParam:(TXAudioMusicParam *)param {
__weak typeof(self) weakSelf = self;
[self.audioEffectManager startPlayMusic:param onStart:nil onProgress:nil onComplete:^(NSInteger errCode) {
__strong typeof(weakSelf) strongSelf = weakSelf;
// 可以在此重新调用播放接口,以实现音乐的循环播放
if (errCode >= 0) {
[strongSelf repeatPlayMusicWithParam:param];
}
}];
}

混流转推及回推

1. 混流回推 RTC Engine 房间。
- (void)startPublishMediaToRoom:(NSString *)roomId userID:(NSString *)userId {
// 媒体流发布的目标地址
TRTCPublishTarget *target = [[TRTCPublishTarget alloc] init];
// 混流后回推到房间
target.mode = TRTCPublishMixStreamToRoom;
target.mixStreamIdentity.strRoomId = roomId;
// 混流机器人的 userid,不能和房间内其他用户的 userid 重复
target.mixStreamIdentity.userId = [NSString stringWithFormat:@"%@%@", userId, MIX_ROBOT];
TRTCStreamEncoderParam* encoderParam = [[TRTCStreamEncoderParam alloc] init];
// 设置转码后的音频流的编码参数(可自定义)
encoderParam.audioEncodedSampleRate = 48000;
encoderParam.audioEncodedChannelNum = 2;
encoderParam.audioEncodedKbps = 64;
encoderParam.audioEncodedCodecType = 2;
// 设置转码后的视频流的编码参数(纯音频混流可忽略)
encoderParam.videoEncodedWidth = 64;
encoderParam.videoEncodedHeight = 64;
encoderParam.videoEncodedFPS = 15;
encoderParam.videoEncodedGOP = 3;
encoderParam.videoEncodedKbps = 30;
// 设置音频混流参数
TRTCStreamMixingConfig *config = [[TRTCStreamMixingConfig alloc] init];
// 默认情况下填空值即可,代表会混合房间中的所有音频
config.audioMixUserList = nil;
// 配置视频混流模板(纯音频混流可忽略)
TRTCVideoLayout *layout = [[TRTCVideoLayout alloc] init];
config.videoLayoutList = @[layout];
// 开始混流转推
[self.trtcCloud startPublishMediaStream:target encoderParam:encoderParam mixingConfig:config];
}
2. 事件回调及更新停止任务。
任务结果事件回调
#pragma mark - TRTCCloudDelegate

- (void)onStartPublishMediaStream:(NSString *)taskId code:(int)code message:(NSString *)message extraInfo:(NSDictionary *)extraInfo {
// taskId: 当请求成功时,TRTC 后台会在回调中提供给您这项任务的 taskId,后续您可以通过该 taskId 结合 updatePublishMediaStream 和 stopPublishMediaStream 进行更新和停止
// code: 回调结果,0 表示成功,其余值表示失败
}

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

- (void)onStopPublishMediaStream:(NSString *)taskId code:(int)code message:(NSString *)message extraInfo:(NSDictionary *)extraInfo {
// 您调用停止发布媒体流 (stopPublishMediaStream) 时传入的 taskId,会通过此回调再带回给您,用于标识该回调属于哪一次停止请求
// code: 回调结果,0 表示成功,其余值表示失败
}
更新发布媒体流
该接口会向 RTC Engine 服务器发送指令,更新通过 startPublishMediaStream 启动的媒体流。
// taskId: 通过 onStartPublishMediaStream 回调的任务 ID
// target: 例如增删发布的 CDN URL
// params: 建议保持媒体流编码输出参数一致,避免播放侧断流
// config: 更新参与混流转码的用户列表,例如跨房 PK
[self.trtcCloud updatePublishMediaStream:taskId publishTarget:target encoderParam:trtcStreamEncoderParam mixingConfig:trtcStreamMixingConfig];
注意:
同一个任务不支持纯音频、音视频、纯视频之间的切换。
停止发布媒体流
该接口会向 RTC Engine 服务器发送指令,停止通过 startPublishMediaStream 启动的媒体流。
// taskId: 通过 onStartPublishMediaStream 回调的任务 ID
[self.trtcCloud stopPublishMediaStream:taskId];
注意:
若 taskId 填空字符串,将会停止该用户所有通过 startPublishMediaStream 启动的媒体流,如果您只启动了一个媒体流或者想停止所有通过您启动的媒体流,推荐使用这种方式。

网络质量实时回调

可以通过监听 onNetworkQuality 来实时统计本地及远端用户的网络质量,该回调每隔2秒抛出一次。
#pragma mark - TRTCCloudDelegate

- (void)onNetworkQuality:(TRTCQualityInfo *)localQuality remoteQuality:(NSArray<TRTCQualityInfo *> *)remoteQuality {
// localQuality userId 为空,代表本地用户网络质量评估结果
// remoteQuality 代表远端用户网络质量评估结果,其结果受远端和本地共同影响
switch(localQuality.quality) {
case TRTCQuality_Unknown:
NSLog(@"未定义");
break;
case TRTCQuality_Excellent:
NSLog(@"当前网络非常好");
break;
case TRTCQuality_Good:
NSLog(@"当前网络比较好");
break;
case TRTCQuality_Poor:
NSLog(@"当前网络一般");
break;
case TRTCQuality_Bad:
NSLog(@"当前网络较差");
break;
case TRTCQuality_Vbad:
NSLog(@"当前网络很差");
break;
case TRTCQuality_Down:
NSLog(@"当前网络不满足 TRTC 最低要求");
break;
default:
break;
}
}

高级权限控制

RTC Engine 高级权限控制可用于对不同房间设置不同进入权限,例如高级 VIP 房;也可用于控制听众上麦权限,例如处理幽灵麦。具体的操作步骤如下:
1. RTC Engine 控制台 应用的功能配置页面打开高级权限控制开关。
2. 在业务后台生成 PrivateMapKey,代码示例参考 PrivateMapKey 计算源码
3. 进房校验&上麦校验 PrivateMapKey。
进房校验
TRTCParams *params = [[TRTCParams alloc] init];
params.sdkAppId = SDKAppID;
params.roomId = self.roomId;
params.userId = self.userId;
// 从业务后台获取到的 UserSig
params.userSig = [self getUserSig];
// 从业务后台获取到的 PrivateMapKey
params.privateMapKey = [self getPrivateMapKey];
params.role = TRTCRoleAudience;
[self.trtcCloud enterRoom:params appScene:TRTCAppSceneVoiceChatRoom];
上麦校验
// 从业务后台获取到最新的 PrivateMapKey 传入切换角色接口
[self.trtcCloud switchRole:TRTCRoleAnchor privateMapKey:[self getPrivateMapKey]];

异常处理

异常错误处理

RTC Engine SDK 遇到不可恢复的错误会在 onError 回调中抛出,详见 错误码表
UserSig 相关。
UserSig 校验失败会导致进房失败,您可使用 UserSig 工具 进行校验。
枚举
取值
描述
ERR_TRTC_INVALID_USER_SIG
-3320
进房参数 UserSig 不正确,请检查 TRTCParams.userSig 是否为空。
ERR_TRTC_USER_SIG_CHECK_FAILED
-100018
UserSig 校验失败,请检查参数 TRTCParams.userSig 是否填写正确或已经过期。
进退房相关。
进房失败请先检查进房参数是否正确,且进退房接口必须成对调用,即便进房失败也需要调用退房接口。
枚举
取值
描述
ERR_TRTC_CONNECT_SERVER_TIMEOUT
-3308
请求进房超时,请检查是否断网或者是否开启 VPN,您也可以切换 4G 进行测试。
ERR_TRTC_INVALID_SDK_APPID
-3317
进房参数 SDKAppId 错误,请检查 TRTCParams.sdkAppId 是否为空。
ERR_TRTC_INVALID_ROOM_ID
-3318
进房参数 roomId 错误,请检查 TRTCParams.roomIdTRTCParams.strRoomId 是否为空,注意 roomId 和 strRoomId 不可混用。
ERR_TRTC_INVALID_USER_ID
-3319
进房参数 UserID 不正确,请检查 TRTCParams.userId 是否为空。
ERR_TRTC_ENTER_ROOM_REFUSED
-3340
进房请求被拒绝,请检查是否连续调用 enterRoom 进入相同 ID 的房间。
设备相关。
可监听设备相关错误,在出现相关错误时 UI 提示用户。
枚举
取值
描述
ERR_MIC_START_FAIL
-1302
打开麦克风失败,例如在 Windows 或 Mac 设备,麦克风的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序。
ERR_SPEAKER_START_FAIL
-1321
打开扬声器失败,例如在 Windows 或 Mac 设备,扬声器的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序。
ERR_MIC_OCCUPY
-1319
麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败。

异常退出处理

1. 断网感知与超时退房。
可以通过以下回调监听 RTC Engine 断网和重连事件通知。
收到 onConnectionLost 回调后可在本地麦位 UI 展示断网标识提醒用户,同时本地启动一个计时器,当超过设定时间阈值后仍然没有收到 onConnectionRecovery 回调,即网络持续处于断连状态,此时可本地启动下麦和退房流程,同时弹窗提醒用户已退出房间并销毁页面。若断网超过90秒(默认)会触发超时退房,RTC Engine 服务端会将该用户踢出房间,如果该用户为主播角色,则房间内其他用户会收到 onRemoteUserLeaveRoom 回调。
#pragma mark - TRTCCloudDelegate

- (void)onConnectionLost {
// SDK 与云端的连接已经断开
}

- (void)onTryToReconnect {
// SDK 正在尝试重新连接到云端
}

- (void)onConnectionRecovery {
// SDK 与云端的连接已经恢复
}
2. 离线状态下自动下麦。
Chat 用户的普通状态分为在线(ONLINE)、离线(OFFLINE)、未登录(UNLOGINED),其中离线状态通常是由于用户强杀进程或网络异常中断导致的。您可以通过主播订阅连麦听众用户状态来检测离线连麦听众,从而将其踢下麦。
// 主播订阅连麦听众用户状态
[[V2TIMManager sharedInstance] subscribeUserStatus:userList succ:^{
// 订阅用户状态成功
} fail:^(int code, NSString *desc) {
// 订阅用户状态失败
}];

// 主播取消订阅下麦听众用户状态
[[V2TIMManager sharedInstance] unsubscribeUserStatus:userList succ:^{
// 取消订阅用户状态成功
} fail:^(int code, NSString *desc) {
// 取消订阅用户状态失败
}];

// 用户状态变更通知与处理
[[V2TIMManager sharedInstance] addIMSDKListener:self];

- (void)onUserStatusChanged:(NSArray<V2TIMUserStatus *> *)userStatusList {
for (V2TIMUserStatus *userStatus in userStatusList) {
NSString *userId = userStatus.userID;
V2TIMUserStatusType status = userStatus.statusType;
if (status == V2TIM_USER_STATUS_OFFLINE) {
// 离线状态执行踢麦
[self kickSeatWithIndex:[self getSeatIndexWithUserId:userId]];
}
}
}

注意:
订阅用户状态需要升级到专业版套餐,详情请参见 基础服务详情
订阅用户状态需要提前在 Chat 控制台 开启 用户状态查询及状态变更通知配置,如果未开启则调用 subscribeUserStatus 会报错。

服务端踢人及解散房间

1. 服务端踢人。
首先调用 RTC Engine 服务端踢人接口 RemoveUser(整型房间号)或 RemoveUserByStrRoomId(字符串房间号)将目标用户踢出 RTC Engine 房间,输入示例如下:
https://trtc.tencentcloudapi.com/?Action=RemoveUser
&SdkAppId=1400000001
&RoomId=1234
&UserIds.0=test1
&UserIds.1=test2
&<公共请求参数>
执行踢人成功后,目标用户在客户端会收到 onExitRoom() 回调,且 reason 值为1。此时您可以在该回调中处理下麦、退出 Chat 群组等操作。
// 离开 TRTC 房间事件回调
- (void)onExitRoom:(NSInteger)reason {
if (reason == 0) {
// 主动调用 exitRoom 退出房间
NSLog(@"Exit current room by calling the 'exitRoom' api of sdk ...");
} else {
// reason 1: 被服务器踢出当前房间
// reason 2: 当前房间被整个解散
NSLog(@"Kicked out of the current room by server or current room is dissolved ...");
// 下麦
[self leaveSeatWithIndex:seatIndex];
// 退出 Chat 群组
[[V2TIMManager sharedInstance] quitGroup:groupID succ:^{
// 退出群组成功
} fail:^(int code, NSString *desc) {
// 退出群组失败
}];
}
}
2. 服务端解散房间。
首先调用 Chat 服务端解散群组接口 destroy_group 解散目标群组,请求 URL 示例如下。
https://xxxxxx/v4/group_open_http_svc/destroy_group?sdkappid=88888888&identifier=admin&usersig=xxx&random=99999999&contenttype=json
执行解散群组成功后,目标群组内的全部成员在客户端均会收到 onGroupDismissed() 回调。此时您可以在该回调中处理退出 RTC Engine 房间等操作。
// 群组被解散回调
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
// 退出 TRTC 房间
[self.trtcCloud stopLocalAudio];
[self.trtcCloud exitRoom];
}
说明:
当房间内所有用户调用 exitRoom() 完成退房后,RTC Engine 房间会自动解散。当然,您也可以调用服务端接口 DismissRoom(整型房间号)或 DismissRoomByStrRoomId(字符串房间号)强制解散 RTC Engine 房间。

进房查看直播间历史消息

使用 AVChatRoom 默认不存储直播间历史消息,当新用户进入直播间后,只能看到进入直播间后用户发送的消息。为了优化新进群用户的体验,可在控制台配置直播群用户拉取进群前消息条数,如图:

直播群用户进群前历史消息与拉起其他群历史消息一样,代码示例:
V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init];
option.getType = V2TIM_GET_CLOUD_OLDER_MSG; // 拉取云端的更老的消息
option.getTimeBegin = 1640966400; // 从 2022-01-01 00:00:00 开始
option.getTimePeriod = 1 * 24 * 60 * 60; // 拉取一整天的消息
option.count = INT_MAX; // 返回时间范围内所有的消息
option.groupID = #your group id#; // 拉取群聊消息
[V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray<V2TIMMessage *> *msgs) {
NSLog(@"success");
} fail:^(int code, NSString *desc) {
NSLog(@"failure, code:%d, desc:%@", code, desc);
}];
注意:
此功能仅限进阶版用户才可开通,且仅支持拉群24小时内最多20条历史消息。

进房感知麦上主播状态

方案一:进房时默认所有主播为状态,然后根据 onUserAudioAvailable(userId, true) 回调解除对应主播禁音状态。
- (void)onUserAudioAvailable:(NSString *)userId available:(BOOL)available {
if (available) {
// 解除对应主播的禁音状态
}
}
方案二:把主播的状态存储在 Chat 群属性中,听众进房获取全量群属性,解析麦上主播禁音状态。
[[V2TIMManager sharedInstance] getGroupAttributes:groupID keys:nil succ:^(NSMutableDictionary<NSString *,NSString *> *groupAttributeList) {
// 获取群属性成功,假设存储主播状态的 key 为 muteStatus
NSString *muteStatus = groupAttributeList[@"muteStatus"];
// 解析 muteStatus,获取每个麦上主播的禁音状态
} fail:^(int code, NSString *desc) {
// 获取群属性失败
}];


帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈