TRTC Web SDKAPI 文档事件错误码类型教程更新日志
En

Tutorial: 互动白板

互动白板

功能描述

本文介绍如何在通话中开启互动白板:发起方(下文称 host)无需屏幕分享,直接开启一块白板推送给远端;观看方(下文称 guest)开启 host 的白板,一起书写、绘画。白板支持插入 PDF、PPT、图片、视频等内容。

角色 能力
host(发起方) 开启白板(占用辅流推送白板画面),插入 PDF / PPT / 图片 / 视频,按需切换只读
guest(观看方) 订阅 host 的白板画面(辅流),开启 host 的白板,一起书写
  • 不传 userId 表示本端是 host,传 userId 表示本端是 guest,开启该用户的白板。
  • 互动白板与 屏幕分享标注 互斥,同一时刻只能开启其中一个。
  • 体验地址:RoomKit

核心优势

常规的互动白板方案通常需要单独接入一套白板 SDK 与服务:白板内容通过独立的信令通道同步,各端分别渲染,与音视频是两条链路,观看端、录制、回放都要额外适配。TRTC 互动白板在 host 端把白板直接渲染成视频画面,推给远端的就是一路普通的视频流(辅流),因此:

对比项 常规方案 TRTC 互动白板
服务开通 需要额外开通白板服务,维护独立的房间与鉴权 复用 TRTC 房间与音视频通道,无需额外服务
接入成本 集成独立 SDK,自行实现工具栏、课件转码与状态同步 注册插件后调用几个接口即可,内置工具栏开箱即用,PDF / PPT 在本地直接加载
观看端 每个观看端都要集成白板 SDK 不支持白板能力的端(只拉流的观众、小程序、原生端)也能看到白板
音画同步 白板数据与音视频分两路传输,需要按时间戳对齐 白板在同一帧里渲染,再与音频一起推流,天然同步
录制 需要专门的白板录制服务,或分别录制音视频与白板数据 直接复用 TRTC 云端录制,录制文件就包含完整的白板画面(笔迹、PDF / PPT 翻页、图片、视频)
旁路推流 需要额外合成后才能转推 直接复用云端混流与转推 CDN,CDN 观众同样看到白板
回放 需要专用播放器还原白板,重新加载课件并与音视频对齐 标准音视频文件,任意播放器、网页 <video>、点播服务都能直接播放,不依赖原始课件文件
显示一致性 各端分别渲染,PDF / PPT 版式受字体、浏览器、分辨率影响 实时观看、录制、旁路、回放画面完全一致
服务端成本 额外的白板服务、课件转码、录制转码费用 无额外服务,沿用现有的录制、混流与点播计费

前提条件

  • TRTC Web SDK 版本 >= 5.21.0

  • 兼容性:

    操作系统 浏览器 host(发起方) guest(参与方)
    Windows / macOS Chrome 86+、Edge 86+ 及其他 Chromium 86+ 内核浏览器 ✅ ✅
    macOS Safari 15.4+ ✅ ✅
    Windows / macOS Firefox 117+ ✅ ✅
    Android Chrome 86+、微信内嵌浏览器及其他 Chromium 86+ 内核浏览器 ❌ 移动端暂不支持合流 ✅
    iOS 15.4+ Safari、微信内嵌浏览器等 ❌ 移动端暂不支持合流 ✅

    只拉流观看、不参与白板互动的用户不受上表限制:白板已渲染进辅流画面,任意支持 TRTC 拉流的环境都能看到,参见 浏览器支持情况。

  • 角色为 anchor(audience 角色不能调用白板 API)。

实现流程

部署静态资源

插入 PDF / PPT 依赖一些静态资源文件,为保证浏览器可以正常加载这些文件,你需要完成以下步骤:

  1. 将 node_modules/trtc-sdk-v5/assets 目录发布至 CDN 或者静态资源服务器中(白板资源位于其中的 whiteboard 目录)。
  2. 在创建 trtc 实例时传入您的 CDN 地址给 assetsPath 参数,例如:TRTC.create({ assetsPath: 'https://xxxx/assets' }),SDK 会按需加载相关资源文件。
  • 未配置 assetsPath 时,白板的 PDF 按钮与 PPT 入口不会出现,其他功能不受影响。
  • 如果目录下文件的 Host URL 与网页应用的 Host URL 不一致,则需要开启访问文件域名的 CORS 策略。
  • 不能把目录文件放在 HTTP 服务下,因为在 HTTPS 域名下加载 HTTP 资源会被浏览器安全策略禁止。

