TRTC Web SDKAPI ReferenceEventsError CodesTypesTutorialsChangelog
中

Tutorial: Interactive Whiteboard

Interactive Whiteboard

Feature Description

This article describes how to use the interactive whiteboard during a call. The initiator (the host) opens a whiteboard and publishes it to remote users without screen sharing. Viewers (the guests) open the host's whiteboard and write and draw together. The whiteboard supports inserting PDF, PPT, images, videos and more.

Role Capability
host (initiator) Starts the whiteboard (publishes it via the sub stream), inserts PDF / PPT / images / videos, switches to read-only as needed
guest (viewer) Subscribes to the host's whiteboard (sub stream), opens the host's whiteboard and writes together
  • Omitting userId means the local user is the host. Passing userId means the local user is a guest opening that user's whiteboard.
  • The interactive whiteboard and Screen Share Annotation are mutually exclusive. Only one of them can run at a time.
  • Demo RoomKit

Core Advantages

Conventional interactive whiteboard solutions usually require a separate whiteboard SDK and service: whiteboard content is synchronized over an independent signaling channel and rendered separately on each endpoint, separate from the audio/video path, so viewing endpoints, recording and playback all need extra adaptation. TRTC Interactive Whiteboard renders the whiteboard directly into a video picture on the host side, and what is published to remote users is an ordinary video stream (the sub stream). As a result:

Item Conventional solution TRTC Interactive Whiteboard
Service activation Requires an extra whiteboard service, with separate rooms and authentication to maintain Reuses the TRTC room and audio/video channel, no extra service needed
Integration cost Integrate a separate SDK and implement the toolbar, courseware transcoding and state synchronization yourself Register the plugins and call a few APIs; the built-in toolbar works out of the box, and PDF / PPT files are loaded locally
Viewing endpoints Every viewing endpoint must integrate the whiteboard SDK Endpoints without whiteboard support (pull-only viewers, mini programs, native apps) also see the whiteboard
Audio/video sync Whiteboard data and audio/video are transmitted separately and must be aligned by timestamp The whiteboard is rendered into the same frame and published together with the audio, naturally in sync
Recording Requires a dedicated whiteboard recording service, or recording audio/video and whiteboard data separately Reuses TRTC Cloud Recording directly; the recording contains the complete whiteboard picture (strokes, PDF / PPT page turns, images, videos)
Relayed push Requires extra compositing before relaying Reuses cloud mixing and CDN relay directly; CDN viewers also see the whiteboard
Playback Requires a dedicated player to restore the whiteboard, reload courseware and align it with audio/video Standard audio/video files that play in any player, an HTML <video> element or a VOD service, without depending on the original courseware files
Display consistency Each endpoint renders separately; PDF / PPT layout is affected by fonts, browser and resolution Live view, recording, relayed stream and playback look exactly the same
Server cost Extra fees for the whiteboard service, courseware transcoding and recording transcoding No extra service; existing recording, mixing and VOD billing applies

Prerequisites

  • TRTC Web SDK version >= 5.21.0

  • Compatibility:

    OS Browser Host (initiator) Guest (participant)
    Windows / macOS Chrome 86+, Edge 86+ and other Chromium 86+ based browsers ✅ ✅
    macOS Safari 15.4+ ✅ ✅
    Windows / macOS Firefox 117+ ✅ ✅
    Android Chrome 86+, WeChat built-in browser and other Chromium 86+ based browsers ❌ Mixing is not supported on mobile yet ✅
    iOS 15.4+ Safari, WeChat built-in browser, etc. ❌ Mixing is not supported on mobile yet ✅

    Users who only pull streams and do not take part in the whiteboard are not limited by the table above: the whiteboard is rendered into the sub stream picture, so it is visible in any environment that supports TRTC stream pulling. See Browsers Supported.

  • The role must be anchor (the audience role cannot call whiteboard APIs).

Implementation

Deploy Static Assets

