SDK 接入

录制回放的示例代码可以看github 上的仓库

接入准备

录制回放需要开发者进行如下配置

  1. 需要在云信后台开启白板功能
  2. 需要配置服务器抄送地址
  3. 在加入白板房间前传入录制参数

云信服务器会抄送一个这样的文件 URL http://nox.xxx.com/51435961-51609352701904-1574992454435-0.mp4 或者 http://nox.xxx.com/51435961-201599034016129-1574992453452-0.gz 到开发者的服务器,开发者可以将文件上传到自己的域名能够访问到的地方。如果录制回放的地址使用的是 https 协议,是不能加载使用 http 协议的录制视频文件的;如果录制文件的 url 和录制回放业务所在的域名是跨域的,需要开启跨域配置。

开发者拿到抄送地址后,可以通过解析 url 获得一些参数。以51435961-201599034016129-1574992453452-0.gz文件举例,51435961是 uid,201599034016129是白板的 cid,1574992453452是时间戳 timestamp,0是 chunk 顺序,gz是文件格式 type。如果是混合录制文件,那么文件的 url 可能是这样的51435961-51609352701904-1574992454435-0-mixed.mp4

// 白板添加抄送参数
whiteboard.joinChannel({
  channelName: "sssss",
  sessionConfig: {
    record: true // 开启服务端录制
  }
});

跨域配置

录制回放的页面一般开发者自己开发的,但是在回放录制 SDK 里,会请求录制好的视频文件和白板录制文件,如果这些文件 URL 的域名没有开启跨域资源共享,将会导致无法回放。

建议开发者需要在自己的服务器上保存抄送的白板录制文件或者音视频文件的 url,必要时,传输到自己的 NOS 上,并允许自己业务所在的域名可以访问。

使用移动端 webview 实现录制回放,也需要关注一下跨域问题。

接入指南

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

然后需要在前端准备录制文件的数据,这个时候一般会在前端页面上请求开发者的服务器,开发者服务器需要返回一些必须的参数。关于参数的解析,可以放在前端或者后端都可以,重要的是保证accounttimestamptypeurl参数的正确性。在开发者服务器获得抄送的 url 后,可以得到大多数参数,accounturl需要开发者自己管理。

示例数据如下:

var recordFiles = [
  {
    account: "cs1", // 这个录制文件对应的account
    url: "https://example.com/51435961-51609352701904-1574992454435-0.mp4", // 这个URL,SDK里不会再去解析了,所以任何可以访问到的URL都可以,比如浏览器里的URL.createObjectURL创建的链接也可以

    // 以下参数都可以通过解析抄送的url得到
    uid: "51435961", // 这个是用户当时的uid,
    cid: "51609352701904",
    timestamp: 1574992454435,
    chunk: 0,
    mixed: false,
    type: "mp4"
  },
  {
    account: "cs1",
    url: "https://example.com/51435961-201599034016129-1574992453452-0.gz",
    uid: "51435961",
    cid: "201599034016129",
    timestamp: 1574992453452,
    chunk: 0,
    mixed: false,
    type: "gz"
  }
  // 如果有更多用户,可以继续添加文件
];

回放录制文件格式如下:

属性 类型 说明
account string 录制文件对应的 account
url string 可以访问到文件的 url, 这个 URL,SDK 里不会再去解析了,所以任何可以访问到的 URL 都可以,比如浏览器里的 URL.createObjectURL 创建的链接也可以
uid string account 的 uid
cid string 音视频或者白板 channel 的 id
timestamp number 时间戳
chunk number chunk 索引,从 0 开始
mixed boolean 是否是混合录制
type string 录制文件的格式,取文件后缀,可以为mp4,flv,gz

注意:在 iOS 上,最多只能同时播放一个视频。如果 recordFiles 中没有'owner' 端的白板文件,需要设置为 identity 为 owner。

获得录制回放实例

var recall = new RecordPlayer({
  account: "superUser", // 这个是以谁的角度来看录制
  identity: 'owner' // 如果 recordFiles中没有'owner' 端的,需要设置为 owner
  // width:1460,
  // height:1200,

  isCompatible: false, // 是否是兼容模式,对于老版本的录制文件,需要做不同的解析
  // 无论有多少个录制文件,都按照上面的格式放入到这个数组中,顺序不影响,可播放多人的录制
  files: recordFiles // 多个视频或者白板,都会有visibaleChange事件告诉开发者处理对应的dom
});

拿到 RecordPlayer 实例后,SDK 内部会自动下载文件。加载完成后,会抛出 Ready 事件。

recall.on("ready", () => {
  console.log("ready");
  console.log('总时长为',recall.timeline.duration);

  // 至此,录制回放就可以开始回放了
});

RecordPlayer 实例上的方法

方法 参数类型 说明
play void 手动播放
pause vodi 暂停
seekTo number 跳转到的时间刻度,从 0 开始
destroy void 返回 promise

在 RecordPlayer 实例上,可以获取到内部timeline实例。在ready事件后,可以通过recall.timeline.duration获得总时长,调用seekTo方法的值不能大于总时长。

visibleChange 事件