引入并注册插件

白板依赖 VideoMixer(生成白板画面)与 Whiteboard(白板插件),并且需要开启 enableSEI。

import TRTC from 'trtc-sdk-v5';
import { VideoMixer } from 'trtc-sdk-v5/plugins/video-effect/video-mixer';
import { Whiteboard } from 'trtc-sdk-v5/plugins/whiteboard';
import AudioPlayer from 'trtc-sdk-v5/plugins/audio-player'; // 可选,见下方说明
const trtc = TRTC.create({
  plugins: [VideoMixer, Whiteboard, AudioPlayer],
  enableSEI: true,
  assetsPath: 'https://xxxx/assets'
});
  • 未注册 VideoMixer / Whiteboard 时调用白板 API 会抛出错误。guest 只需要注册 Whiteboard。
  • AudioPlayer 可选:用于播放白板中插入视频的声音,未注册时视频无声。

host:开启白板

await trtc.startWhiteboard({
  view: 'local-board',            // 白板容器,传元素 id 或 HTMLElement
  option: {
    profile: '1080p',             // 默认值:1920×1080 / 15fps / 2000kbps
    backgroundColor: '#fcfcfc',   // 可选,不传使用主题底色
    theme: 'light'                // 可选
  }
});
  • 白板会占用辅流推送白板画面,因此开启白板期间不能开启屏幕分享,反之亦然。
  • profile 同时决定白板画布尺寸与出帧帧率。

guest:开启白板

guest 需要先订阅 host 的白板画面(辅流),再开启白板。能否开启成功取决于对端状态,请使用 try/catch 处理。

await trtc.startRemoteVideo({
  userId: 'host_user_id',
  streamType: TRTC.TYPE.STREAM_TYPE_SUB,
  view: 'remote-board'
});
try {
  await trtc.startWhiteboard({ userId: 'host_user_id' });
} catch (error) {
  // 对端未开启白板 / 未订阅该用户辅流 / 等待超时等
  console.warn(error.message);
}
  • 必须先 startRemoteVideo 订阅辅流,再调用 startWhiteboard({ userId }),顺序颠倒会抛出错误。
  • host 与 guest 谁先开启都可以,SDK 会等待对端就绪(最长 10s)。
  • guest 同一时刻只能开启一个 host 的白板,传入新的 userId 即切换 host。

按需更新参数

// host:切换底色与主题
await trtc.updateWhiteboard({ option: { backgroundColor: '#ffffff', theme: 'dark' } });
// host:本端只读,guest 照常可以书写
await trtc.updateWhiteboard({ option: { interactive: false } });
// host:更换白板容器
await trtc.updateWhiteboard({ view: 'small-board' });
// guest:只读模式(只看不写,不关闭白板)
await trtc.updateWhiteboard({ userId: 'host_user_id', option: { interactive: false } });
// guest:更换播放容器,白板随视频画面一起迁移
await trtc.updateRemoteVideo({ userId: 'host_user_id', streamType: TRTC.TYPE.STREAM_TYPE_SUB, view: 'another-view' });

关闭白板

// guest:关闭白板
await trtc.stopWhiteboard({ userId: 'host_user_id' });
// host:关闭白板(停止推流并释放白板)
await trtc.stopWhiteboard();

API 说明

trtc.startWhiteboard(options)

  • host:开启白板,并通过辅流推送白板画面。
  • guest:开启指定用户的白板。

options

参数名 类型 Attributes Description
userId string guest 必填 目标 host userId。不传表示本端为 host
view string | HTMLElement | null host 必填 白板容器,传元素 id 或 HTMLElement。不需要本地预览时传 null。guest 传入会被忽略
option.profile string | object 选填,仅 host 白板辅流的编码参数,默认 '1080p'(1920×1080 / 15fps / 2000kbps)。可传 '720p' 等字符串,或 { width, height, frameRate, bitrate }
option.backgroundColor string 选填,仅 host 白板底色(CSS 颜色),同时是远端看到的底色。默认使用主题底色
option.theme string 选填 内置工具栏配色:'light'(默认)、'dark',只影响本端

