# 录制与回放 (/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/record-and-replay)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

声网互动白板 SDK 提供录制与回放功能，支持将实时房间中的所有互动行为录制下来，并支持高质量回放。相较于传统的录屏模式，白板录制和回放具有以下优势：

- **高效录制**：采用信令记录方式，相比传统录屏模式占用带宽更少、存储空间更小
- **高质量回放**：回放时还原现场效果，画质清晰、动画流畅
- **服务端录制**：录制在服务端自动进行，无需客户端额外处理
- **灵活回放**：支持指定时间范围回放、播放控制、视角跟随等功能
- **完整记录**：录制白板操作、自定义事件、视角变化等所有互动行为

本文介绍如何开启白板录制功能以及如何实现白板内容回放。

## 技术原理 [#技术原理]

### 白板录制 [#白板录制]

声网互动白板录制不是屏幕录制，而是记录信令与数据增量构成的私有二进制数据。录制过程完全在服务端进行，具有以下优势：

- **自动录制**：只需在创建房间时开启录制功能，服务端会自动录制房间内的所有互动行为
- **智能管理**：只有在房间活跃时才会生成录制信息，房间无活动时录制会自动停止
- **数据紧凑**：录制生成的数据相比传统音视频格式更紧凑，占用存储空间更小

### 白板回放 [#白板回放]

白板回放时，SDK 会依照录制的信令与数据增量还原现场，得到的效果和实时互动时一模一样。回放支持：

- 指定时间范围回放
- 播放、暂停、跳转等播放控制
- 视角跟随和自由视角切换
- 自定义事件和全局状态变化的回放

## 前提条件 [#前提条件]

开始前，请确保：

