iOS

最近更新时间:2025-04-16 15:33:52

我的收藏

业务流程

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



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



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




接入准备

步骤1:开通服务

语聊房场景通常需要依赖腾讯云 即时通信 IM实时音视频 TRTC 两项付费 PaaS 服务构建。
1. 首先您需要登录 实时音视频 TRTC 控制台 创建应用,此时在 即时通信 IM 控制台 会同步自动创建一个与当前 TRTC 应用相同 SDKAppID 的 IM 体验版应用,二者账号与鉴权体系可复用。后续您可根据需要选择升级 TRTC 或 IM 应用版本,例如旗舰版可解锁更多增值功能服务。



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




步骤2:导入 SDK

TRTC SDK 和 IM 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 精简版(推荐)
# 安装包体积增量最小,仅支持实时音视频(TRTC)和 直播播放器(TXLivePlayer)两项功能。
pod 'TXLiteAVSDK_TRTC', :podspec => 'https://liteav.sdk.qcloud.com/pod/liteavsdkspec/TXLiteAVSDK_TRTC.podspec'

# LiteAVSDK 全功能版
# 包含实时音视频(TRTC)、直播播放器(TXLivePlayer)、RTMP 推流(TXLivePusher)、点播播放器(TXVodPlayer)和短视频录制和编辑(UGSV)等众多功能。
pod 'TXLiteAVSDK_Professional', :podspec => 'https://liteav.sdk.qcloud.com/pod/liteavsdkspec/TXLiteAVSDK_Professional.podspec'

# IM 基础版
pod 'TXIMSDK_iOS'

# IM 增强版(推荐)
pod 'TXIMSDK_Plus_iOS'
# pod 'TXIMSDK_Plus_Swift_iOS'

# IM 增强版 xcframework 版本(推荐)
pod 'TXIMSDK_Plus_iOS_XCFramework'
# pod 'TXIMSDK_Plus_Swift_iOS_XCFramework'

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
语聊房场景推荐集成 TRTC 精简版 SDK 和 IM 增强版 SDK。
除了 CocoaPods 集成方式,您还可以选择下载 SDK 并手动导入,详见 手动导入 TRTC SDK手动导入 IM SDK

步骤3:工程配置

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



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




接入过程

步骤1:生成鉴权凭证

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



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

步骤2:初始化与监听

时序图




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

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

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

// 移除事件监听器
// self 是 id<V2TIMSDKListener> 的实现类
[[V2TIMManager sharedInstance] removeIMSDKListener:self];
// 反初始化 SDK
[[V2TIMManager sharedInstance] unInitSDK];
说明:
如果您的应用生命周期跟 SDK 生命周期一致,退出应用前可以不进行反初始化。若您只在进入特定界面后才初始化 SDK,退出界面后不再使用,可以对 SDK 进行反初始化。
2. TRTC SDK 创建实例与设置事件监听器。
// 创建 TRTC 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;
// 销毁 TRTC SDK 实例(单例模式)
[TRTCCloud destroySharedIntance];
说明:
建议监听 SDK 事件通知,对一些常见错误进行日志打印和处理,详见 错误码表

步骤3:登录与登出

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

时序图




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)
// 注意:其他的错误码,请不要在这里调用登录接口,避免 IM 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);
}];
说明:
如果您的应用生命周期跟 IM SDK 生命周期一致,退出应用前可以不登出。若您只在进入特定界面后才使用 IM SDK,退出界面后不再使用,可以进行登出操作和对 IM SDK 进行反初始化。

步骤4:房间管理

时序图




1. 创建房间。
主播(房主)开播时需要创建房间,这里的“房间”概念对应 IM 中的“群组”。本例仅展示客户端创建 IM 群组的方式,实际也可在 服务端创建群组
// 创建群组
[[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
}
注意:
语聊房场景创建 IM 群组需要选用直播群类型:GroupType_AVChatRoom
TRTC 没有创建房间的 API,当用户要加入的房间不存在时,后台会自动创建一个房间。
2. 加入房间。
加入 IM 群组。
// 加入群组
[[V2TIMManager sharedInstance] joinGroup:groupID msg:message succ:^{
// 加入群组成功
} fail:^(int code, NSString *desc) {
// 加入群组失败
}];

// 监听加入群组事件
[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberEnter:(NSString *)groupID memberList:(NSArray<V2TIMGroupMemberInfo *>*)memberList {
// 有人加入群组
}
加入 TRTC 房间。
- (void)enterRoomWithRoomId:(NSString *)roomId userID:(NSString *)userId {
TRTCParams *params = [[TRTCParams alloc] init];
// 以字符串房间号为例,建议和 IM 群组号保持一致
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!"];
}
}
注意:
TRTC 房间号分为整型 roomId 和字符串类型 strRoomId,两种类型的房间不互通,建议统一房间号类型。
语聊互动场景进房时须指定用户角色(主播/观众),只有主播才有推流权限,如未指定则默认为主播角色。
语聊互动进房场景建议选用 TRTCAppSceneVoiceChatRoom
3. 退出房间。
退出 IM 群组。
[[V2TIMManager sharedInstance] quitGroup:groupID succ:^{
// 退出群组成功
} fail:^(int code, NSString *desc) {
// 退出群组失败
}];

[[V2TIMManager sharedInstance] addGroupListener:self];
- (void)onMemberLeave:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member {
// 群成员离开回调
}
注意:
直播群(AVChatRoom)中,群主是不可以退群的,群主只能调用 dismissGroup 解散群组。
退出 TRTC 房间。
- (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. 解散房间。
解散 IM 群组。本例仅展示客户端解散 IM 群组的方式,实际也可在 服务端解散群组
[[V2TIMManager sharedInstance] dismissGroup:groupID succ:^{
// 解散群组成功
} fail:^(int code, NSString *desc) {
// 解散群组失败
}];

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

步骤5:麦位管理

时序图




首先,我们可以创建一个用于保存麦位信息的 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 成功即可切换 TRTC 角色并开始推流。如为邀请上麦模式,可参照主动上麦的实现逻辑,只需调换信令的发送方与接收方即可。
// 本地保存的全量麦位信息列表
@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 成功即可切换 TRTC 角色并停止推流。
// 本地保存的全量麦位信息列表
@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) {
// 修改群属性失败,移麦失败
}];
}

