插件 2.0

接入指南

在云信官网上下载最新版本的 WEB 端 SDK 压缩包,解压后,会得到这样一个目录结构

├── js
│   ├── NIM_Web_Chatroom_v7.1.0.js
│   ├── NIM_Web_NIM_v7.1.0.js
│   ├── NIM_Web_NRTC_v7.1.0.js
│   ├── NIM_Web_Netcall_v7.1.0.js
│   ├── NIM_Web_SDK_v7.1.0.js
│   ├── NIM_Web_WebRTC_v7.1.0.js
│   └── NIM_Web_WhiteBoard_v7.1.0.js
├── 插件
│   ├── DrawPlugin.2.0.0.js // 新版本
│   ├── DrawPlugin.js  // 老版本
│   ├── DrawPluginUI.2.0.0.js
│   ├── RecordPlayer.2.0.0.js
│   ├── RecordPlayerUI.2.0.0.js
│   ├── replayTest.html // 示例
│   ├── toolbarTest.html // 示例
│   ├── webview.2.0.0.js // webview
│   └── webview.html  // webview页面示例

在使用新白板插件之前,需要先拿到 IM 和 WhiteBoard 的实例对象,IM 的实例对象负责 IM 的消息发送,WhiteBoard 的实例对象是负责传输白板插件绘制的数据内容。 请参考互动白板-初始化 章节里的内容,和[互动白板-双人通话][互动白板-多人通话], 创建 im 实例和 whiteboard 实例,通过whiteboard.createChannel 或者 whiteboard.joinChannel 创建并加入房间后,就可以初始化 DrawPlugin 实例了。

工具栏的示例代码可以看github 上的仓库

引入插件 2.0.js

在项目里可以使用 <script src="/path/to/lib/DrawPlugin.2.0.0.js"></script> 或者 import * as DrawPlugin from 'path/to/lib/DrawPlugin.2.0.0.js', 引入对象DrawPlugin

获得 DrawPlugin 实例

成功加入白板房间后,可以使用以下代码获得 DrawPlugin 实例,容器会被画布填充 Canvas 元素,此时用户还无法与画布交互。执行完下面代码后,鼠标在画布上是激光笔工具,可以看到红色激光笔随着鼠标移动。

let handler = DrawPlugin.getInstance({
  account: "user1", // 当前使用白板用户的account,
  ownerAccount: "user1000", // 白板房间的创建者的account
  identity: "owner", // 可以传入字符串 'normal' 或者 'owner','owner' 代表房主,'normal' 代表普通学生
  appKey: "1ee5a51b7d008254cd73b1d4369a9494", // 当前im SDK使用的appkey
  nim: nim,
  whiteboard: whiteboard
  whiteboardContainer: document.getElementById("wb"), // 这里传入白板插件画图容器
  debug: true, // 是否开启drawPlugin的debug,
});

调用 getInstance 可以传入的具体参数如下:

属性 类型 说明
debug bool 可选,是否开启调试, 默认 false;除此以外,需要在浏览器的 console 里设置 localStorage.debug = true,将会输出更多调试内容。
account string 必选, 当前使用白板用户的 account
ownerAccount string 必选, 白板房间的创建者的 account
whiteboardContainer HTMLElement 必选, 白板容器的 DOM 节点
identity 'owner' / 'normal' 必选, 当前参与白板画板的身份,'owner' 代表房主,'normal' 代表普通学生,一个白板 channel 里只能有一个'owner' 。owner身份会尽量使用容器大小作为自己的画布大小,normal 身份会使用老师传过来的数据作为画布大小。
appKey string 必选,当前 im sdk 使用 appkey
width number 可选,白板画布的宽度,白板插件会根据宽高比在各端保持一致的画布比例,保证绘画内容不歪曲。配置宽度同时必需配置高度,若不配置宽高,则房主的宽高将会默认撑满容器
height number 可选,白板画布的高度,白板插件会根据宽高比在各端保持一致的画布比例,保证绘画内容不歪曲
backgroundUrl string 可选,图片 url 地址或者 base64 编码的图片,白板画布在加载 PPT 文档时,画布中展示的图片
clearTransparentPix boolean 可选,是否清除一些透明像素
maxUserHistory number 可选,默认为 10,用户可以撤销自己的历史记录的长度,可以设置为Number.POSITIVE_INFINITY

