快速实现 PK 直播

更新时间: 2024/07/24 13:38:57

在 PK 直播时,通过跨频道转发,无须切换 NERTC SDK,也不需要退出或重新进入房间,直接将媒体流转发到主播房间和挑战者房间,实现主播跨房间与其他主播实时互动。直播间内的观众可以同时观看两个主播 PK 互动,场景无缝切换。

下文介绍在单人直播的过程中,主播 A 邀请主播 B 进行 PK 直播的实现流程。

功能原理

PK 直播的架构原理如下图所示。

PK 直播.png

PK 直播的业务流程说明如下:

  1. 主播 A 发出 PK 邀请,主播 B 同意。
  2. 通过跨频道转发,主播 A 和主播 B 不需要退出原房间,直接将媒体流转发到房间 A 和房间 B 中,实现主播跨房间与其他主播实时互动。
  3. 互动直播服务器将主播 A 和主播 B 的音视频进行混屏转码后,推到 CDN 分发。
  4. 观众端使用 RTMP/HLS/FLV 协议进行拉流观看。

注意事项

  • 只支持网易云信播放器 NELivePlayer 进行拉流,其他播放器暂不兼容。
  • 单人直播切换到 PK 直播时,音频采样率必须保持一致。

前提条件

实现 PK 直播前,请确保您已经实现了 单人直播

实现 PK 直播

通过跨房间媒体流转发,主播无须退出/加入原房间,即可将媒体流同时转发到多个房间中,实现 PK 直播。

下文介绍在单人直播的过程中,主播 A 邀请主播 B 进行 PK 直播的实现流程。

API 时序图

sequenceDiagram
    title: 实现 PK 直播的 API 时序图
    actor 主播 A
    participant 业务服务器
    actor 主播 B
    participant NERtcSDK as 网易云信 RTC SDK
    %% 开始 PK

    主播 A->>业务服务器: 邀请 PK
    Note right of 主播 A: 邀请主播 B 进行 PK<br>(带上自己的 uid、cname、<br>Token 等信息)
    Note left of 主播 B: 请自行实现麦位<br>管理相关业务逻辑
    业务服务器->>主播 B: 邀请 PK(带上主播 A 的 <br>uid、cname、<br>Token 等信息)

    主播 B-->>业务服务器: 同意 PK
    业务服务器-->>主播 A: 对端同意 PK(带上主播<br> B 的 uid、cname、Token 等信息)

    rect rgb(191, 223, 255)
    主播 A ->> NERtcSDK: startChannelMediaRelay   开启媒体转发
    主播 A ->> NERtcSDK: addLiveStream 开始旁路推流
    NERtcSDK -->> 主播 A: onNERTCEngineLiveStreamState 监听旁路推流状态

    主播 A ->> NERtcSDK: stopPushStreaming   旁路推流成功后,停止单人直播推流

    Note right of 主播 A: 当主播 B 开始 MediaRelay <br>后,更新旁路推流,具体请<br>根据实际业务进行调整
    主播 A ->> NERtcSDK: updateLiveStreamTask 更新旁路推流
    Note right of 主播 A: 根据回调确认 mediaRelay <br>是否成功,如果失败,则进行失败回退,<br>例如调用 stopChannelMediaRelay 和<br> removeLiveStreamTask 方法结束 PK
    NERtcSDK -->> 主播 A: onNERtcEngineChannelMediaRelayStateDidChange:channelName: <br>和 onNERtcEngineDidReceiveChannelMediaRelayEvent: channelName: error:

    end
    NERtcSDK -->> 主播 A: 监听房间中人员进入和音视频打开的回调
    NERtcSDK -->> 主播 B: 监听房间中人员进入<br>和音视频打开的回调

    主播 A ->> 业务服务器: 结束 PK
    业务服务器 ->> 主播 B: 结束 PK
    主播 A ->> NERtcSDK: startPushStreaming 重新开始单人直播推流

    NERtcSDK -->> 主播 A: onNERtcEngineStartPushStreaming
    主播 A ->> NERtcSDK: stopChannelMediaRelay 停止媒体转发
    主播 A ->> NERtcSDK: removeLiveStreamTask 移除旁路推流任务
    主播 B ->> NERtcSDK: stopChannelMediaRelay
    主播 B ->> NERtcSDK: removeLiveStreamTask <br>移除旁路推流任务

