房间的业务状态用于改变白板的行为,或承载自定义业务逻辑。这些状态都包含在,room.state
和 player.state
之中。
在实时房间中时,我们可以通过对 room
调用特定方法来改变 room.state
的值,或直接对 room.state
中的某些成员赋值。如果实时房间刚好开启了自动录制,则这些行为会被录制下来,在回放的时候,player.state
的某些成员也会重演 room.state
在之前的变化。
可以阅读《房间业务状态管理》,了解业务状态的概念与含义,本章节仅作逐字段解释。
以读取 room
对象的 globalState
字段为例,通过如下代码可以读取它。
var state = room.state.globalState;
对于 player
对象而言,必须在加载完首帧之后,才能读取 player.state
。否则可能得到如下报错。
can't read state before load first frame
当出现这个错误,你可以检查一下 player.phase
的值,不出所料,它的值应该是 "waitingFirstFrame"
。在该状态下,播放器尚未加载完首帧,自然无法获取 player.state
对象。
此时,需要调用 player.play()
或 player.seekToProgressTime(timestamp)
以让播放器开始加载首帧。之后,需要等待 onLoadFirstFrame
事件发生后才能读取 player.state
。
如下代码可以保证能正常读取到 player.state
。
var player = null;
whiteWebSdk.replay({slice: "$slice"}, {
onLoadFirstFrame: function() {
// 首帧加载成功,读取 player.state
console.log(player.state);
},
}).then(function(_player) {
player = _player;
player.play(); // 开始从第 0ms 位置播放,开始加载首帧
});
构造一个回调方法来监听,globalState
字段的变化。
function onStateChanged(modifyState) {
if (modifyState.globalState) {
// modifyState.globalState 不为空,说明 globalState 发生了改变
console.log(modifyState.modifyState);
} else {
// 其他 state 的字段发生了改变,这里我们不关心,所以不做处理
}
}
之后,我们要把这个方法注册成回调方法。对于 room
对象而言,我们需要监听 onRoomStateChanged
的回调;对于 player
对象而言,我们需要监听 onPlayerStateChanged
的回调。这两个方法的签名如下。
readonly onRoomStateChanged: (modifyState: Partial<RoomState>) => void;
readonly onPlayerStateChanged: (modifyState: Partial<PlayerState>) => void;
参考《回调方法|构造 Room 与 Player 对象》中的描述,我们知道若干种方法来监听它们,在此不做赘述。
如果在构造 WhiteWebSdk
对象时,将 useMobXState
字段置为 true
,如下代码。
var whiteWebSdk = new WhiteWebSdk({
appIdentifier: "$APP_IDENTIFIER",
useMobXState: true,
});
则 room.state
与 player.state
会变成一个 MobX observable object。这意味着,当它的成员发生变化时,你可以用 MobX 的方式监听它的变化并自动响应。
例如,你可以通过如下方式监听 room.state
的 globalState
字段的变化。
var mobx = require("mobx");
mobx.reaction(function() {return room.state.globalState}, function(globalState) {
// 监听到 globalState 发生了变化
});
MobX 是 JavaScript 社区的透明的函数响应式编程库,你可以阅读《@observable》 来了解 observable object 是什么意思。
room.state
和 player.state
的类型定义如下。
// room.state 的业务状态
type RoomState = DisplayerState & {
// 当前用户的成员状态
readonly memberState: MemberState;
// 当前视角模式
readonly broadcastState: Readonly<BroadcastState>;
// 当前视角放大比例(已弃用)
readonly zoomScale: number;
};
// player.state 的业务状态
type PlayerState = DisplayerState & {
// 当前观察者模式
readonly observerMode: ObserverMode;
};
// room.state 与 player.state 公共的业务状态
type DisplayerState = {
// 房间的公共状态
readonly globalState: GlobalState;
// 房间当前成员状态列表
readonly roomMembers: ReadonlyArray<RoomMember>;
// 房间当前场景状态
readonly sceneState: SceneState;
// 当前视角状态
readonly cameraState: CameraState;
};
room.state.memberState
是当前用户业务状态,在该用户加入房间时出现,用户离开房间后释放。其类型定义如下。
type MemberState = {
// 当前教具,修改则视为切换教具
currentApplianceName: ApplianceNames;
// 当前教具的填充颜色
strokeColor: Color;
// 当前教具的笔迹粗细
strokeWidth: number;
// 下一个添加的文字以多大的字号初始化
textSize: number;
};
enum ApplianceNames {
// 选择工具
selector = "selector",
// 激光笔
laserPointer = "laserPointer",
// 铅笔工具
pencil = "pencil",
// 矩形工具
rectangle = "rectangle",
// 圆形工具
ellipse = "ellipse",
// 橡皮
eraser = "eraser",
// 文字工具
text = "text",
// 直线工具
straight = "straight",
// 箭头
arrow = "arrow",
// 抓手工具
hand = "hand",
}
// 由 3 到 4 个 0 ~ 255 分量组成的数组,分别表示 R、G、B、A 的取值
type Color = number[];
当前用户的 ``memberState`` 可以被其他人从 ``displayer.state.roomMembers`` 数组中读取到。
```javascript
for (var member of room.state.roomMembers) {
// 读取房间内某个用户的 memberState 字段
console.log(member.memberState);
}
可以通过如下代码修改 memberState
中的某个字段。
room.setMembemrState({
currentApplianceName: "pencil",
});
也可以通过将某个字段置为 undefined
,来删掉该字段。
room.setMembemrState({
toDeleteMe: undefined,
});
你可以在
memberState
中插入自定义字段来存储自己的业务信息。这些自定义字段可以被房间内其他人监听 / 读取room.state.roomMembers
得到。同时,自定义字段的值,以及今后的变化会被录制下来,在回放时复现。
room.state.broadcastState
是关于当前视角的业务状态,其类型 BroadcastState
定义如下。你可以阅读《主播与跟随者|视角与坐标》来了解视角追踪模式的概念。
type BroadcastState = {
// 当前视角追踪模式
readonly mode: ViewMode;
// 房间内主播的 ID,如果不存在主播,则为 undefined
readonly broadcasterId?: number;
}
enum ViewMode {
// 自由视角,不会跟随主播
Freedom = "freedom",
// 跟随者视角,会跟随主播的视角
Follower = "follower",
// 主播视角,房间内的追随者会跟我
Broadcaster = "broadcaster",
}
我们可以通过调用 room.setViewMode
来调整视角跟随模式。例如,如下代码能将我们切换到主播模式。
room.setViewMode(ViewMode.Broadcaster);
player.state.observerMode
表明了回放录像时,白板应该以何种方式处理视角问题。其类型 ObserverMode
类型定义如下。
enum ObserverMode {
// 导演模式
Directory = "directory",
// 自由模式
Freedom = "freedom",
}
在「自由模式」下,用户可以自由操作视角。在「导演模式」下,白板会根据如下规则,让视角跟随录像中的某个人。
0.0
, 0.0
) 的位置,并且放缩比例为 1.0
特别的,如果在「导演模式」下,用户通过设备调整视角,则会自动切换到「自由模式」。
若希望切回来,可以通过如下代码切回「导演模式」。
player.setObserverMode(ObserverMode.Directory);
displayer.state.globalState
是房间内的自定义业务状态,房间内任何用户都可以读它、修改它、监听它的变化。它是房间内全局唯一的,自房间创建起就创建,直到房间被删除才释放。
如果 displayer
是 player
,你只能读取它、监听它的变化,但不能修改它。
如果 displayer
是 room
,你可以通过如下代码修改某个字段。
room.setGlobalState({
foobar: "hello world",
});
也可以通过将某个字段置为 undefined
,来删掉该字段。
room.setGlobalState({
foobar: undefined,
});
displayer.state.roomMembers
用来描述当前房间内所有拥有「可写」权限的用户的状态。它是一个数组,数组的每个元素是 RoomMember
类型,其定义如下。
type RoomMember = {
// 用户的 ID
readonly memberId: number;
// 用户的 memberState
readonly memberState: MemberState;
// 用户的 session ID
readonly session: string;
// 用户加入房间时定义的 payload
readonly payload: any;
}
特别的,如果用户以「可写」权限加入房间,这个列表中会包含该用户自己。如果你的业务逻辑需要排除用户自己,可以通过读取 displayer.observerId
来得知用户的 ID,然后通过和数组元素的成员变量 memberId
比较来排除。例如,如下代码就可以排除自己。
for (var member of displayer.state.roomMembers) {
if (displayer.observerId !== member.memberId) {
// 此时 member 不可能是用户自己了
}
}
displayer.state.sceneState
用来获取当前场景的状态。你可以阅读《场景管理》来了解场景的概念。
type SceneState = {
// 当前场景组下的同级场景列表
readonly scenes: ReadonlyArray<Scene>;
// 当前场景路径
readonly scenePath: string;
// 当前场景的名字
readonly sceneName: string;
// 当前场景所属的直接场景组的地址
readonly contextPath: string;
// 当前场景在当前场景组下所处的索引号
readonly index: number;
}
type Scene = {
// 场景名
readonly name: string;
// 当前场景中有多少组建
readonly componentsCount: number;
// 当前场景中包含的 PPT 描述
readonly ppt?: PptDescription;
}
displayer.state.cameraState
用来表示当前这一瞬间视角的状态。你可以通过阅读《视角|视角与坐标》来了解视角的概念。
type CameraState = {
// 视角中心对准的点,在世界坐标中的 x 轴坐标
readonly centerX: number;
// 视角中心对准的点,在世界坐标中的 y 轴坐标
readonly centerY: number;
// 视角放大比例
readonly scale: number;
// 当前白板的宽
readonly width: number;
// 当前白板的高
readonly height: number;
}