Example:

// host
await trtc.startWhiteboard({ view: 'local-board', option: { profile: '720p' } });
// guest
await trtc.startWhiteboard({ userId: 'host_user_id' });

trtc.updateWhiteboard(options)

增量更新白板参数,只修改传入的字段。

options

参数名 类型 Attributes Description
userId string guest 必填 目标 host userId。不传表示本端为 host
view string | HTMLElement | null 选填,仅 host 更换白板容器,null 表示不预览。传入与当前相同的容器时,若容器尺寸发生变化,白板会按新尺寸重新布局
option.backgroundColor string 选填,仅 host 白板底色(CSS 颜色)
option.theme string 选填 内置工具栏配色:'light'、'dark'
option.interactive boolean 选填 本端能否书写。白板开启时为 true,不传保持当前值。false 时隐藏工具栏、页码条等交互 UI

trtc.stopWhiteboard(options)

  • host:关闭白板,停止推流并释放白板。
  • guest:关闭白板。

options

参数名 类型 Attributes Description
userId string guest 必填 目标 host userId。不传表示本端为 host

事件

白板状态变化通过 TRTC.EVENT.WHITEBOARD_STATE_CHANGED 通知,回调参数为 { userId, type, payload }:

字段 说明
userId host 端为空字符串;guest 端为白板所属的 host userId
type 'available':白板可用;'unavailable':白板不可用;'error':白板运行异常
payload 事件详情,见下文

请只处理以上三种 type,其他取值请忽略。

available:白板可用

收到后可以显示 / 点亮白板相关的入口。payload.reason 取值:

reason 含义
'started' 白板会话建立:host 白板开启成功;guest 与 host 握手成功
'resume' 仅 guest:白板从暂时不可用(网络中断、同步繁忙)中恢复
trtc.on(TRTC.EVENT.WHITEBOARD_STATE_CHANGED, ({ userId, type, payload }) => {
  if (type !== 'available') return;
  // 显示 / 点亮白板入口
  showWhiteboardEntry(userId);
  if (payload.reason === 'resume') {
    hideBanner(); // 收起「连接中断」等提示
  }
});

unavailable:白板不可用

收到后应收起 / 置灰白板相关的入口。是否需要提示用户、是否需要重新调用接口,取决于 payload.reason:

reason 角色 含义 处理建议
'end' host / guest 白板会话结束:host 调用了 stopWhiteboard() 收起白板入口;guest 可提示「主讲人已关闭白板」。guest 无需关闭白板,host 再次开启时会重新收到 available
'local-stop' guest 本端已关闭该 host 的白板:调用了 stopWhiteboard({ userId }),或切换到了其他 host(userId 为原来的 host) 收起该 host 的白板入口即可,不要提示「已结束」:会话本身可能仍在进行
'silence' guest 等待 host 响应超时,通常是 host 尚未开启白板 置灰入口,可提示「等待主讲人开启白板」。插件会继续等待,host 开启后收到 available,无需重新调用
'host-lost' guest 与 host 之间的连接中断(本端或 host 断网、host 无响应)。会话未结束,已有笔迹保留 暂停书写,提示「连接中断,正在重连」。连接恢复后收到 available(reason 为 'resume');长时间未恢复会收到 'end'
'queue-full' / 'ack-timeout' guest 笔迹同步繁忙或确认超时,暂时不能书写 提示「同步中,请稍候」,无需处理,恢复后收到 available(reason 为 'resume')
  • host 端只会收到 'end'。
  • 同一个会话可能先后收到多次 unavailable(例如先 'host-lost' 再 'end'),请以最近一次为准。
trtc.on(TRTC.EVENT.WHITEBOARD_STATE_CHANGED, ({ userId, type, payload }) => {
  if (type !== 'unavailable') return;
  disableWhiteboardEntry(userId);
  switch (payload.reason) {
    case 'end':
      showToast('主讲人已关闭白板');
      break;
    case 'local-stop':
      // 本端主动关闭白板 / 切换 host,不提示
      break;
    case 'silence':
      showBanner('等待主讲人开启白板');
      break;
    case 'host-lost':
      showBanner('连接中断,正在重连');
      break;
    case 'queue-full':
    case 'ack-timeout':
      showBanner('同步中,请稍候');
      break;
  }
});

