互动白板
  • iOS : Objective-C
  • Android
  • macOS
  • Windows
  • Web
  • Electron
  • 概述
  • 价格说明
  • 下载
  • 跑通示例源码
  • 接入指南
  • 快速开始
  • 场景实践
  • 客户端 API
  • 服务端 API
  • 常见错误码
  • 常见问题
  • 文档中心
  • 互动白板
  • 场景实践
  • 互动白板与文件共享的搭配使用

互动白板与文件共享的搭配使用

更新时间:2023-11-02 17:35

1 导读

1.1 简介

在实际业务开发过程中,互动白板 (ZegoWhiteboardView SDK)经常与文件共享 (ZegoDocsView SDK) 搭配使用。通常情况下,ZegoWhiteboardView 会覆盖在 ZegoDocsView 之上,然后通过一定的逻辑处理,就能实现在文件上绘制图元的业务需求。

1.2 概念解释

  • ZegoExpress-Video SDK:ZEGO 音视频互动 SDK,能够提供互动白板所需的实时信令传输的能力,其核心类为 ZegoExpressEngine。
  • ZegoWhiteboardView SDK:提供 ZEGO 互动白板服务的 SDK。
  • ZegoDocsView SDK:提供 ZEGO 文件共享服务 的 SDK。
  • ZegoWhiteboardView:在代码实现过程中,开发者直接使用的 ZegoWhiteboardView 视图。
  • ZegoDocsView:在代码实现过程中,开发者直接使用的 ZegoDocsView 视图。
  • 文件白板:ZegoWhiteboardView 覆盖在 ZegoDocsView 之上组成的视图。
  • 普通白板:ZegoWhiteboardView 视图。

2 前提条件

2.3.0 或以上 版本的 SDK 支持 Token 鉴权,若您需要升级鉴权方式,可参考 如何从 AppSign 鉴权升级为 Token 鉴权

3 实现流程

3.1 初始化 SDK

3.1.1 引入头文件

开发者在工程中引入头文件。

// 引入 ZegoExpress-Video SDK 头文件
#import <ZegoExpressEngine/ZegoExpressEngine.h>
// 引入 ZegoDocsView SDK 头文件
#import <ZegoDocsView/ZegoDocsView.h>
// 引入 ZegoWhiteboardView SDK 头文件
#import <ZegoWhiteboardView/ZegoWhiteboardView.h

3.1.2 初始化 ZegoExpress-Video SDK

初始化 ZegoExpress-Video SDK,详情可参考实时音视频的 快速开始 - 实现流程

/**
* appID:ZEGO 为开发者签发的应用 ID,请从 ZEGO 控制台 https://console-express. 
* zego.im 申请,取值范围为 0~4294967295。
* appSign: ZEGO 为开发者签发的应用 appSign,请从 ZEGO 控制台 https://console-express.  zego.im 申请
* scenario: 所属的应用场景,ZegoScenarioDefault为通用场景
*/
ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];
profile.appID = <#appID#>;
profile.appSign = <#appSign#>;
profile.scenario = ZegoScenarioDefault;
// 创建引擎,并注册 self 为 eventHandler 回调。不需要注册回调的话,eventHandler 参数可以传 nil,后续可调用 "-setEventHandler:" 方法设置回调
[ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];

如果您需要切换鉴权方式,请参考 如何从 AppSign 鉴权升级为 Token 鉴权

3.1.3 初始化 ZegoWhiteboardView SDK

  1. 使用 ZegoWhiteboardManagerinitWithCompleteBlock 接口初始化 ZegoWhiteboardView SDK。

如果回调 completeBlock 中的 “errorCode” 为 0,代表初始化成功,可进行更多操作。errorCode 可参考 常见错误码

[[ZegoWhiteboardManager sharedInstance] initWithCompleteBlock:^(ZegoWhiteboardViewError errorCode) {
     //errorCode = 0 代表初始化成功
}];
  1. 监听 ZegoWhiteboardView SDK 回调。

设置 ZegoWhiteboardManager 的代理,监听常用回调事件:[[ZegoWhiteboardManager sharedInstance] setDelegate:self]

建议关注以下事件:

//当房间内其他用户创建新的ZegoWhiteboardView的时,会收到新增ZegoWhiteboardView的回调
//用户可以将新增的 whiteboardView 添加到视图中
- (void)onWhiteboardAdd:(ZegoWhiteboardView *)whiteboardView;

