连麦PK
场景概述
主播连麦 PK 场景指的是主播在直播时,可以对另外一个直播间的主播发起 PK 挑战,一旦挑战被接受,两个直播间的主播就会加入到同一个房间,并开始进行连麦互动。
与常规 1v1 连麦场景不同的是,PK 场景下直播界面会一分为二,每位主播各自的粉丝观看链接不会改变,但是可以同时看到两位主播的画面。
实现分析
纯直播切换到 PK 场景
主播 A 在直播中,收到主播 B 的 PK 邀请。
主播 A 首先需要离开当前的直播房间,但不能更改合流配置的 mergeJob id 以及推流地址,然后主动加入主播 B 的房间,与主播 B 进行连麦。
此时,主播 B 需要修改合流界面布局的配置,将自己的画面位置布局在分屏的左边,将对方(主播 A)的画面位置布局在分屏的右边。
同时,主播 A 则需要重新创建合流任务,注意要使用原先的 mergeJob id 以及推流地址,然后修改合流的布局,从原来的画面全屏,改为将自己的画面位置设置在分屏的左边,将主播 B 的画面位置设置在分屏的右边,最后配置 layout 布局。
这样,主播 B 是观众看到的画面是主播 B 在左边,主播 A 在右边。
而主播 A 的观众不需要更换拉流地址,看到的是主播 A 在左边,主播 B 在右边。
PK 场景切换到纯直播
主播 A 与主播 B 在 PK 连麦直播中,需要结束 PK 的模式。
主播 A 退出主播 B 所在房间,与此同时,主播 B 及时修改画面布局,从左右分屏的布局,改为自己画面全屏布局。
主播 A 在离开主播 B 的所在房间后,及时进入自己的原房间,重新创建合流任务,注意要使用原先的 mergeJob id 以及推流地址,然后修改合流的布局,从左右分屏布局,改为自己画面全屏布局。
这样,主播 A 的观众不需要更换拉流地址,看到主播 A 画面全屏;主播 B 的观众看到主播 B 画面全屏。
分析总结
1v1 的连麦 PK 模式下,不需要离开自己房间的主播,只需要及时修改更新合流布局即可。
而需要离开房间的主播,逻辑相对较复杂,除了要离开/加入房间,每次模式切换必须要重新创建合流任务,且要保证合流任务 id、转推流地址的前后一致,及时配置更新合流布局。
代码参考
自定义合流
无论是主播 A 还是主播 B,都需要维护 QNMergeStreamConfiguration
有初始化的默认 API 调用配置,也可根据实际需要配置,示例代码大致如下:
配置自定义合流信息
// 不存在时需要创建配置对象
if (self.mergeStreamConfiguration == nil) {
self.mergeStreamConfiguration = [QNMergeStreamConfiguration defaultConfiguration];
}
// 设置合流任务 id,为合流任务的唯一标识
self.mergeStreamConfiguration.jobId = _mergeJobId;
// 设置合流任务的推流地址,该场景下需保持一致
self.mergeStreamConfiguration.publishUrl = [NSString stringWithFormat:@"rtmp://xxxxx.com/xxx/%@", _mergeJobId];
// 合流画面的宽高
self.mergeStreamConfiguration.width = 720;
self.mergeStreamConfiguration.height = 1280;
// 码率的单位是 bps
self.mergeStreamConfiguration.minBitrateBps = 1000*1000;
self.mergeStreamConfiguration.maxBitrateBps = 1000*1000;
// 合流画面的填充模式
self.mergeStreamConfiguration.fillMode = QNVideoFillModePreserveAspectRatioAndFill;
更具体的可配置信息,可见 QNMergeStreamConfiguration
创建自定义合流
* @abstract 创建合流任务。
*
* @discussion 此场景下,必须创建合流任务。
* 异步接口,创建成功后,会通过 - (void)RTCEngine:(QNRTCEngine *)engine didCreateMergeStreamWithJobId:(NSString *)jobId; 接口回调通知。
*
* @since v2.0.0
*/
- (void)createMergeStreamJobWithConfiguration:(QNMergeStreamConfiguration *)configuration;
调用创建自定义合流的 API,将配置完成的 mergeStreamConfiguration 传入
[self.engine createMergeStreamJobWithConfiguration:_mergeStreamConfiguration];
调用创建合流成功后,会有成功创建合流任务的回调
/*!
* @abstract 成功创建合流任务的回调。
*
* @since v2.0.0
*/
- (void)RTCEngine:(QNRTCEngine *)engine didCreateMergeStreamWithJobId:(NSString *)jobId;
获取需要配置的 layout
_layouts = [NSMutableArray array];
for (QNTrackInfo *trackInfo in tracks) {
if (trackInfo.kind == QNTrackKindAudio) {
QNMergeStreamLayout *audioLayout = [[QNMergeStreamLayout alloc] init];
audioLayout.trackId = trackInfo.trackId;
[_layouts addObject:audioLayout];
}
if (trackInfo.kind == QNTrackKindVideo) {
QNMergeStreamLayout *layout = [[QNMergeStreamLayout alloc] init];
layout.frame = CGRectMake(0, 0, 480, 848);
layout.zIndex = 0;
layout.trackId = trackInfo.trackId;
[_layouts addObject:layout];
}
}
layout 的分类以及可配置项详见 QNMergeStreamLayout
可从以下 2 个代理回调中获取相应已发布的 QNTrackInfo
注意:实际业务逻辑建议根据需要获取使用。
/*!
* @abstract 远端用户发布音/视频的回调。
*
* @since v2.0.0
*/
- (void)RTCEngine:(QNRTCEngine *)engine didPublishTracks:(NSArray<QNTrackInfo *> *)tracks ofRemoteUserId:(NSString *)userId;
/*!
* @abstract 本地音/视频 Track 成功发布的回调。
*
* @since v2.0.0
*/
- (void)RTCEngine:(QNRTCEngine *)engine didPublishLocalTracks:(NSArray<QNTrackInfo *> *)tracks;
设置合流布局
/*!
* @abstract 将对应的音视频 Track 加入合流。
*
* @discussion 需要更新合流布局时,重新调用该接口即可。若使用默认的合流任务,则 jobId 传入 nil 即可。
*
* @since v2.0.0
*/
- (void)setMergeStreamLayouts:(NSArray <QNMergeStreamLayout *> *)layouts jobId:(nullable NSString *)jobId;
调用自定义合流布局 API,传入准备好的 layouts 和相应的合流 id
[self.engine setMergeStreamLayouts:_layouts jobId:_mergeJobId];
务必确保收到创建合流任务成功的回调后,再进行 layouts 的配置,否则可能会导致配置合流布局失败,无法成功合流转推。
需要取消自定义合流布局时,可调用以下 API 实现:
/*!
* @abstract 将对应的音视频 Track 从合流中移除。
*
* @discussion 此处 QNMergeStreamLayout 中只需要设置 trackId 即可,其它参数可忽略。若使用默认的合流任务,则 jobId 传入 nil 即可。
*
* @since v2.0.0
*/
- (void)removeMergeStreamLayouts:(NSArray <QNMergeStreamLayout *> *)layouts
jobId:(nullable NSString *)jobId;
调用取消合流布局的 API,将需要移除的 layouts 信息传入即可。
[self.engine removeMergeStreamLayouts:_layouts jobId:_mergeJobId];
停止合流任务的 API,根据业务需要进行调用:
/*!
* @abstract 停止合流任务。
*
* @param
* jobId 合流任务 id,如果使用的是默认的合流任务,则传入 nil 即可。
*
* @since v2.0.0
*/
- (void)stopMergeStreamWithJobId:(nullable NSString *)jobId;
调用停止自定义合流 API,合流将会立即停止。
注意:停止合流时,一定要对应需要停止的流的 jobId。
[self.engine stopMergeStreamWithJobId:_mergeJobId];
主播 A 操作流程
退出当前房间
- 主播 A 在接受 PK 邀请后,需要停止当前合流,并退出当前房间:
[self.engineA stopMergeStreamWithJobId:_mergeJobId];
[self.engineA leaveRoom];
- 成功退出房间后,会触发如下回调接口,主播 A 可以在该回调内重新加入主播 B 的房间:
/*!
* @abstract 本地用户离开房间成功的回调。
*
* @discussion 调用 leaveRoom 离开房间成功的回调
*
* @since v2.3.2
*/
- (void)RTCEngine:(QNRTCEngine *)engine didLeaveOfLocalSuccess:(BOOL)success;
加入主播 B 的房间
加入主播 B 的房间后,需要重新创建合流任务,同时需要对分屏的合流布局进行配置。具体步骤可分解如下:
成功加入房间:可根据房间状态
QNRoomState
的回调看是否成功加入房间,详情可参考房间状态回调创建合流任务:成功加入房间后,需要在适当的时机创建合流任务,合流任务和纯直播时保持一致即可,尤其是推流地址,切换地址会影响观众端的拉流体验,因此,建议维护一个全局的合流任务对象。
设置合流布局:SDK 提供了
本地成功发布音视频
以及远端成功发布音视频
的回调接口,详情可参考 QNRTCEngineDelegate,收到音视频成功发布的回调后,在确保合流任务创建成功的前提下,可以进行合流画面的布局的配置,从纯直播布局切换为 PK 模式布局,并设置生效直播 PK:合流布局配置成功后,便可以进行正常的主播间 PK 了,切换房间的过程对观众端基本没有明显的影响
退出主播 B 的房间
当 PK 完成后,主播 A 可以回到自己的直播间,操作流程和上述基本一致,唯一的区别是合流布局存在差异,基本流程如下:
- 停止合流,退出主播 B 的房间
- 加入自己的房间
- 重新发布音视频 Track
- 重新创建合流任务
- 重新配置合流布局(配置为纯直播场景的布局)
在上述操作后,主播 A 便可以恢复正常直播了
主播 B 操作流程
主播 B 仅需要监听主播 A 加入房间的动作,并适当调整配置相应的合流布局即可,示例代码如下:
- (void)RTCEngine:(QNRTCEngine *)engine didPublishTracks:(NSArray<QNTrackInfo *> *)tracks ofRemoteUserId:(NSString *)userId {
// 主播 A 加入房间并发布音视频后,重新设置直播布局为 PK 模式的布局
}
- (void)RTCEngine:(QNRTCEngine *)engine didUnPublishTracks:(NSArray<QNTrackInfo *> *)tracks ofRemoteUserId:(NSString *)userId {
// 主播 A 取消发布音视频并退出房间后,重新设置直播布局为纯直播模式
}
上述代码仅供参考,具体监听的回调需要根据您的业务场景进行适当的调整,更详细的回调说明请参考 QNRTCEngineDelegate
注意事项
- 离开房间再进入房间,必须要重新创建合流任务,配置 layout 布局,否则会导致合流失败布局超时。
- 务必使用自定义合流任务,防止使用默认合流在同一房间出现抢流的情况。
- 务必确保在创建合流成功的情况下,设置合流 layouts 布局,否则会合流失败。
- 业务逻辑上管理好相应的 mergeJobId 以及转推流地址,保证 pk 逻辑的正常实现。
- 配置合流布局时,分音频 track 和视频 track,若合流中既要音频也要视频,需要都配置传入。
- 配置合流布局 layout 信息时,务必对应好 trackInfo 的类型,防止布局视频 track 时 frame 没有值,导致合流黑屏。
- 需要及时更新 layouts 布局,否则可能会出现合流画面不符合预期设置的问题。