开始 PK

  1. 开始 mediaRelay。

    主播 A 调用 startChannelMediaRelay 方法开启媒体转发功能,将主播 A 的视频流推送到主播 B 房间。

    Objective-CNERtcChannelMediaRelayConfiguration *mediaRelayConfig = [[NERtcChannelMediaRelayConfiguration alloc]init];
    NERtcChannelMediaRelayInfo *info = [[NERtcChannelMediaRelayInfo alloc]init];
    info.channelName = channelName; //PK 目标房间的房间号
    info.token = token;//用于加入 PK 目标房间的 token, 与加入房间获取的 token 方式一致,注意 Token 需要及时使用,防止过期导致异常。
    info.uid = uid;//本人在 PK 目标房间的 uid
    [mediaRelayConfig setDestinationInfo:info forChannelName:info.channelName];
    
    [[NERtcEngine sharedEngine] startChannelMediaRelay:mediaRelayConfig]
    
  2. 开启旁路推流任务。

    主播 A 调用 addLiveStreamTask 方法添加旁路推流任务,将主播 A 和主播 B 房间的音视频流推送到 CDN 上进行合流。

    Objective-C//设置旁路推流参数
    NERtcLiveStreamTaskInfo *info = [[NERtcLiveStreamTaskInfo alloc] init];
    NERtcLiveConfig *config = [[NERtcLiveConfig alloc] init];
    
    info.streamURL = self.pushStreamingUrl;
    info.taskID = PKTaskId;
    info.lsMode = kNERtcLsModeVideo;
    
    config.channels = 2;
    config.sampleRate = kNERtcLiveStreamAudioSampleRate44100;
    config.audioBitrate = 128;
    config.audioCodecProfile = kNERtcLiveStreamAudioCodecProfileLCAAC;
    
    //如果需要服务器录制
    info.serverRecordEnabled = YES;
    info.config = config;
    
    //设置整体布局
    NERtcLiveStreamLayout *layout = [[NERtcLiveStreamLayout alloc] init];
    layout.width = 720;//整体布局宽度 建议与单推一致
    layout.height = 1280;//整体布局高度 建议与单推一致
    info.layout = layout;
    
    //自己
    NERtcLiveStreamUserTranscoding *user1 = [[NERtcLiveStreamUserTranscoding alloc]init];
    user1.uid = myUid;
    user1.audioPush = YES; // 推流是否发布 user1 的音频
    user1.videoPush = YES; // 推流是否发布 user1 的视频
    user1.x = 0; // user1 的视频布局 x 偏移,相对整体布局的左上角
    user1.y = 320; // user1 的视频布局 y 偏移,相对整体布局的左上角
    user1.width = 360; // user1 的视频布局宽度
    user1.height = 640; //user1 的视频布局高度
    user1.adaption = kNERtcLsModeVideoScaleCropFill; //会填满画面,超出部分会被裁减
    
    //如果此时知道对方 Uid 可以在这里预先填上,如果不知道,可以先不设置 User2,然后参考第 6 步 update
    NERtcLiveStreamUserTranscoding *user2 = [[NERtcLiveStreamUserTranscoding alloc]init];
    user2.uid = pkUid;
    user2.audioPush = YES; // 推流是否发布 user2 的音频
    user2.videoPush = YES; // 推流是否发布 user2 的视频
    user2.x = 360; // user2 的视频布局 x 偏移,相对整体布局的左上角
    user2.y = 320; // user2 的视频布局 y 偏移,相对整体布局的左上角
    user2.width = 360; // user2 的视频布局宽度
    user2.height = 640; //user2 的视频布局高度
    user2.adaption = kNERtcLsModeVideoScaleCropFill; //
    
    NSMutableArray* layoutUsers = [NSMutableArray array];
    [layoutUsers addObject:user1];
    [layoutUsers addObject:user2];
    info.layout.users = layoutUsers;
    
    //结束之前的旁路推流,防止异常
    [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
    }];
    
    //开启旁路推流
    [[NERtcEngine sharedEngine] addLiveStreamTask:info compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        if (0 != errorCode) {
            //表示开启开启 PK 失败,此时仍然处于单人直播状态,此时业务可以根据自身定制。如果想要继续 PK 需要重新按照实现 PK 直播逻辑继续开始 PK。
        }
    }]
    
  3. 等待旁路推流结果。

    通过 onNERTCEngineLiveStreamState 回调监听旁路推流状态,如果状态为 kNERtcLsStatePushing,表示旁路推流成功。

    Objective-C//旁路直播状态回调
    - (void)onNERTCEngineLiveStreamState:(NERtcLiveStreamStateCode)state
                                taskID:(NSString *)taskID
                                    url:(NSString *)url {
        //表示 PK 成功
        if (state == kNERtcLsStatePushing) {
        //停止单人直播
        [NERtcEngine sharedEngine] stopPushStreaming];
        }else{
            //此时需要按照以下操作回退到单推
            [[NERtcEngine sharedEngine] stopChannelMediaRelay];
            [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
            }];
    
            [[NERtcEngine sharedEngine] startPushStreaming:config];
        }
    }
    

    如果旁路推流失败,则根据业务实际情况进行失败回退,例如调用 stopChannelMediaRelay 方法停止媒体转发功能。

  4. 停止单人直播推流。

    主播 A 调用 stopPushStreaming 方法停止单人直播推流。

    Objective-C//在第 4 步骤收到 onNERTCEngineLiveStreamState 回调 kNERtcLsStatePushing 后
    //调用 stopPushStreaming 停止单人直播
    [NERtcEngine sharedEngine] stopPushStreaming];
    
  5. 等待主播 B 加入房间后更新旁路推流任务。

    主播 A 调用 updateLiveStreamTask 如果无法预先知道 PK 对方的 Uid,或者需要保证主播房间和挑战者房间的音视频流能够同步播放明,可以参考这一步。

    Objective-C//对方加入房间回调
    - (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID userName:(NSString *)userName {
        //参考第 4 步的旁路推流设置
        //调用 update 接口更新
        [[NERtcEngine sharedEngine] updateLiveStreamTask:info compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        }]
    }
    
  6. 确认 mediaRelay 是否成功。

    通过 onNERtcEngineChannelMediaRelayStateDidChangeonNERtcEngineDidReceiveChannelMediaRelayEvent: channelName: error: 回调监听媒体转发状态,确认媒体转发是否成功。

    如果媒体转发失败,则根据业务实际情况进行失败回退,例如调用 stopChannelMediaRelayremoveLiveStreamTask 方法结束 PK。

    Objective-C- (void)onNERtcEngineDidReceiveChannelMediaRelayEvent:(NERtcChannelMediaRelayEvent)event channelName:(NSString *)channelName error:(NERtcError)error {
        //表示失败
        if (event == NERtcChannelMediaRelayEventFailure) {
            //此时需要按照以下操作回退到单推
            [[NERtcEngine sharedEngine] stopChannelMediaRelay];
            [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
            }];
    
            [[NERtcEngine sharedEngine] startPushStreaming:config];
        }
        //表示成功
        ifevent == NERtcChannelMediaRelayEventConnected){
            //此时表示 PK 建立成功
        }
    }
    