inited 事件

当我们调用DrawPlugin.getInstance方法后,白板插件会进入页面初始化流程,会初始化当前画布的比例。

只有抛出inited事件后,白板才算初始化完毕,才能正确响应 handler 的方法。建议用户在inited事件后,才设置画布的颜色,工具等设置,建议开发者在此处调用 handler 的方法来设置默认工具等初始化状态。如果是普通用户端,需要在 enableDraw 以后设置画布工具的状态。

handler.on("inited", () => {
  // 在初始化完成后,执行下面的方法,
  handler.readyToSyncData(); // 建议在inited方法后调用同步操作
  handler.setEnable(true); //   调用handler上的方法,让画布可以交互。必须要enableDraw以后,才能设置工具,大小和颜色等等
  handler.useTool("free"); //   使用自由画笔工具,
});

当画布同步完成以后,画布就可以使用了,各端也都会展示相同的画笔内容。

同步流程

当我们调用handler.readyToSyncData方法后,白板插件会进入如下的初始化流程:

  1. 如果当前用户是房主,会将自己的画布容器比例通知其他成员,然后马上抛出afterSync事件。其他成员收到消息后,会清空画布内容,并设置自己的画布比例和房主的画布比例一致,然后将自己的插件状态同步给房主,避免普通用户的画布状态突然恢复为默认状态。

  2. 如果当前用户是普通成员,会向房主请求同步数据,此时插件进入等待状态,直到房主返回数据,才会继续初始化。房主收到要求同步数据的消息后,会取得将当前画布状态的快照,然后经过 gzip 压缩后返回给当前的普通用户。普通用户收到画布快照后,会还原画布内容,然后还原自己保存在房主那里的状态(如果快照里没有,将使用默认状态),然后抛出afterSync事件。

画布比例同步

owner初始化时,会使用自己容器的宽高作为画布的宽高,如果设置了width或者height,将会以设置的值作为画布的宽高,然后将这个宽高发送给其他用户。其他用户收到这个宽高以后,也会设置画布为这个宽高。画布始终会尽量充满容器,所以在normal端的画布会等比例缩放。owner端改变窗口大小,其他端的画布也会跟着改变。建议owner端对容器宽高比改变特别大的时候,清空画布内容重新书写。

handler 上的实例方法

方法 参数类型 说明
setFileObj object 传入 IM 转码完成后返回的对象,参考文档转码这一章节里的 getFileList 返回的数据,handler 自动会转换为插件所需的格式 ,然后设置为画布的背景;画布背景不可以填充,擦除
setFileObjRaw object 手动传入插件所需的格式
clearImage void 清除图片格式的 PPT 背景
setTool string 设置当前画布使用的工具,工具具体参数,请见下方的工具图表
setFontSize number 设置文字工具的字体大小
setContainer HTMLElement 设置画布容器,只有在调用 DrawPlugin.getInstance 没有传入 container 时,才需要手动调用这个方法
setColor string 设置画笔颜色,需要为"#xxxxxx"格式的颜色
setSize number 设置画笔大小,
enableDraw boolean 传入 true 时,表示允许使用画布;传入 false 时,将不允许使用画布 。注意,在不允许交互的情况下,DrawPlugin 实例对设置颜色,设置工具等等都是不会生效的。
changeRoleToPlayer void 等同于 enableDraw(true)
changeRoleToAudience void 等同于 enableDraw(false)
undo void 撤销画笔操作,只能撤销自己的,撤销的数量受 maxUserHistory 控制
redo void 重做画笔操作,只能重做自己画的内容
clear void 清空当前画布所有笔画数据,不包括 PPT
resetState void 重置画布状态,建议在断线重连后,还原默认画布状态调用
page string 参数可以为,'leftEnd' / 'left' / 'up' / 'down' / 'right' / 'rightEnd' 翻页操作
getImageBlob (callback,mimeType?,quality?) Canvas.prototype.toBlob 所需的参数
getImageURL (mimeType?, quality?) 返回 base64 编码的图片数据 ,Canvas.prototype.toDataURL 所需的参数
destroy void 销毁实例