Inserting PDF / PPT depends on some static asset files. To make sure the browser can load them, complete the following steps:

  1. Publish the node_modules/trtc-sdk-v5/assets directory to your CDN or static asset server (the whiteboard assets are in its whiteboard subdirectory).
  2. Pass your CDN address to the assetsPath parameter when creating the trtc instance, e.g. TRTC.create({ assetsPath: 'https://xxxx/assets' }). The SDK loads the assets on demand.
  • Without assetsPath, the PDF button and PPT entry do not appear in the whiteboard. Other features are not affected.
  • If the host URL of the assets differs from that of your web app, enable CORS on the asset domain.
  • Do not serve the assets over HTTP, because loading HTTP resources from an HTTPS page is blocked by the browser.

Import and Register Plugins

The whiteboard depends on VideoMixer (renders the whiteboard picture) and Whiteboard (the whiteboard plugin), and requires 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'; // optional, see below
const trtc = TRTC.create({
  plugins: [VideoMixer, Whiteboard, AudioPlayer],
  enableSEI: true,
  assetsPath: 'https://xxxx/assets'
});
  • Calling whiteboard APIs without VideoMixer / Whiteboard registered throws an error. Guests only need Whiteboard.
  • AudioPlayer is optional. It plays the audio of videos inserted into the whiteboard. Without it, those videos are silent.

Host: Start the Whiteboard

await trtc.startWhiteboard({
  view: 'local-board',            // whiteboard container: element id or HTMLElement
  option: {
    profile: '1080p',             // default: 1920×1080 / 15fps / 2000kbps
    backgroundColor: '#fcfcfc',   // optional; the theme background is used if omitted
    theme: 'light'                // optional
  }
});
  • The whiteboard occupies the sub stream, so screen sharing cannot be started while the whiteboard is running, and vice versa.
  • profile determines both the whiteboard canvas size and its frame rate.

Guest: Open the Whiteboard

A guest must subscribe to the host's whiteboard (sub stream) first, then open the whiteboard. Whether it succeeds depends on the remote side, so wrap it in 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) {
  // The remote user hasn't started the whiteboard / the sub stream isn't subscribed / timed out, etc.
  console.warn(error.message);
}
  • You must subscribe to the sub stream with startRemoteVideo before calling startWhiteboard({ userId }). The reverse order throws an error.
  • Either the host or the guest can start first. The SDK waits for the remote side to be ready (up to 10s).
  • A guest can open the whiteboard for only one host at a time. Passing a new userId switches the target host.

Update Parameters as Needed

// Host: change the background color and theme
await trtc.updateWhiteboard({ option: { backgroundColor: '#ffffff', theme: 'dark' } });
// Host: read-only locally; guests can still write
await trtc.updateWhiteboard({ option: { interactive: false } });
// Host: switch the whiteboard container
await trtc.updateWhiteboard({ view: 'small-board' });
// Guest: open in read-only mode (watch without writing, keep the whiteboard open)
await trtc.updateWhiteboard({ userId: 'host_user_id', option: { interactive: false } });
// Guest: switch the playback container; the whiteboard moves with the video
await trtc.updateRemoteVideo({ userId: 'host_user_id', streamType: TRTC.TYPE.STREAM_TYPE_SUB, view: 'another-view' });

Stop the Whiteboard

// Guest: close the whiteboard
await trtc.stopWhiteboard({ userId: 'host_user_id' });
// Host: stop the whiteboard (stops publishing and releases the whiteboard)
await trtc.stopWhiteboard();

API Description

trtc.startWhiteboard(options)

  • Host: starts the whiteboard and publishes it via the sub stream.
  • Guest: opens the specified user's whiteboard.

options

Name Type Attributes Description
userId string Required for guests The host userId whose whiteboard to open. Omit it for the host
view string | HTMLElement | null Required for the host Whiteboard container: element id or HTMLElement. Pass null if no local preview is needed. Ignored for guests
option.profile string | object Optional, host only Encoding parameters of the whiteboard sub stream. Default '1080p' (1920×1080 / 15fps / 2000kbps). Accepts strings such as '720p', or { width, height, frameRate, bitrate }
option.backgroundColor string Optional, host only Whiteboard background (CSS color), which is also what remote users see. Defaults to the theme background
option.theme string Optional Built-in toolbar theme: 'light' (default) or 'dark'. Affects the local side only