//当房间内其他用户删除 ZegoWhiteboardView 时,会收到 ZegoWhiteboardView 移除的回调
//用户可以根据 whiteboardID 移除相应的 ZegoWhiteboardView
- (void)onWhiteboardRemoved:(ZegoWhiteboardID)whiteboard

3.1.4 初始化 ZegoDocsView SDK

使用 ZegoDocsViewManagerinitWithConfig 接口初始化 ZegoDocsView SDK。

ZegoDocsViewConfig *config = [ZegoDocsViewConfig new];
config.appID = appID;//即构 AppID
config.appSign = appSign;// ZEGO 为开发者签发的应用 appSign,请从 ZEGO 控制台申请
config.dataFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"data"];//SDK 内部数据保存目录
config.cacheFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"doc"];//SDK 缓存目录
config.logFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"log"];//SDK 日志保存目录

[[ZegoDocsViewManager sharedInstance] initWithConfig:config completionBlock:^(ZegoDocsViewError errorCode) {
    if (errorCode == ZegoDocsViewSuccess) {
        NSLog(@"初始化 SDK 成功");
    } else {
        NSLog(@"初始化 SDK 失败 %ld",errorCode);
    }
}];

3.2 登录房间

调用 ZegoExpressEngineloginRoom 接口登录房间。

  1. 需保证 “roomID” 信息的全局唯一。
  2. “userID” 与 “userName” 不能为 “nil” 否则会导致登录房间失败。
  3. ZegoUser 的构造方法 ZegoUser userWithUserID: 会将 userName 设为与传的参数 userID 一样。
  4. 每个 “userID” 必须唯一,建议设置成一个有意义的值,开发者可将 “userID” 与自己业务账号系统进行关联。

错误码详情请参考 登录房间错误码