以下是 setTool 可以传入的工具类型字符串。

工具类型 说明
flag 红色激光笔工具
free 自由画笔工具
line 直线工具
rect 直角空心矩形工具
solidRect 直角实心矩形工具
roundRect 圆角空心矩形工具
solidRoundRect 圆角实心矩形工具
circle 空心圆形工具
solidCircle 实心圆形工具
ellipse 空心椭圆工具
solidEllipse 实心椭圆工具
fill 油漆桶填充工具
text 文字工具
erase 像素橡皮擦

handler 上的事件

事件名称 事件参数类型 说明
inited void 初始化画布完成后会调用,画布会计算容器大小
afterSync void 或 同步数据快照对象 普通用户同步完成后,会返回快照数据,房主不会返回快照
userUpdate (account:string, updated:object) 第一个参数是用户状态更新的 account, udpated 是用户更新了哪些状态的 diff 对象
userShapes (account:string,visible:number,recycle:number) 第一个参数是用户状态更新的 account ,visible 是用户的展示中的图层数, recycle 是用户可以重做的次数
globalShapes (count:number) 全局的图层总数
globalFile (file:object) 全局的画布里使用的 PPT 文档
canDraw boolean 画布允许绘制或者不允许绘制时,会抛出事件

handler 上的属性

    userMap: { [uid: string]: any }; // shadow copy,映射当前的worker里的用户状态
    file?: IFileActionOption // 当前使用的ppt数据
    totalShapes: number; // 所有图层的总数
    isEnable: boolean; //  是否允许绘制,当前画布是否允许绘制

DrawPluginUI 实例

为了方便用户使用图形 UI 对画布进行操作,建议在界面上展示图形界面封装对 DrawPlugin 实例方法的调用。开发者可以引入 DrawPluginUI 来实例化一个工具栏,方便用户进行操作。目前的 DrawPluginUI 使用的是preact框架开发的,只是一个 SampleCode,具体代码放在 github 上,开发者可以参考里面的逻辑,自行实现其他框架的工具栏以及其他定制需求。开发者也可以提 PR 到 github 仓库里,增强 DrawPluginUI 的功能。

在项目里可以使用 <script src="/path/to/lib/DrawPluginUI.2.0.0.js"></script> 或者 import * as DrawPluginUI from 'path/to/lib/DrawPluginUI.2.0.0.js', 引入对象DrawPluginUI

DrawPluginUI 上只有mountunmount两个方法,如下所示:


 mount = (
  root: HTMLElement, // mount方法的第一个参数是,工具栏挂载的容器DOM
  user: { account: UID_TYPE, identity: 'owner' | 'normal' }, // 当前工具栏使用的account和identity
  handler: WBHook, // DrawPlugin实例
  options: UIControlDefaultOptions, // 工具栏支持的自定义配置项
  compare?: Element // 可选参数,preact.render方法里的compare参数
) => [PreactElement, app] ; // 将会返回PreactElement对象和它挂载的DOM节点,在调用unmout的时候,需要将这个DOM节点,作为第二个参数传入unmount方法

unmount = (
  root: HTMLElement, //工具栏挂载的容器DOM
   app: Element // 工具栏挂载的DOM节点,用于替换
   ) => void;

)

使用的示例代码如下:

