加入白板房间
声网互动白板房间提供丰富的编辑工具,支持展示静态图片、动态 PPT、H5 课件等多种格式的文件,不同终端用户能在白板上实时展示想法,轻松实现多人互动协作。
本文介绍如何创建 Web 项目、加入白板房间,并接入多窗口、动态 PPT 和新教具。示例采用 npm 与 Vite,使用 TypeScript 编写浏览器端模块。
由于浏览器的安全策略对除 127.0.0.1 以外的 HTTP 地址作了限制,声网互动白板 SDK 仅支持 HTTPS 协议或者 http://localhost ( http://127.0.0.1 ) ,请勿使用 HTTP 协议部署你的项目。
技术原理
下图展示了加入声网互动白板房间的基本流程。
当你的 App 客户端请求加入互动白板房间时:
- 你的 App 服务端使用 SDK Token 向声网互动白板服务端发起创建房间的请求。
- 成功创建房间后,声网互动白板服务端向你的 App 服务端返回新建房间的房间 UUID。
- 你的 App 服务端使用房间 UUID 生成 room Token,并将房间 UUID 和 room Token 发送给 App 客户端。
- App 客户端使用从声网控制台获取的 AppIdentifier 初始化互动白板 SDK 实例。
- App 客户端调用 SDK 的方法并传入房间 UUID 和 room Token 以加入互动白板房间。
前提条件
- 满足最低版本要求的浏览器。
- 有效的声网开发者账号。
- 已在声网控制台开启互动白板服务并获取白板项目的 AppIdentifier 和 SDK Token。
- Node.js 及 npm。
创建 Web 项目
在项目目录执行以下命令,初始化 npm 项目并安装开发服务器:
npm init -y
npm install -D vite
mkdir -p scripts
创建以下目录结构;public 中的资源通过站点根路径访问:
.
├── index.html
├── scripts
│ └── index.ts
└── public
将以下内容写入 index.html。容器必须有明确高度,浏览器模块在容器创建后加载:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>互动白板</title>
</head>
<body style="margin: 0;">
<div id="whiteboard" style="width: 100%; height: 100vh;"></div>
<script type="module" src="/scripts/index.ts"></script>
</body>
</html>
获取 SDK
安装本页示例使用的 SDK、窗口管理器、动态 PPT App 和教具插件:
npm install white-web-sdk@2.16.59 @netless/window-manager@1.0.25 @netless/app-slide@0.2.108 @netless/appliance-plugin@1.1.45
将步骤 3~7 的 TypeScript 代码依次放入同一个 scripts/index.ts。步骤 4~6 定义接入函数和注册 App,步骤 7 调用这些函数完成一次入房和插件初始化。
SDK 版本见发版说明。
实现步骤
先由业务服务端创建房间并生成 Room Token,再在浏览器端完成 SDK 初始化和接入。SDK Token 应保存在服务端,客户端只接收 Room Token。
1. 创建房间
在 App 客户端加入互动白板房间前,你需要在 App 服务端调用互动白板服务端 RESTful API 创建一个房间。详见创建房间(POST)。
请求示例
你可以使用以下 Node.js 脚本发送请求:
使用 Node.js 发送 HTTP 请求前安装 request 模块。你可以运行 npm install request 安装。
var request = require("request");
var options = {
method: "POST",
url: "https://api.netless.link/v5/rooms",
headers: {
"token": "你的 SDK Token",
"Content-Type": "application/json",
"region": "cn-hz",
},
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
如果方法调用成功,声网互动白板服务端将返回新建房间的信息,其中的 uuid 是房间的唯一标识,App 客户端加入房间时需要传入该参数。
响应示例
{
"uuid": "4a50xxxxxx796b", // 房间的 UUID
"teamUUID": "RMmLxxxxxx15aw",
"appUUID": "i54xxxxxx1AQ",
"isRecord": true,
"isBan": false,
"createdAt": "2021-01-18T06:56:29.432Z",
"limit": 0
}
2. 生成 Room Token
创建房间并获取新建房间的 uuid 后,你需要在 App 服务端生成 Room Token 并下发给 App 客户端。当 App 客户端加入房间时,声网互动白板服务端会使用该 Token 对其鉴权。
你可以通过以下方式在 App 服务端生成 Room Token:
- 使用代码生成 Room Token,详见在 App 服务端生成 Token。(推荐)
- 调用互动白板服务端 RESTful API 生成 Room Token,详见生成 Room Token(POST)。
下面以调用 RESTful API 的方式为例介绍如何生成 Room Token。
请求示例
你可以使用以下 Node.js 脚本发送请求:
使用 Node.js 发送 HTTP 请求前安装 request 模块。你可以运行 npm install request 安装。
var request = require("request");
var options = {
method: "POST",
// 将 <房间的 UUID> 替换成你的房间 UUID
url: "https://api.netless.link/v5/tokens/rooms/<房间的 UUID>",
headers: {
"token": "你的 SDK Token",
"Content-Type": "application/json",
"region": "cn-hz",
},
body: JSON.stringify({ lifespan: 3600000, role: "admin" }),
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
如果方法调用成功,声网互动白板服务端将返回生成的 Room Token。
响应示例
"NETLESSROOM_YWs9XXXXXXXXXXXZWNhNjk" // Room Token
3. 创建 WhiteWebSdk 实例
填写项目的 appIdentifier 和房间所属数据中心。多窗口使用 useMobXState=true:
import { WhiteWebSdk, ApplianceNames, type InvisiblePluginClass, type Room } from 'white-web-sdk';
const whiteWebSdk = new WhiteWebSdk({
appIdentifier: 'APP_IDENTIFIER',
region: 'cn-hz',
useMobXState: true
});
4. 加入房间
准备入房函数,传入步骤 1、2 获取的房间 UUID、Room Token,以及本用户唯一的 uid。region 必须与创建房间时一致。
useMultiViews=true 启用多 View;窗口管理器和教具插件需在入房前通过 invisiblePlugins 注册,实际列表在步骤 7 传入。disableMagixEventDispatchLimit=true 用于多窗口的事件分发。
async function joinWhiteboard(
invisiblePlugins: InvisiblePluginClass<string, any, any>[]
): Promise<Room> {
return await whiteWebSdk.joinRoom({
uuid: 'ROOM_UUID',
roomToken: 'ROOM_TOKEN',
uid: 'CLIENT_UID',
region: 'cn-hz',
isWritable: true,
useMultiViews: true,
disableMagixEventDispatchLimit: true,
invisiblePlugins
});
}
5. 接入多窗口
WindowManager 管理主白板与课件窗口。入房成功后调用 WindowManager.mount,将界面挂载到页面容器,并引入窗口样式。本示例由 Window Manager 绑定视图,无需再调用 room.bindHtmlElement。
import { WindowManager } from '@netless/window-manager';
import '@netless/window-manager/dist/style.css';
async function mountWindows(room: Room, supportAppliancePlugin = false) {
const container = document.getElementById('whiteboard');
if (!container) throw new Error('找不到白板容器');
return await WindowManager.mount({
room,
container,
cursor: true,
supportAppliancePlugin
});
}
同时接入步骤 7 的新教具时,传入 supportAppliancePlugin=true。窗口配置和页面控制见多窗口与课件控制。
6. 接入动态 PPT
安装的 @netless/app-slide 用于展示转换后的动态 PPT。在入房前调用 WindowManager.register 注册 Slide,这样新打开和房间中已存在的课件窗口都能加载 App 实现。
import AppSlide from '@netless/app-slide';
WindowManager.register({
kind: 'Slide',
src: AppSlide
});
async function openDynamicPpt(manager: WindowManager) {
const appId = await manager.addApp({
kind: 'Slide',
options: {
title: '教学课件.pptx',
scenePath: '/slides/lesson-1'
},
attributes: {
taskId: 'CONVERSION_TASK_UUID',
url: 'https://convertcdn.netless.link/dynamicConvert'
}
});
console.info('动态 PPT 窗口 ID:', appId);
return appId;
}
业务服务端先完成动态 PPT 转换,taskId 使用转换任务 UUID,url 使用转换结果的资源地址前缀(不是 PPT 源文件地址,末尾不带 /)。scenePath 必须是以 / 开头的场景目录,同一房间内每个 App 使用不同目录。创建 App 需要可写权限;等待窗口挂载后再调用 addApp。注册实现是每个客户端的本地准备,addApp 创建的是房间内同步的实例,页面刷新或另一用户入房时不要重复插入同一课件。
addApp 返回 ID 不表示课件首帧和全部资源已加载。转换、展示与课件操作见展示文件。
7. 开启新教具
多窗口新教具通过 ApplianceMultiPlugin 接入。先准备同一插件版本的两份 Worker:
mkdir -p public/appliance-1.1.45
cp node_modules/@netless/appliance-plugin/dist/fullWorker.js public/appliance-1.1.45/
cp node_modules/@netless/appliance-plugin/dist/subWorker.js public/appliance-1.1.45/
注册不可见插件并完成入房,挂载支持新教具的窗口管理器,然后调用 ApplianceMultiPlugin.getInstance 初始化。以下代码放在步骤 3~6 的代码之后:
import { ApplianceMultiPlugin } from '@netless/appliance-plugin';
import '@netless/appliance-plugin/dist/style.css';
async function startWhiteboard() {
const room = await joinWhiteboard([
WindowManager as unknown as InvisiblePluginClass<string, any, any>,
ApplianceMultiPlugin
]);
const manager = await mountWindows(room, true);
const plugin = await ApplianceMultiPlugin.getInstance(manager, {
options: {
cdn: {
fullWorkerUrl: '/appliance-1.1.45/fullWorker.js',
subWorkerUrl: '/appliance-1.1.45/subWorker.js'
},
extras: { useBackgroundThread: true }
}
});
// 入房并完成插件初始化后设置初始教具。
room.setMemberState({ currentApplianceName: ApplianceNames.pencil });
// 在业务“打开课件”按钮中调用 openDynamicPpt(manager)。
// 返回实例供业务保存,避免刷新或重新入房时重复 addApp。
return { room, manager, plugin };
}
startWhiteboard().then(({ room, manager, plugin }) => {
// 保存实例并绑定业务按钮,例如按钮点击时调用 openDynamicPpt(manager)。
console.info('白板与新教具已初始化', room, manager, plugin);
}).catch(console.error);
Web 通过上述注册、supportAppliancePlugin 和实例初始化启用新教具。插件提供 Pencil、Selector、Eraser、Shape、Text 等绘制与编辑能力,业务应用提供教具切换按钮。同一房间各端应一致启用新教具。
Worker 与主线程插件必须来自同一版本,站点需允许 Worker 加载。离开页面时先解除业务事件监听,等待 plugin.destroy(),再等待 manager.destroy() 和 room.disconnect()。教具配置、扩展功能、背景图及事件监听见教具插件介绍与启用。
运行项目
替换 AppIdentifier、房间 UUID、Room Token 和 uid 后,在项目目录启动开发服务器:
npx vite --host 127.0.0.1
访问终端显示的本地地址,成功入房后可在主白板书写。需要展示动态 PPT 时,填入已完成转换的任务信息,再通过业务按钮调用 openDynamicPpt(manager)。部署时使用 HTTPS。
配置远端参数
需要在入房前读取远端参数时,参见动态配置。地址在创建 SDK 前设置,本地显式参数优先;读取失败回退本地参数。