结束 PK

  1. 开始单人直播推流。

    主播 A 调用 startPushStreaming 方法开始单人直播推流。

    Objective-C//设置推流参数,此时 streamingRoomInfo 可以不传
    NERtcPushStreamingConfig *pushConfig = [[NERtcPushStreamingConfig alloc] init];
    pushConfig.streamingUrl = streamingUrl;
    
    //开启推流
    [[NERtcEngine sharedEngine] startPushStreaming:config];
    
  2. 等待单人直播推流结果。

    主播 A 通过 onNERtcEngineStartPushStreamingWithResult:channelId: 回调监听单人直播推流状态,如果推流成功,则执行下一步操作。

    Objective-C- (void)onNERtcEngineStartPushStreamingWithResult:(NERtcError)result channelId:(uint64_t)channelId {
        if (kNERtcNoError != result && && kNERtcErrInvalidState != result) {
            //开始 cdn 推流失败,此时仍然处于 PK 直播中,可以根据业务情况进行相关处理,如需要继续结束需要参照第一步,再次调用开启推流
            return;
        }
    
        //停止 MediaRelay
        [[NERtcEngine sharedEngine] stopChannelMediaRelay];
        //停止旁路推流
        [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        }];
    }
    
  3. 停止 mediaRelay。

    主播 A 和 主播 B 分别调用 stopChannelMediaRelay 方法停止媒体转发功能。

    根据第 2 步, 在 onNERtcEngineStartPushStreamingWithResult 回调方法停止 MediaRelay

  4. 移除旁路推流任务。

    主播 A 和 主播 B 分别调用 removeLiveStreamTask 方法移除旁路推流任务。

    根据第 2 步, 在 onNERtcEngineStartPushStreamingWithResult 回调方法停止旁路推流。