1. 已在项目中集成声网互动白板 SDK。详见[快速开始](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/get-started/join-room-wb)。
2. 已获取有效的 SDK Token 和 Room Token。详见[获取互动白板项目的安全密钥](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/setup-and-access/enable-service#获取互动白板项目的安全密钥)。
3. 如需在业务服务器上创建房间，请参考[房间管理服务端 API](/zh-CN/api-reference/api-ref/whiteboard/restful/create-room)。

## 实现录制功能 [#实现录制功能]

### 开启录制 [#开启录制]

要开启白板录制功能，需要在创建房间时设置 `isRecord: true` 参数。以下示例展示如何通过服务端 API 创建支持录制的房间：

```javascript
// 注意：建议在业务服务器上执行创建房间操作，不要在前端执行
var url = "https://api.netless.link/v5/rooms";
var requestInit = {
    method: "POST",
    headers: {
        "content-type": "application/json",
        "token": "NETLESSSDK_YWs9xxxxxxM2MjRi", // 替换为你的 SDK Token
        "region": "cn-hz", // 替换为你的数据中心
    },
    body: JSON.stringify({
        name: "我的录制房间",
        isRecord: true, // 开启录制功能
    }),
};

fetch(url, requestInit).then(function(response) {
    return response.json();
}).then(function(roomJSON) {
    // 创建房间成功，获取房间信息
    console.log("房间 UUID:", roomJSON.uuid);
    console.log("房间已开启录制功能");
}).catch(function(err) {
    console.error("创建房间失败:", err);
});
```

:::warning[注意]
- SDK Token 是重要的安全凭证，建议在业务服务器中使用，不要写死在前端代码中。
- 开启录制功能后，该房间内的所有互动行为都会被自动录制。
:::

### 录制内容 [#录制内容]

开启录制功能后，以下内容会被自动录制：

- 白板绘制操作（画笔、图形、文字等）
- 场景切换和内容变化
- 图片、MP3、MP4、课件（PPT、DOC）的展示和操作
- 用户视角变化
- 自定义事件（通过 `dispatchMagixEvent` 发送的事件）
- 全局状态变化（`GlobalState` 的修改）

## 实现回放功能 [#实现回放功能]

### 检查录制内容是否可回放 [#检查录制内容是否可回放]

在创建回放播放器之前，建议先检查指定时间段是否有可回放的录制内容：

```javascript
var whiteWebSdk = new WhiteWebSdk({
    appIdentifier: "RMxxxAQ", // 替换为你的 App Identifier
});

var playableCheckingParams = {
    room: "a7xxx69", // 房间 UUID
    roomToken: "NETLESSROOM_YWxxxjk", // Room Token
    beginTimestamp: new Date("2023-01-01 08:00:00").getTime(), // 回放开始时间
    duration: 45 * 60 * 1000, // 回放持续时间（毫秒），这里是 45 分钟
};

whiteWebSdk.isPlayable(playableCheckingParams).then(function(isPlayable) {
    if (isPlayable) {
        console.log("该时间段有可回放的录制内容");
        // 可以创建回放播放器
    } else {
        console.log("该时间段没有录制内容");
    }
}).catch(function(err) {
    console.error("检查回放内容失败:", err);
});
```

### 创建回放播放器 [#创建回放播放器]

使用 `replayRoom` 方法创建回放播放器：

```javascript
var replayRoomParams = {
    room: "a7xxx69", // 房间 UUID
    roomToken: "NETLESSROOM_YWxxxjk", // Room Token
    beginTimestamp: new Date("2023-01-01 08:00:00").getTime(), // 回放开始时间
    duration: 45 * 60 * 1000, // 回放持续时间（毫秒）
};

whiteWebSdk.replayRoom(replayRoomParams).then(function(player) {
    // 成功创建回放播放器
    console.log("回放播放器创建成功");
    
    // 将播放器绑定到页面元素
    player.bindHtmlElement(document.getElementById("whiteboard"));
    
    // 开始播放
    player.play();
    
}).catch(function(err) {
    console.error("创建回放播放器失败:", err);
});
```

### 播放器控制 [#播放器控制]

回放播放器提供了丰富的播放控制功能：

```javascript
// 播放控制
player.play();     // 开始播放
player.pause();    // 暂停播放
player.stop();     // 停止播放

// 跳转到指定位置（单位：毫秒，相对于回放开始时间）
player.seekToProgressTime(10 * 60 * 1000) // 跳转到第 10 分钟
    .then(function(result) {
        console.log("跳转结果:", result);
    });

// 设置播放倍速
player.playbackSpeed = 1.5; // 1.5 倍速播放
player.playbackSpeed = 1.0; // 恢复正常速度

// 获取播放状态信息
console.log("当前播放进度:", player.progressTime); // 当前播放位置（毫秒）
console.log("总时长:", player.timeDuration);       // 回放总时长（毫秒）
console.log("播放状态:", player.phase);            // 播放状态
```

### 监听播放器状态变化 [#监听播放器状态变化]

可以通过回调函数监听播放器状态的变化：

```javascript
var playerCallbacks = {
    // 播放状态改变回调
    onPhaseChanged: function(phase) {
        switch(phase) {
            case "playing":
                console.log("正在播放");
                break;
            case "pause":
                console.log("已暂停");
                break;
            case "buffering":
                console.log("正在缓冲");
                break;
            case "ended":
                console.log("播放结束");
                break;
        }
    },
    
    // 播放进度改变回调
    onProgressTimeChanged: function(progressTime) {
        console.log("当前播放进度:", progressTime + "ms");
    },
    
    // 播放出错回调
    onStoppedWithError: function(error) {
        console.error("播放出错:", error);
    }
};

// 创建播放器时注册回调
whiteWebSdk.replayRoom(replayRoomParams, playerCallbacks).then(function(player) {
    // 播放器创建成功
});
```

### 视角控制 [#视角控制]

回放时可以控制观看视角：

```javascript
// 设置观看模式
player.setObserverMode("directory"); // 跟随模式，跟随录制时的视角变化
player.setObserverMode("freedom");   // 自由模式，可以自由调整视角

// 禁止用户调整视角
player.disableCameraTransform = true;  // 禁止用户通过手势调整视角
player.disableCameraTransform = false; // 允许用户调整视角
```

### 回放自定义事件 [#回放自定义事件]

如果录制时发送了自定义事件，回放时也可以监听这些事件：

```javascript
// 监听回放中的自定义事件
player.addMagixEventListener("Greetings", function(event) {
    console.log("回放中收到自定义事件:", event.payload);
});

// 监听全局状态变化
var playerCallbacks = {
    onPlayerStateChanged: function(modifyState) {
        if (modifyState.globalState) {
            console.log("全局状态发生变化:", modifyState.globalState);
        }
    }
};
```

### 使用 React 展示回放 [#使用-react-展示回放]

如果你使用 React 框架，可以使用 `white-react-sdk` 更方便地展示回放内容：

```jsx
import React from "react";
import { PlayerWhiteboard } from "white-react-sdk";

class ReplayComponent extends React.Component {
    render() {
        const style = {
            width: "100%",
            height: "100vh",
        };
        
        return (
            <PlayerWhiteboard 
                player={this.props.player} 
                style={style}
            />
        );
    }
}
```

## 完整示例 [#完整示例]

以下是一个完整的录制和回放示例：

```html
<!DOCTYPE html>
<html>
<head>
    <title>白板录制与回放示例</title>
    <script src="https://sdk.netless.link/white-web-sdk/2.16.59.js"></script>
</head>
<body>
    <div>
        <button onclick="createRecordingRoom()">创建录制房间</button>
        <button onclick="startReplay()">开始回放</button>
        <button onclick="pauseReplay()">暂停回放</button>
        <button onclick="resumeReplay()">继续回放</button>
    </div>
    <div id="whiteboard" style="width: 100%; height: 500px; border: 1px solid #ccc;"></div>

    <script>
        var whiteWebSdk = new WhiteWebSdk({
            appIdentifier: "RMxxxAQ", // 替换为你的 App Identifier
        });
        
        var player = null;

        // 创建录制房间
        function createRecordingRoom() {
            // 注意：实际项目中应该在服务端创建房间
            var url = "https://api.netless.link/v5/rooms";
            var requestInit = {
                method: "POST",
                headers: {
                    "content-type": "application/json",
                    "token": "NETLESSSDK_YWs9xxxxxxM2MjRi", // 替换为你的 SDK Token
                    "region": "cn-hz",
                },
                body: JSON.stringify({
                    name: "录制房间示例",
                    isRecord: true, // 开启录制
                }),
            };

            fetch(url, requestInit).then(function(response) {
                return response.json();
            }).then(function(roomJSON) {
                console.log("录制房间创建成功:", roomJSON);
                alert("录制房间创建成功，UUID: " + roomJSON.uuid);
            }).catch(function(err) {
                console.error("创建房间失败:", err);
            });
        }

        // 开始回放
        function startReplay() {
            var replayRoomParams = {
                room: "a7xxx69", // 替换为实际的房间 UUID
                roomToken: "NETLESSROOM_YWxxxjk", // 替换为实际的 Room Token
                beginTimestamp: new Date("2023-01-01 08:00:00").getTime(),
                duration: 45 * 60 * 1000, // 45 分钟
            };

            var playerCallbacks = {
                onPhaseChanged: function(phase) {
                    console.log("播放状态:", phase);
                },
                onProgressTimeChanged: function(progressTime) {
                    console.log("播放进度:", Math.floor(progressTime / 1000) + "秒");
                }
            };

            whiteWebSdk.replayRoom(replayRoomParams, playerCallbacks).then(function(replayPlayer) {
                player = replayPlayer;
                player.bindHtmlElement(document.getElementById("whiteboard"));
                player.play();
                console.log("开始回放");
            }).catch(function(err) {
                console.error("回放失败:", err);
            });
        }

        // 暂停回放
        function pauseReplay() {
            if (player) {
                player.pause();
                console.log("暂停回放");
            }
        }

        // 继续回放
        function resumeReplay() {
            if (player) {
                player.play();
                console.log("继续回放");
            }
        }
    </script>
</body>
</html>
```

## 参考信息 [#参考信息]

### 开发注意事项 [#开发注意事项]

在使用录制与回放功能时，需要注意以下几点：

- **录制延时**：录制内容不是实时生成的，新创建的房间可能需要等待一段时间才能获得录制内容
- **时间范围**：建议根据实际使用场景设置合适的回放时间范围，避免回放过长的无效内容
- **网络缓冲**：回放时可能需要网络缓冲，特别是跳转到较远时间点时
- **存储成本**：长期开启录制功能会产生存储成本，建议根据业务需要合理使用
- **播放器释放**：使用完毕后记得调用 `player.stop()` 释放播放器资源

### 错误处理 [#错误处理]

常见错误及处理方法：

```javascript
whiteWebSdk.replayRoom(replayRoomParams).then(function(player) {
    // 成功
}).catch(function(err) {
    if (err.message.includes("couldn't find any matching slices")) {
        console.log("录制内容还未生成，请稍后再试");
    } else {
        console.error("回放失败:", err.message);
    }
});
```

### API 参考 [#api-参考]

- [`WhiteWebSdk.replayRoom`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/classes/whitewebsdk#replayroom) - 创建回放播放器
- [`WhiteWebSdk.isPlayable`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/classes/whitewebsdk#isplayable) - 检查是否可回放
- [`Player.play`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/interfaces/player#play) - 开始播放
- [`Player.pause`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/interfaces/player#pause) - 暂停播放
- [`Player.seekToProgressTime`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/interfaces/player#seektoprogresstime) - 跳转播放位置
- [`Player.setObserverMode`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/interfaces/player#setobservermode) - 设置观看模式
- [`PlayerCallbacks`](/zh-CN/api-reference/whiteboard/whiteboard-sdk/web/globals#playercallbacks) - 播放器回调

### 相关文档 [#相关文档]

- [房间管理服务端 API](/zh-CN/api-reference/api-ref/whiteboard/restful/create-room) - 创建房间 API
- [互动白板 Token 概述](/zh-CN/realtime-media/whiteboard/concepts#token) - Token 相关说明
- [创建自定义事件](/zh-CN/realtime-media/whiteboard/whiteboard-sdk/build/extend-whiteboard/custom-event) - 自定义事件使用指南