- (void)loginRoom {
    // roomID 由您本地生成,需保证 “roomID” 全局唯一。不同用户要登录同一个房间才能进行通话
    NSString *roomID = @"room1";

    // 创建用户对象,ZegoUser 的构造方法 userWithUserID 会将 “userName” 设为与传的参数 “userID” 一样。“userID” 与 “userName” 不能为 “nil”,否则会导致登录房间失败。
    // userID 由您本地生成,需保证 “userID” 全局唯一。
    ZegoUser *user = [ZegoUser userWithUserID:@"user1"];

    // 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
    ZegoRoomConfig *roomConfig = [[ZegoRoomConfig alloc] init];
    roomConfig.isUserStatusNotify = YES;
    // 登录房间
    [[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:roomConfig callback:^(int errorCode, NSDictionary * _Nullable extendedData) {
        // (可选回调) 登录房间结果,如果仅关注登录结果,关注此回调即可
        if (errorCode == 0) {
            NSLog(@"房间登录成功");
        } else {
            // 登录失败,请参考 errorCode 说明 https://doc-zh.zego.im/article/4377
            NSLog(@"房间登录失败");
        }
    }];
}

3.3 创建 ZegoDocsView

3.3.1 创建 ZegoDocsView 并添加到视图中

// 1. init
self.docView = [[ZegoDocsView alloc] init]; //初始化文件View
self.docView.backgroundColor = UIColor.whiteColor; //设置文件View背景颜色
[self.docView setDelegate:self]; //设置文件View 代理通知
[self.view addSubview:self.docView]; //添加到自己需要展示的View 上
// 2.设置 frame
self.docView.frame = CGRectMake(0, 0, 500, 500);

3.3.2 加载文件

调用 ZegoDocsViewloadFileWithFileID 接口,传入上传文件 uploadFile 成功后获取的 fileID 加载文件。

如果遇到加载失败的情况,请参考 常见错误码。常见的原因如下:

  • 开发者申请的 AppID 不包含文件共享的能力,需要向 ZEGO 技术支持申请开通。
  • 上传文件和加载文件使用不同的 AppID。例如,开发者在 AppID1 中上传了文件1,却在 AppID2 中加载文件1的 fileID,此时会找不到文件。
  • 文件上传和加载环境不一致。例如,文件在正式环境中上传,却在测试环境中加载,此时会找不到文件。
// fileID 在 uploadFile 成功之后获取
NSString *fileID = @"";
[docsView loadFileWithFileID:fileID //后台获取到文件ID
 authKey:@"" //鉴权key。可以为空。根据你所申请环境是否需要鉴权填入。最好是通过后台获取
 completionBlock:^(ZegoDocsViewError errorCode) {
    //创建白板模型
    ZegoWhiteboardViewModel *model = [[ZegoWhiteboardViewModel alloc] init];
    // 当前示例为 创建 宽高比为 16 : 9,横向页数为 5 页的白板
    model.aspectWidth = docsView.contentSize.width;
    model.aspectHeight = docsView.contentSize.height;
    model.pageCount = docsView.pageCount;
    model.roomID = @"123456"; //传入房间ID,便于接收端判断,需要保证 roomID 和登录时的 roomID 一致
    model.name = @"白板";
    model.fileInfo.fileName = docsView.fileName;
    model.fileInfo.fileID = docsView.fileID;
    model.fileInfo.fileType = docsView.fileType;
    [[ZegoWhiteboardManager sharedInstance] createWhiteboardView:model completeBlock:^(ZegoWhiteboardViewError errorCode, ZegoWhiteboardView *whiteboardView) {
       //将 ZegoWhiteboardView 添加到界面中
       whiteboardView.frame = CGRectMake(0, 0, 300, 300);
       //通常将白板覆盖在 docsView 之上,这样就能实现在文件上绘制图元的功能
       [self.view addSubView: whiteboardView];

    }];
}];

3.4 创建并展示 ZegoWhiteboardView

在文件共享和互动白板白板搭配使用时,开发者需要在文件加载成功后,通过如下步骤创建和展示 ZegoWhiteboardView。

  1. 通过 ZegoDocsView 的 “fileName”、“fileID”、“fileType” 来构建 fileInfo,并设置给 ZegoWhiteboardViewModel
  2. 调用 createWhiteboardView 接口创建 ZegoWhiteboardView,并设置好 “frame” 添加到界面中。

通常开发者会将白板覆盖在 ZegoDocsView 之上,这样就能实现在文件上绘制图元的功能。

3.5 ZegoDocsView 与 ZegoWhiteboardView 之间的同步滚动与翻页

  1. 文件同步白板的翻页滚动

先调用 ZegoDocsView 的翻页接口 flipPage,再调用 ZegoWhiteboardView 的滚动接口 scrollToHorizontalPercent。如果接口调用 errorCode 为 0 代表滚动成功,非 0 代表滚动同步失败,此时需要再次同步 ZegoDocsView 到正确的页数。

[self.currentDocsView flipPage:page step:step completionBlock:^(BOOL isScrollSuccess) {
    float pageNum = (float)MAX((weakSelf.currentDocsView.currentPage - 1), 0);
    [weakSelf.currentWhiteboardView scrollToHorizontalPercent:0 verticalPercent: pageNum/ (float)weakSelf.currentDocsView.pageCount pptStep:weakSelf.currentDocsView.currentStep completionBlock:^(ZegoWhiteboardViewError error_code, float horizontalPercent, float verticalPercent, unsigned int pptStep) {
            if (error_code != 0) {
            //白板滚动同步失败
                NSInteger pageNumFianel = round(verticalPercent * weakSelf.docsView.pageCount) + 1;
                if (pageNumFianel == weakSelf.docsView.currentPage && pptStep == self.docsView.currentStep) {
                    return;
                }
                [weakSelf.docsView flipPage:pageNumFianel step:pptStep completionBlock:^(BOOL isScrollSuccess) {

                }];
            }
    }];
}];
  1. 白板同步文件的滚动百分比

监听到 ZegoWhiteboardViewonScrollWithHorizontalPercent 回调时,同步文件到对应的滚动百分比(动态 PPT 或者 H5 课件同步到对应的页码)。

if (self.currentWhiteboardView.whiteboardModel.fileInfo.fileType == ZegoDocsViewFileTypeDynamicPPTH5
            || self.currentWhiteboardView.whiteboardModel.fileInfo.fileType == ZegoDocsViewFileTypeCustomH5) {
            //动态ppt或者H5课件
            CGFloat yPercent = self.currentWhiteboardView.contentOffset.y / self.currentWhiteboardView.contentSize.height;
            NSInteger pageNo = round(yPercent * self.currentDocsView.pageCount) + 1;
            //同步文件视图内容
            [self.currentDocsView flipPage:pageNo step:MAX(self.currentWhiteboardView.whiteboardModel.pptStep, 1) completionBlock:^(BOOL isScrollSuccess) {

            }];

        } else {
        //普通静态文件
            [self.currentDocsView scrollTo:verticalPercent completionBlock:^(BOOL isScrollSuccess) {

            }];
            }
        }