步骤6:音频管理

时序图




1. 订阅模式.
TRTC 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];
说明:
TRTC 预设音质共分为三档(Speech/Default/Music)分别对应不同的音频参数,详见 音频音质
音量类型设置。
TRTC 每档音质都对应有默认的音量类型,如需强制指定音量类型可以使用如下接口。
// 设置音量类型
[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeAuto];
说明:
TRTC 音量类型共分为三档(VOIP/Auto/Media)分别对应不同的音量通道,详见 音量类型
音频路由设置。
移动端设备上通常有扬声器和听筒两个播放位置,如需强制指定音频路由可以使用如下接口。
// 设置音频路由
[self.trtcCloud setAudioRoute:TRTCAudioModeSpeakerphone];
注意:
音频路由仅支持在通话音量模式(VOIP)下手动切换,媒体音量模式(Media)下默认扬声器输出。

高级功能

弹幕消息互动

语聊直播间通常会有文本形式的弹幕消息互动,这里可以通过 IM 的发送及接收群聊普通文本消息来实现。
// 发送公屏弹幕消息
[[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);
}

音量大小回调

TRTC 可以按照固定频率回调麦上主播的音量大小,通常用于展示音波音浪,提示正在发言的主播。
// 启用音量大小回调,建议在进房成功后即开启
// 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 不为空时代表远端用户的音量大小。
麦上用户声波动画的渲染可以根据 onUserVoiceVolume 回调中的音量大小来确定,声波动画的开启和关闭(用户开闭麦状态)建议根据 onUserAudioAvailable 回调状态来确定。

音乐及音效播放

播放背景音乐及音效是语聊房场景中的高频需求,下面将对常用的背景音乐相关接口的使用及注意事项进行说明。
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];
注意:
TRTC 支持同时播放多首音乐,通过 musicID 唯一标识,若您想要同一时刻只播放一首音乐,需要注意在开始播放前停止播放其他音乐,或者可以使用同一个 musicID 来播放不同的音乐,这样 SDK 会先停止播放旧的音乐,再播放新的音乐。
TRTC 支持播放本地和网络音频文件,通过 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,但有爆音风险。
如果出现背景音乐压制人声的情况,可适当调低音乐播放音量,调高人声采集音量。
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. 混流转推直播 CDN。
- (void)startPublishMediaToCDN:(NSString *)streamName {
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:0];
// 推流地址的过期时间,默认一天
NSTimeInterval time = [date timeIntervalSince1970] + (24 * 60 * 60);
// LIVE_URL_KEY 鉴权密钥请在控制台推流地址配置页面获取
NSString *secretParam = [self getSafeUrl:LIVE_URL_KEY streamName:streamName time:time];
// 媒体流发布的目标地址
TRTCPublishTarget* target = [[TRTCPublishTarget alloc] init];
// 混流后发布到 CDN
target.mode = TRTCPublishMixStreamToCdn;
TRTCPublishCdnUrl* cdnUrl = [[TRTCPublishCdnUrl alloc] init];
// 推流地址必须带参数,否则推流不成功
cdnUrl.rtmpUrl = [NSString stringWithFormat:@"rtmp://%@/live/%@?%@", PUSH_DOMAIN, streamName, secretParam];
// 腾讯云直播推流地址为 true,第三方为 false
cdnUrl.isInternalLine = YES;
NSMutableArray* cdnUrlList = [NSMutableArray new];
// 可以添加多个 CDN 推流地址
[cdnUrlList addObject:cdnUrl];
target.cdnUrlList = cdnUrlList;
TRTCStreamEncoderParam* encoderParam = [[TRTCStreamEncoderParam alloc] init];
// 设置转码后的音频流的编码参数(可自定义)
encoderParam.audioEncodedSampleRate = 48000;
encoderParam.audioEncodedChannelNum = 1;
encoderParam.audioEncodedKbps = 50;
encoderParam.audioEncodedCodecType = 0;
// 设置转码后的视频流的编码参数(若需混入黑帧则必填,纯音频混流可忽略)
encoderParam.videoEncodedWidth = 64;
encoderParam.videoEncodedHeight = 64;
encoderParam.videoEncodedFPS = 15;
encoderParam.videoEncodedGOP = 3;
encoderParam.videoEncodedKbps = 30;
// 媒体流转码配置参数
TRTCStreamMixingConfig* config = [[TRTCStreamMixingConfig alloc] init];
// 默认情况下填空值即可,代表会混合房间中的所有音频
config.audioMixUserList = nil;
// 若需混入黑帧则必须携带 TRTCVideoLayout 参数(纯音频混流可忽略)
TRTCVideoLayout* layout = [[TRTCVideoLayout alloc] init];
config.videoLayoutList = @[layout];
// 开始混流转推
[self.trtcCloud startPublishMediaStream:target encoderParam:encoderParam mixingConfig:config];
}
2. 混流回推 TRTC 房间。
- (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];
}
3. 事件回调及更新停止任务。
任务结果事件回调。
#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 表示成功,其余值表示失败
}
更新发布媒体流。
该接口会向 TRTC 服务器发送指令,更新通过 startPublishMediaStream 启动的媒体流。
// taskId: 通过 onStartPublishMediaStream 回调的任务 ID
// target: 例如增删发布的 CDN URL
// params: 建议保持媒体流编码输出参数一致,避免播放侧断流
// config: 更新参与混流转码的用户列表,例如跨房 PK
[self.trtcCloud updatePublishMediaStream:taskId publishTarget:target encoderParam:trtcStreamEncoderParam mixingConfig:trtcStreamMixingConfig];
注意:
同一个任务不支持纯音频、音视频、纯视频之间的切换。
停止发布媒体流。
该接口会向 TRTC 服务器发送指令,停止通过 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;
}
}