handler.on("inited", function() {
  handler.enableDraw(true);
  handler.setSize(6);
  handler.setTool("free");

  // 挂载工具栏
  DrawPluginUI.mount(
    document.getElementById("tb"),
    { account: "cs1", identity: "owner" }, // 注意identity参数
    handler, // 传入DrawPlugin实例
    {
      customClear: false, // 是否使用自定义事件通知
      toolbar: [
        "flag",
        "free",
        { type: "text" }, // 等同于 "text"
        "fill",
        { type: "shapes", items: ["line", "rect", "circle"] },
        { type: "formats", fontsize: [23, 30, 44] },
        "erase",
        "undo",
        "redo",
        "clear",
        { type: "customTXT", label: "文档库", eventName: "selectDoc" } // 文档库建议普通用户不要设置
      ]
    }
  );
});

mount 方法传入的工具栏配置项的结构如下:

interface UIControlDefaultOptions {
  customClear?: boolean; // 是否使用自定义清除,当用户点击clear按钮时,将不会清除画布,而是以customEvent事件通知,可以做二次弹窗确认
  toolbar: ToolbarArray; // 这是一个数组,可以传入 上面 setTool可以传入的字符串工具类型
}

以下参数都是合法的工具栏配置参数,toolbar 数组里的顺序可以随意调换

{
  toolbar:[
    'free' // 这个是字符串类型的工具名称,和传入 {type:'flag'} 对象 是等价的
    {type:'free'}, // 这两个是等价的,
    // setTool可以传入的参数都可以这么设置
  ]
}

// 以下是特殊的类型

{
  toolbar:[
    {
     type: "shapes", // shapes类型可以配置items数组,建议传入绘画图形的集合
     items: [
       "line", "rect", "circle"
       ]
    },
    {
      type: "formats", // formats类型可以配置fontsize,size,colors 数组,若没有设置,将会使用默认值
      fontsize: [23, 30, 44], // 设置工具的文本的大小,建议不要太多
      // size:[4,6,12], // 也可以不传入 ,设置工具的大小,建议不要太多
      colors: ['#334455','#ff0000'] ,// 建议不要太多
    },
    {
      type: "customTXT", // 自定义文字,可以配置label,和eventName,
      label: "文档库",
      eventName: "selectDoc" ,// 这个eventName将会在 handler上的'custom'
    },
    {
      type: "customSVG",
      label: "<svg ></svg>",// 这里是svg文本,注意做好XSS防范
      eventName: 'evt1'
    },
    {
      type: "customIMG",
      label: "https://163.com/logo.jpg", // 可以是图片的url,也可以是base64格式的
      eventName: 'evt1'
    },
  ]
}

当使用 DrawPluginUI 后,会在 hanlder 上抛出 customEvent 事件,示例如下:

 handler.on('customEvent', function (event) { //这里的event是在
      if (event === 'selectDoc') { //
      // 建议开发者这里弹出选择文档的组件让用户选择,然后再调用下面的方法。传入的参数是格式如下
        handler.setFileObj({
          currentPage: 1,
          docId: "c83e6ff4-297e-4fc3-b0c0-a0294776cb12",
          type: "img",
          pageCount: "13",
          url: "http://nim.nosdn.127.net/60a9143c-146d-42f0-b55e-d8009dadc9e4_2_1.jpg",
          urlStr: "http://nim.nosdn.127.net/60a9143c-146d-42f0-b55e-d8009dadc9e4_2_{index}.jpg"
          type: "h5",
          pageCount: "22",
        })
      } else if (event === 'closeDoc') {  // 这个是在toolbar内部的自定义事件
        handler.clearFile()
      }else if(event === 'evt1'){
        // 如果是用户点击了自定义设置的工具栏
      }
}

断线重连

var isReconnecting;
whiteboard.on("connected", () => {
  // 如果断线重连之后,重新连上,
  if (isReconnecting) {
    isReconnecting = false;
    if (handler) {
      // 连接上以后,同步数据
      handler.readyToSyncData();
    }
  }
});

whiteboard.on("willReconnect", () => {
  // 标记重连
  isReconnecting = true;
});