功能实现流程

下载pdf
更新时间:2019-08-14 14:36

实时视频场景的典型使用之一是同一会话中的多用户进行视频实时通话。即多个人一起互相推拉流,例如:

以2人实时视频通话的场景为例,ZEGO SDK 的详细 API 调用时序图如下:

请注意:

  1. 上面流程中以 2 名房间成员间的实时视频为例,实际上 ZEGO SDK 支持多人实时视频。建议开发者参考上述流程设计自己的多人实时视频通话的场景。

  2. 为了便于开发者更快理解示例专题视频通话模块中的逻辑,下述每节会将功能核心源码片段挑出来并加以讲解。开发者亦可直接参考 视频通话模块的源码

1 初始化 SDK

参考文档:快速开始-初始化,示例代码如下:

ZGVideoTalkDemo.m

/**
 初始化 ZGVideoTalkDemo 实例。

 @param appID appID
 @param appSign appSign
 @param completionBlock 初始化回调。errorCode == 0 表示成功。
 @return 是否初始化成功
 */
- (instancetype)initWithAppID:(unsigned int)appID
                      appSign:(NSData *)appSign
              completionBlock:(void(^)(ZGVideoTalkDemo *demo, int errorCode))completionBlock
- (instancetype)initWithAppID:(unsigned int)appID
                      appSign:(NSData *)appSign
              completionBlock:(void(^)(ZGVideoTalkDemo *demo, int errorCode))completionBlock {
    if (appSign == nil) {
        ZGLogWarn(@"appSign 不能为空。");
        return nil;
    }

    if (self = [super init]) {
        self.remoteUserStreams = [NSMutableArray<ZegoStream *> array];

        __weak typeof(self) weakSelf = self;
        ZegoLiveRoomApi *api = [[ZegoLiveRoomApi alloc] initWithAppID:appID appSignature:appSign completionBlock:^(int errorCode) {
            __strong typeof(weakSelf) strongSelf = weakSelf;

            if (strongSelf) {
                strongSelf.apiInitialized = errorCode == 0;
            }

            ZGLogInfo(@"初始化 zego api,errorCode:%d", errorCode);
            if (completionBlock) {
                completionBlock(strongSelf, errorCode);
            }
        }];

        if (api) {
        // 设置各类型代理,处理相应回调
            [api setRoomDelegate:self];
            [api setPublisherDelegate:self];
            [api setPlayerDelegate:self];
        }

        self.zegoApi = api;
    }
    return self;
}

2 设置监听房间内实时通话用户推流变化的代理

已经在房间内进行视频通话的用户需要获取后来进入房间进行视频通话的用户推流的通知,并且也需要获取中途退出视频通话的用户停止推流的通知。在获取这些通知之后,先前在房间内进行视频通话的用户应拉取房间内新增的推流、停止拉取房间内停推的流,并做相应的 UI 展示。示例中的核心代码如下:

ZGVideoTalkDemo.m

/**
  * 通过设置 api 房间代理,可以收到房间内的一些信息回调。通过回调 onStreamUpdated:streams:roomID: 可以收到房间内流变化的事件
  */
[api setRoomDelegate:self];
ZGVideoTalkDemo.m

/**
  * 房间内流变化回调。房间内增加流、删除流,均会触发此回调,主播推流自己不会收到此回调,房间内其他成员会收到。
  */
- (void)onStreamUpdated:(int)type streams:(NSArray<ZegoStream *> *)streamList roomID:(NSString *)roomID {
    BOOL isTypeAdd = type == ZEGO_STREAM_ADD;//流变更类型:增加/删除  
    if (![roomID isEqualToString:self.talkRoomID]) {
        return;
    }
    if (isTypeAdd) {
        [self addRemoteUserTalkStreams:streamList];
    } else {
        NSArray<NSString *> *streamIDs = [streamList valueForKeyPath:@"streamID"];
        [self removeRemoteUserTalkStreamWithIDs:streamIDs];
    }
}

3 登录房间并推拉流,开始视频通话

用户间进行实时视频对话前,需要先登录到同一个房间,在收到登录房间成功的回调之后可以直接调用 ZEGO SDK 的 API 接口进行推拉流操作,同时对房间内已存在的流进行拉流,实现视频通话。示例中的核心接口代码如下:

ZGVideoTalkDemo.m

/**
 加入视频聊天

 @param talkRoomID 通话房间 ID。根据业务取系统唯一值
 @param userID 用户 ID。根据业务取系统惟一值,最好有意义
 @param callback 回调。errorCode 为 0 表示加入成功,joinTalkUserIDs:房间内已加入到通话的用户 ID 列表
 @return 请求是否发送成功
 */