高级权限控制

TRTC 高级权限控制可用于对不同房间设置不同进入权限,例如高级 VIP 房;也可用于控制听众上麦权限,例如处理幽灵麦。具体的操作步骤如下:
1. TRTC 控制台 应用的功能配置页面打开高级权限控制开关。
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]];

异常处理

异常错误处理

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

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

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

- (void)onConnectionRecovery {
// SDK 与云端的连接已经恢复
}
2. 离线状态下自动下麦。
IM 用户的普通状态分为在线(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]];
}
}
}



注意:
订阅用户状态需要升级到旗舰版套餐,详情请参见 基础服务详情
订阅用户状态需要提前在 即时通信 IM 控制台 开启 “用户状态查询及状态变更通知”,如果未开启,调用 subscribeUserStatus 会报错。

上下麦防音频卡顿方案

iOS 端上下麦时可能会出现短暂音频卡顿,这与其系统音频机制有关。startLocalAudiostopLocalAudio 等操作会获取和释放设备麦克风权限,SDK 音频重采集导致 iOS AVAudioSession 重启音频驱动,从而导致上下麦音频卡顿。您可以通过在进房后统一调用一次 startLocalAudio,后续上下麦通过 muteLocalAudio 控制本地音频上行的方式来避免这一问题。



注意:
此方案进房即会向用户申请麦克风权限,可能会造成用户误解,如介意请选择音频管理中的常规上下麦方案。

服务端踢人及解散房间

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

进房查看直播间历史消息

使用 AVChatRoom 默认不存储直播间历史消息,当新用户进入直播间后,只能看到进入直播间后用户发送的消息。为了优化新进群用户的体验,可在 即时通信 IM 控制台 配置直播群新成员查看入群前消息量,如下图所示:



开启后会在加入直播群成功后,在 onRecvNewMessage 回调中获得入群前消息。
// self 为 id<V2TIMAdvancedMsgListener>
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
NSLog(@"onRecvNewMessage, sender: %@, msg: %@", msg.sender, msg.textElem.text);
}
}
注意:
此功能仅旗舰版用户才可开通,且仅支持新入群成员查看入群前 24h 产生的消息,最多可查看 20 条。

进房感知麦上主播禁音状态

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

主播闭麦状态下播放音乐,远端用户听不到

通常情况下,本地闭麦操作需要调用 muteLocalAudio 方法,但在播放背景音乐时调用此方法会暂停将音乐发布至远端,即远端用户听不到背景音乐了。如果希望在闭麦的同时能够正常播放和发布背景音乐,可以参考下述方案实现。
// 1. 获取当前的人声采集音量
int volume = [self.trtcCloud getAudioCaptureVolume];

// 2. 将人声采集音量设为0,达到闭麦效果
[self.trtcCloud setAudioCaptureVolume:0];

// 3. 解除闭麦时,恢复至原先的人声采集音量
[self.trtcCloud setAudioCaptureVolume:volume];