示例代码

Objective-C//发起 PK
- (int)enterPK:(NSString *)channelName token:(NSString *)token uid:(uint64_t)uid {
    //step1 开始 mediaRelay(startMediaRelay)
    NERtcChannelMediaRelayConfiguration *mediaRelayConfig = [[NERtcChannelMediaRelayConfiguration alloc]init];
    NERtcChannelMediaRelayInfo *info = [[NERtcChannelMediaRelayInfo alloc]init];
    info.channelName = channelName;
    info.token = token;
    info.uid = uid;
    [mediaRelayConfig setDestinationInfo:info forChannelName:info.channelName];
    [[NERtcEngine sharedEngine] startChannelMediaRelay:mediaRelayConfig]

    //step2 开启旁路推流任务(addLiveStream,旁路任务中的音频参数保持跟 cdn 下相同(采样率、声道数等等))
    [[NERtcEngine sharedEngine] addLiveStreamTask:task compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
    }];

    //step3 等待旁路推流结果(onNERTCEngineLiveStreamState:taskID:url:),如果 state 为 kNERtcLsStatePushing 表示添加旁路任务成功,则停止 cdn 推流(stopPushStreaming)
    [[NERtcEngine sharedEngine] stopPushStreaming];

    //step4 等待 pk 对方加入房间后更新旁路推流任务 updateLiveStreamTask
    [[NERtcEngine sharedEngine] updateLiveStreamTask:task compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
    }];

    //step5 确认 mediaRelay 是否成功,如果失败根据实际情况做相应处理
    //等待 mediaRelay 任务状态回调 onNERtcEngineChannelMediaRelayStateDidChange:channelName:和 onNERtcEngineDidReceiveChannelMediaRelayEvent: channelName: error:确认 mediaRelay 是否成功
}

//结束 PK
- (int)leavePK {
    //step1 开始 cdn 推流
    [[NERtcEngine sharedEngine] startPushStreaming:config];

    //step2 等待 cdn 推流结果回调(onNERtcEngineStartPushStreamingWithResult:channelId:),
    //成功后停止 mediaRelay(stopMediaRelay)和移除旁路推流任务(removeLiveStreamTask:compeltion:)
    [[NERtcEngine sharedEngine] stopChannelMediaRelay];
    [[NERtcEngine sharedEngine] removeLiveStreamTask:task compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
    }];
}

注意事项

removeLiveStreamTask 的调用

  1. 当 PK 业务直接退出房间时候,需要调用 removeLiveStreamTask 停止旁路推流,防止流没有停止,观众还能拉到流,出现业务异常,可参考以下场景。

    Objective-C//主动 leaveChannel,退出房间 (不是切为单人直播)
    - (void)leavePK{
        [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        }];
        [[NERtcEngine sharedEngine] leavechannel];
    }
    
    //SDK disconnect 断开
    - (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason{
        [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        }];
    }
    
  2. 推荐每次开始 PK addLiveStreamTask 前,可以先调用 removeLiveStreamTask 停止之前的旁路推流任务,防止一些异常情况,出现 addLiveStreamTask 失败,导致业务异常,流程见开始 PK 第 2 步。

    Objective-C[[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
    }];
    [[NERtcEngine sharedEngine] addLiveStreamTask:[self mockLiveStreamInfoForPk] compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
        if (0 != errorCode) {
            //表示开启开启 PK 失败,此时仍然处于单人直播状态,此时业务可以根据自身定制。如果想要继续 PK 需要重新按照实现 PK 直播逻辑继续开始 PK。
        }
    }
    