如果不对 visibleChange 事件响应,那么录制回放会卡在那里,不会继续播放。当录制回放过程中,需要展示或者隐藏视频元素时,或者需要展示或者隐藏白板容器时,会触发 visibleChange 事件。开发者必须要调用回调函数里的 callback,将白板容器节点或者视频元素节点传入 callback 里。开发者可以自己将视频容器或者白板容器放置好或者异步方法里挂载好以后,才调用 callback 传入对应的 DOM 节点。

    recall.on('visibleChange', function (type, option, callback) {
        // type是'show' 或者’hide‘
        // option是
      switch (option.type) {
        case 'gz': {
          if (type === 'show') {
            // 展示白板
            let dom = document.getElementById('wb')// 一个div容器
            // 开发者可以在这个时候展示白板容器区域,完成后,将dom传入callback
            callback(dom!)
          } else {
            // 开发者可以在这个时候隐藏白板容器区域,完成后,调用callback回调
            callback()
          }
          break;
        }
        case 'aac':
        case 'flv':
        case 'mp4': {
          var jsVideo = document.getElementById('js-video')
          var videoDom = jsVideo!.querySelector(`[data-wv-vid="${option.account}"]`)
          if (type === 'show') {
            if (!videoDom) {
              videoDom = document.createElement('div') // 手动构建dom节点,如果是使用React等框架,需要将节点挂载好以后,才能将dom的ref传递给callback
              videoDom.setAttribute('data-wv-vid', option.account) // 开发者自行处理,此处是标记当前用户是哪一个dom
              videoDom.classList.add('user-video-item')// 开发者自行处理,此处是示例加class来布局video元素
              jsVideo!.appendChild(videoDom)
            }
            callback(videoDom as HTMLElement)
          } else {
            // 开发者自己控制视频区域的隐藏以及布局,完成后,调用callback回调
            videoDom && videoDom.remove()
            callback()
          }

          break
        }
      }

    })

RecordPlayer 实例上的事件

事件名称 事件参数类型 说明
ready void 当文件解析成功后,会执行 回调 ready 事件
visibleChange (type:'show'/'hide',file:object, callback) 隐藏或者显示元素的时候的回调,会暂停回放,直到 callback 回调后才会继续
tick (tick:number) 内部时钟回调,回调频率比较高,建议不要放置耗时的函数,比如在 Webview 里,可以过几秒后再回调给移动原生端
pause void 回放暂停了 ,可能由于内部或者调用实例上的pause方法触发的,如果要展示暂停状态,建议延迟一定时间展示
play void 回放开始播放了
finished void 回放结束后

回放旧版本的录制文件

为了让旧版本的白板录制文件能够回放出来,需要做些额外的工作,为了兼容旧版本,需要登录 IM。

同时,也需要传入 identity: 'owener' 的参数,才能正确播放白板内容,否则看到的画笔没有作用。

另外,如果录制文件是在 2019 年 12 月 17 日以前录制的,需要将normal端的时间戳参数统一调整为owner所使用的时间戳。


    var nim = NIM.getInstance({}) ;// 先要登录IM,这就是为什么在webview里如果

        // 这个是一个处理函数,用于兼容旧版本的白板录制文件中,需要使用的是nim的docId去下载文件
       var handleOldVersionHook = function (type, file) {
          console.log(type, file);
          if (type === 'img') {
            let docId = file.docId;
            if (!file.urlStr && docId !== '0' && docId !== '') {
              // 这里需要调用nim去获取文件
              let currentPage = file.currentPage;
              return new Promise((resolve, reject) => {
                nim.getFile({
                  docId,
                  success: function (cloudFile) {
                    console.log('getFile success', cloudFile)
                    const fileTypeMap = {
                      '10': 'jpg',
                      '11': 'png',
                      '0': 'unknown'
                    }
                    let urlStr = `${cloudFile.prefix}_${cloudFile.type}_{index}.${fileTypeMap[cloudFile.transType]}`
                    let url = urlStr.replace('{index}', currentPage)

                    // 修改file
                    file.pageCount = parseInt(cloudFile.pageCount);
                    file.url = url;
                    file.urlStr = urlStr;
                    console.log('after',file)
                    resolve(file)
                  },
                  error: function (error) {
                    console.error('getFile error', error)
                    reject(error)
                  }
                })
              })
            }
          }
          return Promise.resolve(file)
        }

      }


var recall = new RecordPlayer({
  account: "superUser", // 这个是以谁的角度来看录制
  identity: 'owner',// 需要设置为房主,否则画布没有大小。旧的录制文件里没有包含这个数据
  // width:1460, 如果需要按比例设置容器,需要设置宽高
  // height:1200,
  isCompatible: true, // 是否是兼容模式,对于老版本的录制文件,需要做不同的解析
  handleFile: handleOldVersionHook, // 如果isCompatible为 false, 则不用传入这个函数,
  // 无论有多少个录制文件,都按照上面的格式放入到这个数组中,顺序不影响,可播放多人的录制
  files: recordFiles // 多个视频或者白板,都会有visibaleChange事件告诉开发者处理对应的dom
});



RecordPlayerUI

和 DrawPluginUI 类似,回放也提供了一个 UI 去展示播放控制。 在项目里可以使用 <script src="/path/to/lib/RecordPlayerUI.2.0.0.js"></script> 或者 import * as RecordPlayerUI from 'path/to/lib/RecordPlayerUI.2.0.0.js', 引入对象RecordPlayerUI

目前的 RecordPlayerUI 使用的是preact框架开发的,只是一个 SampleCode,具体代码放在 github 上,开发者可以参考里面的逻辑,自行实现其他框架的工具栏以及其他定制需求。开发者也可以提 PR 到 github 仓库里,增强 DrawPluginUI 的功能。

通过以下代码可以加载回放的 UI

 recall.on('ready', () => {
      // 播放器已经初始化完毕了
      // RecordPlayerUI 是提供的简单的播放控制的组件,使用preact编写,开发者可以根据提供的源文件,编写其他框架的播放控制UI
      const unmountFunc = RecordPlayerUI.mount(document.getElementById('tb'), recall)
      console.log('ready', recall, unmountFunc);
    })

RecordPlayerUI 也只有一个 mount 方法,第一个参数是工具条展示的 DOM 容器,第二个是 RecordPlayer 实例。调用 mount 后,会返回一个卸载函数,调用这个函数就会卸载 UI 了。