Example:

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

trtc.updateWhiteboard(options)

Updates whiteboard parameters incrementally. Only the fields passed in are changed.

options

Name Type Attributes Description
userId string Required for guests The host userId whose whiteboard to open. Omit it for the host
view string | HTMLElement | null Optional, host only Switches the whiteboard container; null means no preview. If the same container is passed and its size has changed, the whiteboard re-lays out to the new size
option.backgroundColor string Optional, host only Whiteboard background (CSS color)
option.theme string Optional Built-in toolbar theme: 'light' or 'dark'
option.interactive boolean Optional Whether the local user can write. It is true when the whiteboard starts and keeps its current value if omitted. When false, interactive UI such as the toolbar and page bar is hidden

trtc.stopWhiteboard(options)

  • Host: stops the whiteboard, stops publishing and releases it.
  • Guest: closes the whiteboard.

options

Name Type Attributes Description
userId string Required for guests The host userId whose whiteboard to open. Omit it for the host

Events

Whiteboard state changes are notified via TRTC.EVENT.WHITEBOARD_STATE_CHANGED. The callback receives { userId, type, payload }:

Field Description
userId An empty string on the host side; the host userId whose whiteboard is open on the guest side
type 'available': the whiteboard is available; 'unavailable': the whiteboard is unavailable; 'error': a whiteboard runtime error
payload Event details, see below

Handle only the three type values above and ignore any others.

available: Whiteboard Is Available

Show / enable your whiteboard entry when you receive it. Values of payload.reason:

reason Meaning
'started' The whiteboard session is established: the whiteboard started successfully on the host; the guest completed the handshake with the host
'resume' Guest only: the whiteboard recovered from being temporarily unavailable (network interruption, busy syncing)
trtc.on(TRTC.EVENT.WHITEBOARD_STATE_CHANGED, ({ userId, type, payload }) => {
  if (type !== 'available') return;
  // Show / enable the whiteboard entry
  showWhiteboardEntry(userId);
  if (payload.reason === 'resume') {
    hideBanner(); // dismiss notices such as "Connection lost"
  }
});

unavailable: Whiteboard Is Unavailable

Hide / disable your whiteboard entry when you receive it. Whether to notify the user or call an API again depends on payload.reason:

reason Role Meaning Recommended handling
'end' host / guest The whiteboard session ended: the host called stopWhiteboard() Hide the whiteboard entry; a guest can show "The presenter closed the whiteboard". The guest does not need to close the whiteboard, and receives available again when the host restarts
'local-stop' guest The local user closed this host's whiteboard: stopWhiteboard({ userId }) was called, or the whiteboard was opened for another host (userId is the previous host) Just hide that host's whiteboard entry. Do not say "ended": the session may still be running
'silence' guest Timed out waiting for the host, usually because the host has not started the whiteboard yet Disable the entry and optionally show "Waiting for the presenter to open the whiteboard". The plugin keeps waiting and you receive available once the host starts; no need to call again
'host-lost' guest The connection to the host is lost (local or host network down, host unresponsive). The session has not ended and existing strokes are kept Pause writing and show "Connection lost, reconnecting". You receive available (reason is 'resume') when the connection recovers; if it does not recover for a long time, you receive 'end'
'queue-full' / 'ack-timeout' guest Stroke syncing is busy or acknowledgements timed out; writing is temporarily unavailable Show "Syncing, please wait". No action needed; you receive available (reason is 'resume') after recovery
  • The host only receives 'end'.
  • A session may receive unavailable several times (e.g. 'host-lost' and then 'end'). Use the most recent one.