3.6 销毁文件和白板

通过调用 ZegoWhiteboardViewdestroyWhiteboardID 接口可以销毁指定的白板,在收到成功回调 onRemoteWhiteboardRemoved 后再移除对应的 ZegoDocsView

__weak typeof(self) weakSelf = self;
[[ZegoWhiteboardManager sharedInstance]destroyWhiteboardID:whiteboardID completeBlock:^(ZegoWhiteboardViewError errorCode, ZegoWhiteboardID whiteboardID) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if ([strongSelf.delegate respondsToSelector:@selector(onRemoteWhiteboardRemoved:)]) {
        [strongSelf.delegate onRemoteWhiteboardRemoved:whiteboardID];
    }
}];

//接收远端白板移除消息
//whiteboardViewArray 存放生成的白板的数组
- (void)onRemoteWhiteboardRemoved:(ZegoWhiteboardID)whiteboardID {
    for (int i = 0; i < self.whiteboardViewArray.count; i++) {
    //找到对应的需要销毁的白板
        ZegoWhiteboardView *whiteboardView = self.whiteboardViewArray[i];
        if (whiteboardID == whiteboardView.whiteboardModel.whiteboardID) {
            [self onRemoveWhiteboardIdnex:i];
            break;
        }
    }
}

- (void)removeWhiteboardWithID:(ZegoWhiteboardID)whiteboardID {
    ZegoWhiteboardView *removeView = [self fetchWhiteboardView:whiteboardID];//找到对应的白板view
    [self.whiteboardViewArray removeObject:removeView];//从白板列表中移除对应的白板视图
    if ([self.currentWhiteboardView isEqual:removeView]) {
        [removeView removeFromSuperview];
        self.currentWhiteboardView.whiteboardViewDelegate = nil;
        self.currentDocsView.delegate = nil;
        self.currentWhiteboardView = nil;
        self.currentDocsView = nil;
    }
    for (ZegoDocsView *docsView in self.docsViewArray) {
    //找销毁白板对应的文件view,将其移除
        if (docsView.fileID == removeView.whiteboardModel.fileInfo.fileID) {
            [docsView removeFromSuperview];
            [self.docsViewArray removeObject:docsView];
            break;
        }
    }
}

3.7 ZegoDocsView 和 ZegoWhiteboardView 的大小变更

如果 ZegoDocsView 的大小发生了变更(比如横竖屏切换),需要重新设置 ZegoWhiteboardView 的可见区域。

docsView.frame = newFrame;
[docsView layoutIfNeeded];  //更新 visibleSize
CGSize visibleSize = docsView.visibleSize;
whiteboardView.frame = [self frameWithSize:visibleSize docsViewFrame:docsViewFrame];
whiteboardView.contentSize = docsView.contentSize;

// 根据 docsView 的 frame 计算白板的 frame
- (CGRect)frameWithSize:(CGSize)visibleSize docsViewFrame:(CGRect)frame {
    CGFloat x = frame.origin.x + (frame.size.width - visibleSize.width) / 2;
    CGFloat y = frame.origin.y + (frame.size.height - visibleSize.height) / 2;
    return CGRectMake(x, y, visibleSize.width, visibleSize.height);
}

3.8 (可选)Token 过期更新

此步骤介绍 Token 过期时如何处理;如果您未使用 Token 鉴权,可忽略此步。

监听 Token 过期通知,如果 Token 过期,需要主动更新 Token。

- (void)onRoomTokenWillExpire:(int)remainTimeInSecond roomID:(NSString *)roomID {
    NSString *token = [MyToken getToken]; // 重新请求开发者服务端获取 Token

    [[ZegoExpressEngine sharedEngine] renewToken:token roomID:roomID];

    [[ZegoDocsViewManager sharedInstance] renewToken:token];
}

4 示例源码

以上介绍了互动白板和文件共享搭配使用的基本场景,更复杂的交互场景请查看完整的示例源码,详情请参考 下载示例源码

相关文档

本篇目录