error:白板运行异常

运行过程中发生的异步异常,payload 有两种形式:

形式 含义 处理建议
Error 对象 白板运行失败 上报日志,按需提示用户
带 reason 字段的对象 白板中的 PDF / PPT / 图片 / 视频加载失败。白板仍可正常使用,只是该内容没有显示 按 reason 提示用户,例如「文件已损坏」「网络异常,请重试」

内容加载失败时 payload 的字段:

字段 说明
reason 失败原因:'invalid-url' 链接不合法;'file-too-large' 文件过大;'corrupt' 文件损坏或无法解码;'unsupported' 格式不支持,或未配置 assetsPath;'network' 资源获取失败(请求失败、跨域被拒、断网);'unknown' 其他原因
contentType 内容类型:'pdf'、'ppt'、'image'、'video'
message 原始错误信息(英文),用于排查问题

接口调用失败(如 startWhiteboard 被 reject)通过 try/catch 处理,见下文「错误处理」。

const CONTENT_ERROR_TEXT = {
  'invalid-url': '文件链接不合法',
  'file-too-large': '文件过大',
  corrupt: '文件已损坏',
  unsupported: '不支持该文件格式',
  network: '网络异常,请重试',
  unknown: '文件加载失败'
};
trtc.on(TRTC.EVENT.WHITEBOARD_STATE_CHANGED, ({ userId, type, payload }) => {
  if (type !== 'error') return;
  if (payload instanceof Error) {
    console.error('[whiteboard] error', payload.message);
    reportError(payload);
    return;
  }
  // PDF / PPT / 图片 / 视频加载失败
  showToast(CONTENT_ERROR_TEXT[payload.reason] || CONTENT_ERROR_TEXT.unknown);
  console.warn('[whiteboard] content load failed', payload.contentType, payload.message);
});

错误处理

白板相关的错误 code 为 TRTC.ERROR_CODE.INVALID_OPERATION(5100),通过 extraCode 区分:

extraCode 含义
5110 白板异常,具体原因见 error.message,例如插件未注册、屏幕分享正在运行、远端辅流未订阅、对端未开启白板、等待对端超时
5107 audience 角色不能调用
5108 未开启 enableSEI
try {
  await trtc.startWhiteboard({ userId: 'host_user_id' });
} catch (error) {
  if (error.code === TRTC.ERROR_CODE.INVALID_OPERATION && error.extraCode === 5110) {
    console.warn(error.message);
  }
}

常见问题

1. 白板工具栏中没有 PDF / PPT 入口?

请检查是否已按上文「部署静态资源」完成部署,并在 TRTC.create 中传入 assetsPath。

2. 开启白板时报错「屏幕共享正在运行」?

白板与屏幕分享互斥,请先调用 stopScreenShare() 再开启白板。

白板与屏幕分享共用同一条辅流。前一个 startScreenShare() / startWhiteboard() 还没有返回(例如选屏弹窗尚未关闭)时就调用另一个,报的是 OPERATION_ABORT(already start),而不是 INVALID_OPERATION(5110)。请等前一个调用返回(成功或失败)后再切换,或在调用期间禁用切换入口。

3. 本地预览中 PDF / 图片位置偏移,远端画面正常?

请不要用后代选择器给容器内的 canvas / video 设置尺寸(尤其带 !important),这会命中插件内部的画布。推荐只约束容器自身这一层:

#local-board > .trtc-wb-video-layer video {
  width: 100%;
  height: 100%;
}

4. 容器尺寸变化后需要调用 API 吗?

不需要。容器自身改变尺寸,或父容器尺寸变化传递到容器(百分比 / flex 布局),白板及其中的 PDF / PPT 会自动重新布局。更换容器时请使用 updateWhiteboard({ view })(host)或 updateRemoteVideo({ view })(guest)。

不建议给容器或其祖先元素设置 transform: scale(),否则窗口、PDF、文字等内容会与白板错位。需要调整显示大小时,请直接修改容器的 width / height。