trtc.on(TRTC.EVENT.WHITEBOARD_STATE_CHANGED, ({ userId, type, payload }) => {
  if (type !== 'unavailable') return;
  disableWhiteboardEntry(userId);
  switch (payload.reason) {
    case 'end':
      showToast('The presenter closed the whiteboard');
      break;
    case 'local-stop':
      // The local user closed the whiteboard / switched hosts; no notice needed
      break;
    case 'silence':
      showBanner('Waiting for the presenter to open the whiteboard');
      break;
    case 'host-lost':
      showBanner('Connection lost, reconnecting');
      break;
    case 'queue-full':
    case 'ack-timeout':
      showBanner('Syncing, please wait');
      break;
  }
});

error: Whiteboard Runtime Error

An asynchronous error during runtime. payload takes one of two forms:

Form Meaning Recommended handling
An Error object The whiteboard failed at runtime Report it to your logs and notify the user if needed
An object with a reason field A PDF / PPT / image / video in the whiteboard failed to load. The whiteboard still works; only that content is not shown Notify the user according to reason, e.g. "The file is corrupted" or "Network error, please retry"

Fields of payload when content fails to load:

Field Description
reason Failure reason: 'invalid-url' invalid link; 'file-too-large' file too large; 'corrupt' file corrupted or cannot be decoded; 'unsupported' format not supported, or assetsPath not configured; 'network' failed to fetch the resource (request failed, blocked by CORS, offline); 'unknown' other reasons
contentType Content type: 'pdf', 'ppt', 'image', 'video'
message The original error message (English) for troubleshooting

Failed API calls (e.g. startWhiteboard rejected) are handled with try/catch; see "Error Handling" below.

const CONTENT_ERROR_TEXT = {
  'invalid-url': 'Invalid file link',
  'file-too-large': 'The file is too large',
  corrupt: 'The file is corrupted',
  unsupported: 'Unsupported file format',
  network: 'Network error, please retry',
  unknown: 'Failed to load the file'
};
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;
  }
  // A PDF / PPT / image / video failed to load
  showToast(CONTENT_ERROR_TEXT[payload.reason] || CONTENT_ERROR_TEXT.unknown);
  console.warn('[whiteboard] content load failed', payload.contentType, payload.message);
});

Error Handling

The error code for the whiteboard is TRTC.ERROR_CODE.INVALID_OPERATION (5100), distinguished by extraCode:

extraCode Meaning
5110 Whiteboard error. See error.message for the reason, e.g. plugins not registered, screen sharing is running, sub stream not subscribed, the remote user hasn't started the whiteboard, timed out waiting for the remote user
5107 Cannot be called in the audience role
5108 enableSEI is not enabled
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);
  }
}

FAQ

1. There is no PDF / PPT entry in the whiteboard toolbar?

Check that you have deployed the static assets as described in "Deploy Static Assets" above and passed assetsPath to TRTC.create.

2. Starting the whiteboard fails because screen sharing is running?

The whiteboard and screen sharing are mutually exclusive. Call stopScreenShare() before starting the whiteboard.

The whiteboard and screen sharing share the same sub stream. If you call one while the previous startScreenShare() / startWhiteboard() has not returned yet (for example, the screen picker is still open), the call fails with OPERATION_ABORT (already start) instead of INVALID_OPERATION (5110). Wait for the previous call to return (whether it succeeds or fails) before switching, or disable the switch entry while it is in progress.

3. PDF / images are misaligned in the local preview but correct for remote users?

Do not size canvas / video inside the container with descendant selectors (especially with !important), because they also match the plugin's internal canvases. Constrain only the container's own layer:

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

4. Do I need to call an API after the container size changes?

No. When the container itself is resized, or a parent's size change propagates to the container (percentage / flex layout), the whiteboard and the PDF / PPT in it re-lay out automatically. To switch containers, use updateWhiteboard({ view }) (host) or updateRemoteVideo({ view }) (guest).

Applying transform: scale() to the container or its ancestors is not recommended, as windows, PDF, text and other content will be misaligned with the whiteboard. To change the display size, change the container's width / height directly.