- (BOOL)joinTalkRoom:(NSString *)talkRoomID
              userID:(NSString *)userID
            callback:(void(^)(int errorCode, NSArray<NSString *> *joinTalkUserIDs))callback {
    if (talkRoomID.length == 0 || userID.length == 0) {
        ZGLogWarn(@"必填参数不能为空!");
        return NO;
    }

    if (![self checkApiInitialized]) {
        return NO;
    }

    if (self.joinRoomState != ZGVideoTalkJoinRoomStateNotJoin) {
        ZGLogWarn(@"已登录或正在登录,不可重复请求登录。");
        return NO;
    }

    self.talkRoomID = talkRoomID;
    self.localUserID = userID;
    [self updateLocalUserJoinRoomState:ZGVideoTalkJoinRoomStateOnRequestJoin];

    // 设置 ZegoLiveRoomApi 的 userID 和 userName。在登录前必须设置,否则会调用 loginRoom 会返回 NO。
    // 业务根据需要设置有意义的 userID 和 userName。当前 demo 没有特殊需要,可设置为一样
    [ZegoLiveRoomApi setUserID:userID userName:userID];

    Weakify(self);
    BOOL result = [self.zegoApi loginRoom:talkRoomID role:ZEGO_ANCHOR withCompletionBlock:^(int errorCode, NSArray<ZegoStream *> *streamList) {
        Strongify(self);

        ZGLogInfo(@"登录房间,errorCode:%d, 房间号:%@, 流数量:%@", errorCode, talkRoomID, @([streamList count]));

        BOOL isLoginSuccess = errorCode == 0;
        NSArray<NSString *> *joinTalkUserIDs = nil;
        [self updateLocalUserJoinRoomState:isLoginSuccess?ZGVideoTalkJoinRoomStateJoined:ZGVideoTalkJoinRoomStateNotJoin];

        if (isLoginSuccess) {
            joinTalkUserIDs = [streamList valueForKeyPath:@"userID"];
            // 直接加入通话,推流处理
            [self internalJoinTalk];
        // 添加房间内已有流列表,进行拉流处理
            [self addRemoteUserTalkStreams:streamList];
        }
        if (callback) {
            callback(errorCode, joinTalkUserIDs);
        }
    }];
    if (!result) {
        self.talkRoomID = nil;
        self.localUserID = nil;
        [self updateLocalUserJoinRoomState:ZGVideoTalkJoinRoomStateNotJoin];
    }
    return result;
}

登录房间参考文档:快速开始-登录房间

推流参考文档:快速开始-推流

注意:在推流之前应该调用 -setPublisherDelegate: 设置推流的回调,以监听推流是否成功。若推流不成功,一般为网络问题,SDK内部会做重试工作,开发者也可根据情况做有限次数的推流重试,或给出友好的交互提示。

拉流参考文档:快速开始-拉流

注意:在拉流之前应该调用 -setPlayerDelegate: 设置拉流的回调,以监听拉流是否成功。若拉流不成功,一般为网络问题,SDK内部会做重试工作,开发者也可根据情况做有限次数的拉流重试,或给出友好的交互提示。

4 结束视频通话

用户在视频通话过程中退出视频通话,应该停止推流、停止拉流、退出房间,释放对应的 UI 对象资源等。

视频通话专题模块中结束视频通话相关源码片段如下,仅供参考:

ZGVideoTalkViewController.m

- (void)closePage:(id)sender {
    [self exitRoomWithRequestLeave:YES];
}

- (void)exitRoomWithRequestLeave:(BOOL)requestLeave {
    [self removeJoinUsers:[self getAllJoinUserIDs]];
    [self.videoTalkDemo setDelegate:nil];
    if (requestLeave) {
        [self.videoTalkDemo leaveTalkRoom];
    }
    [self dismissViewControllerAnimated:YES completion:nil];
}
ZGVideoTalkDemo.m

/**
 离开视频聊天房间。
 */
- (BOOL)leaveTalkRoom {
    if (![self checkApiInitialized]) {
        return NO;
    }

    if (self.roomLoginState != ZGVideoTalkDemoRoomLoginStateHasLogin) {
        ZGLogWarn(@"未登录房间,无需离开房间。");
        return NO;
    }

    [self.zegoApi stopPreview];
    [self stopPublishing];
    BOOL result = [self.zegoApi logoutRoom];
    if (result) {
        [self onLogout];
    }
    return result;
}