异常情况的回调处理

  1. 旁路推流回调失败处理,回退至单推处理逻辑。

    Objective-C//开启旁路推流失败回调
    [[NERtcEngine sharedEngine] addLiveStreamTask:info compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
            if (0 != errorCode) {
                //停止 MediaRelay
                [[NERtcEngine sharedEngine] stopChannelMediaRelay];
                //停止旁路推流
                [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
                }];
            }
    }]
    
    //旁路推流状态回调
    - (void)onNERTCEngineLiveStreamState:(NERtcLiveStreamStateCode)state
                                taskID:(NSString *)taskID
                                    url:(NSString *)url {
        //表示 PK 成功
        if (state == kNERtcLsStatePushing) {
        //参考开始 PK 第 3 步停止单人直播
        } else if (state == kNERtcLsStatePushFail){
            //此时需要按照以下操作回退到单推
            [[NERtcEngine sharedEngine] stopChannelMediaRelay];
            [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
            }];
    
            [[NERtcEngine sharedEngine] startPushStreaming:config];
        }
    }
    
  2. MediaRelay 失败,回退至单推处理逻辑。

    Objective-C- (void)onNERtcEngineDidReceiveChannelMediaRelayEvent:(NERtcChannelMediaRelayEvent)event channelName:(NSString *)channelName error:(NERtcError)error {
        //表示失败
        if (event == NERtcChannelMediaRelayEventFailure) {
            //此时需要按照以下操作回退到单推
            [[NERtcEngine sharedEngine] stopChannelMediaRelay];
            [[NERtcEngine sharedEngine] removeLiveStreamTask:PKTaskId compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
            }];
    
            [[NERtcEngine sharedEngine] startPushStreaming:config];
        }
        //表示成功
        else if (event == NERtcChannelMediaRelayEventConnected){
            //此时表示 PK 建立成功
        }
    }
    

    以上回退时业务需要告知 PK 对方,同时回退。

主要回调

请在初始化时注册推流相关的回调,PK 场景需要关注的主要回调:

Objective-C//开始推流 startPushStreaming 结果回调
- (void)onNERtcEngineStartPushStreamingWithResult:(NERtcError)result channelId:(uint64_t)channelId{
    if (kNERtcNoError != result) {
        //kNERtcErrInvalidState 不需要关注
        if(kNERtcErrInvalidState != result){
            return;
            }
        //推流失败,业务按需处理,参考结束 PK 的第 2 步。
    }else{
        //推流成功,业务按需处理,参考结束 PK 的第 2 步。
    }
}

//停止推流 stopPushStreaming 结果回调
-(void)onNERtcEngineStopPushStreaming:(NERtcError)result{
    //可以不需要关注
}

//SDK 客户端和服务器断开连接
- (void)onNERtcEngineReconnectingStart{
    //此时表示客户端 SDK 无法连上服务器,正在重连,业务按需处理,可以提示主播
}

//SDK 客户端重连状态结果回调
- (void)onNERtcEngineRejoinChannel:(NERtcError)result{
    //此时表示重连成功,SDK 连上了服务器
}

//推流过程重连失败,最终断开回调
- (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason{
    //此时表示推流失败了,业务按需处理,如果需要继续推流,需要再次调用 startPushStreaming 接口
}

//MediaRelay 事件回调
- (void)onNERtcEngineDidReceiveChannelMediaRelayEvent:(NERtcChannelMediaRelayEvent)event channelName:(NSString *)channelName error:(NERtcError)error {
    //表示失败
    if (event == NERtcChannelMediaRelayEventFailure) {
        //参照开始 PK 第 6 步
    }
    //表示成功
    if(event == NERtcChannelMediaRelayEventConnected){
        //参照开始 PK 第 6 步
    }
}

//旁路直播状态回调
- (void)onNERTCEngineLiveStreamState:(NERtcLiveStreamStateCode)state
                            taskID:(NSString *)taskID
                                url:(NSString *)url {
    //表示 PK 成功
    if (state == kNERtcLsStatePushing) {
    //参考开始 PK 第 3 步停止单人直播
    }else{
    //参考开始 PK 第 3 步操作回退到单推
    }
}
此文档是否对你有帮助?
有帮助
去反馈
  • 功能原理
  • 注意事项
  • 前提条件
  • 实现 PK 直播
  • API 时序图
  • 开始 PK
  • 结束 PK
  • 示例代码
  • 注意事项
  • 主要回调