2025/11/17 18:27:18
录制与回放
声网 Fastboard SDK 提供录制与回放功能,支持将实时房间中的所有互动行为录制下来,并支持高质量回放。相较于传统的录屏模式,白板录制和回放具有以下优势:
- 高效录制:采用信令记录方式,相比传统录屏模式占用带宽更少、存储空间更小
- 高质量回放:回放时还原现场效果,画质清晰、动画流畅
- 服务端录制:录制在服务端自动进行,无需客户端额外处理
- 灵活回放:支持指定时间范围回放、播放控制、视角跟随等功能
- 完整记录:录制白板操作、自定义事件、视角变化、多窗口应用等所有互动行为
- 音视频同步:支持白板回放与音视频播放同步
本文介绍如何开启白板录制功能以及如何使用 Fastboard SDK 实现白板内容回放。
技术原理
白板录制
声网互动白板录制不是屏幕录制,而是记录信令与数据增量构成的私有二进制数据。录制过程完全在服务端进行,具有以下优势:
- 自动录制:只需在创建房间时开启录制功能,服务端会自动录制房间内的所有互动行为
- 智能管理:只有在房间活跃时才会生成录制信息,房间无活动时录制会自动停止
- 数据紧凑:录制生成的数据相比传统音视频格式更紧凑,占用存储空间更小
- 多窗口支持:完整记录 Fastboard 多窗口模式下的所有应用和操作
白板回放
Fastboard Android SDK 对回放功能提供了轻量级封装,通过 FastReplay 类简化了配置和使用流程。但核心回放逻辑仍由底层 Whiteboard SDK 的 Player 类提供。
回放时,SDK 会依照录制的信令与数据增量还原现场,得到的效果和实时互动时一模一样。回放支持:
- 指定时间范围回放
- 播放、暂停、跳转等播放控制
- 播放速度调节
- 视角跟随和自由视角切换
- 自定义事件和全局状态变化的回放
- 多窗口应用的完整回放
- 音视频与白板同步回放
前提条件
开始前,请确保:
- 已在项目中集成声网 Fastboard SDK。详见快速开始。
- 已获取有效的 App Identifier 和 Room Token。详见获取互动白板项目的安全密钥。
- 如需在业务服务器上创建房间,请参考房间管理服务端 API。
实现录制功能
开启录制
要开启白板录制功能,需要在创建房间时设置 isRecord: true 参数。通过服务端 API POST 创建房间创建支持录制的房间。
以下示例展示如何通过服务端 API 创建支持录制的房间:
Java
// 注意:建议在业务服务器上执行创建房间操作,不要在客户端执行
String url = "https://api.netless.link/v5/rooms";
OkHttpClient client = new OkHttpClient();
JSONObject requestBody = new JSONObject();
try {
requestBody.put("name", "我的 Fastboard 录制房间");
requestBody.put("isRecord", true); // 开启录制功能
} catch (JSONException e) {
e.printStackTrace();
}
RequestBody body = RequestBody.create(
MediaType.parse("application/json"),
requestBody.toString()
);
Request request = new Request.Builder()
.url(url)
.addHeader("token", "NETLESSSDK_YWs9xxxxxxM2MjRi") // 替换为你的 SDK Token
.addHeader("region", "cn-hz") // 替换为你的数据中心
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseBody = response.body().string();
try {
JSONObject json = new JSONObject(responseBody);
String uuid = json.getString("uuid");
Log.i(TAG, "房间 UUID: " + uuid);
Log.i(TAG, "房间已开启录制功能");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "创建房间失败: " + e.getMessage());
}
});
注意
- SDK Token 是重要的安全凭证,建议在业务服务器中使用,不要写死在客户端代码中。
- 开启录制功能后,该房间内的所有互动行为都会被自动录制。
录制内容
开启录制功能后,以下内容会被自动录制:
- 白板绘制操作(画笔、图形、文字等)
- 场景切换和内容变化
- 图片、音视频、课件(PPT、DOC)的展示和操作
- 多窗口应用的所有操作和状态变化
- 用户视角变化
- 自定义事件(通过
dispatchMagixEvent发送的事件) - 全局状态变化(
GlobalState的修改)
实现回放功能
基本回放
使用底层 Whiteboard SDK 实现回放功能:
Java
import com.herewhite.sdk.WhiteSdk;
import com.herewhite.sdk.WhiteSdkConfiguration;
import com.herewhite.sdk.Player;
import com.herewhite.sdk.PlayerConfiguration;
import com.herewhite.sdk.PlayerListener;
import com.herewhite.sdk.domain.PlayerPhase;
import com.herewhite.sdk.domain.PlayerState;
import com.herewhite.sdk.domain.SDKError;
public class DirectReplayActivity extends AppCompatActivity implements PlayerListener {
private WhiteboardView whiteboardView;
private WhiteSdk whiteSdk;
private Player player;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_direct_replay);
// 1. 创建白板视图
whiteboardView = findViewById(R.id.whiteboard_view);
// 2. 配置并初始化 WhiteSdk
WhiteSdkConfiguration config = new WhiteSdkConfiguration("RMxxxAQ", true);
config.setRegion(Region.cn);
config.setUseMultiViews(true); // 启用多窗口回放
whiteSdk = new WhiteSdk(whiteboardView, this, config);
// 3. 配置回放参数
PlayerConfiguration playerConfig = new PlayerConfiguration("a7xxx69", "NETLESSROOM_YWxxxjk");
playerConfig.setBeginTimestamp(1609459200000L);
playerConfig.setDuration(45 * 60 * 1000L);
// 配置多窗口参数
WindowParams windowParams = new WindowParams();
windowParams.setContainerSizeRatio(9f / 16f);
playerConfig.setWindowParams(windowParams);
// 4. 创建回放播放器
whiteSdk.createPlayer(playerConfig, this, new Promise<Player>() {
@Override
public void then(Player p) {
player = p;
Log.i(TAG, "回放播放器创建成功");
// 开始播放
player.seekToScheduleTime(0);
player.play();
}
@Override
public void catchEx(SDKError error) {
Log.e(TAG, "创建回放播放器失败: " + error.getMessage());
}
});
}
// PlayerListener 回调方法
@Override
public void onPhaseChanged(PlayerPhase phase) {
Log.i(TAG, "播放状态: " + phase);
switch (phase) {
case waitingFirstFrame:
Log.i(TAG, "等待首帧");
break;
case playing:
Log.i(TAG, "正在播放");
break;
case pause:
Log.i(TAG, "已暂停");
break;
case stopped:
Log.i(TAG, "已停止");
break;
case ended:
Log.i(TAG, "播放结束");
break;
case buffering:
Log.i(TAG, "正在缓冲");
break;
}
}
@Override
public void onLoadFirstFrame() {
Log.i(TAG, "首帧加载完成");
}
@Override
public void onPlayerStateChanged(PlayerState modifyState) {
// 播放器状态变化
}
@Override
public void onStoppedWithError(SDKError error) {
Log.e(TAG, "播放出错: " + error.getMessage());
}
@Override
public void onScheduleTimeChanged(long time) {
// 播放进度变化(约每 500ms 回调一次)
Log.d(TAG, "当前播放进度: " + time + "ms");
}
@Override
public void onSliceChanged(String slice) {
// 切片变化(一般不需要实现)
}
@Override
public void onCatchErrorWhenAppendFrame(SDKError error) {
Log.e(TAG, "追加帧错误: " + error.getMessage());
}
@Override
public void onCatchErrorWhenRender(SDKError error) {
Log.e(TAG, "渲染错误: " + error.getMessage());
}
@Override
protected void onDestroy() {
super.onDestroy();
if (player != null) {
player.stop();
}
}
}
音视频同步回放
如果需要同步播放音视频和白板回放,可以使用 PlayerSyncManager:
Java
import com.herewhite.sdk.combinePlayer.PlayerSyncManager;
import com.herewhite.sdk.combinePlayer.NativePlayer;
public class SyncReplayActivity extends AppCompatActivity implements PlayerListener {
private WhiteSdk whiteSdk;
private Player whitePlayer;
private PlayerSyncManager syncManager;
private NativePlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sync_replay);
// 1. 创建原生媒体播放器(以 ExoPlayer 为例)
mediaPlayer = new WhiteExoPlayer(this);
((WhiteExoPlayer) mediaPlayer).setVideoPath("https://example.com/video.m3u8");
// 2. 创建同步管理器
syncManager = new PlayerSyncManager(mediaPlayer, new PlayerSyncManager.Callbacks() {
@Override
public void startBuffering() {
Log.d(TAG, "开始缓冲");
}
@Override
public void endBuffering() {
Log.d(TAG, "缓冲结束");
}
});
// 3. 初始化 WhiteSdk 并创建白板回放
WhiteSdkConfiguration config = new WhiteSdkConfiguration("RMxxxAQ", true);
config.setRegion(Region.cn);
whiteSdk = new WhiteSdk(whiteboardView, this, config);
PlayerConfiguration playerConfig = new PlayerConfiguration("a7xxx69", "NETLESSROOM_YWxxxjk");
playerConfig.setBeginTimestamp(1609459200000L);
playerConfig.setDuration(45 * 60 * 1000L);
whiteSdk.createPlayer(playerConfig, this, new Promise<Player>() {
@Override
public void then(Player player) {
whitePlayer = player;
// 关联白板播放器到同步管理器
syncManager.setWhitePlayer(player);
// 开始同步播放
player.seekToScheduleTime(0);
syncManager.play();
}
@Override
public void catchEx(SDKError error) {
Log.e(TAG, "创建回放失败: " + error.getMessage());
}
});
}
@Override
public void onPhaseChanged(PlayerPhase phase) {
// 重要!必须同步白板播放器的阶段变化
if (syncManager != null) {
syncManager.updateWhitePlayerPhase(phase);
}
Log.i(TAG, "播放状态: " + phase);
}
// 播放控制
private void play() {
if (syncManager != null) {
syncManager.play();
}
}
private void pause() {
if (syncManager != null) {
syncManager.pause();
}
}
private void seekTo(long time) {
if (syncManager != null) {
syncManager.seek(time);
}
}
private void setPlaybackSpeed(float speed) {
if (syncManager != null) {
syncManager.setPlaybackSpeed(speed);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (whitePlayer != null) {
whitePlayer.stop();
}
if (mediaPlayer != null) {
mediaPlayer.release();
}
}
// 实现其他 PlayerListener 方法...
}
参考信息
开发注意事项
在使用 Fastboard 录制与回放功能时,需要注意以下几点:
- 轻量级封装:Fastboard Android SDK 提供了
FastReplay类简化回放配置,但核心功能仍由底层 Whiteboard SDK 提供。 - 多窗口回放:如果录制时使用了多窗口模式,回放时需要在 SDK 配置和回放配置中都启用多窗口支持,并设置正确的窗口比例。
- 录制延时:录制内容不是实时生成的,新创建的房间可能需要等待一段时间才能获得录制内容。
- 时间范围:建议根据实际使用场景设置合适的回放时间范围,避免回放过长的无效内容。
- 网络缓冲:回放时可能需要网络缓冲,特别是跳转到较远时间点时。
- 同步与异步方法:
- 同步方法(如
getPlayerPhase())返回本地缓存的值,可能不是最新值 - 异步方法(如
getPhase(promise))返回从 JS 端获取的最新值 - 建议日常使用同步方法,调试时使用异步方法
- 同步方法(如
- 线程安全:
PlayerListener的回调在子线程执行,UI 操作需要使用runOnUiThread()切换到主线程。 - 状态同步:使用
PlayerSyncManager时,必须在onPhaseChanged回调中调用updateWhitePlayerPhase方法,否则无法正确同步状态。 - 资源释放:使用完毕后记得调用
player.stop()或fastReplay.destroy()释放播放器资源。
检查房间是否可回放
在创建回放前,可以先检查房间是否有可用的回放数据:
Java
whiteSdk.isPlayable(playerConfig, new Promise<Boolean>() {
@Override
public void then(Boolean isPlayable) {
if (isPlayable) {
// 可以创建回放
whiteSdk.createPlayer(playerConfig, listener, promise);
} else {
Log.w(TAG, "该房间没有可用的回放数据");
}
}
@Override
public void catchEx(SDKError error) {
Log.e(TAG, "检查回放状态失败: " + error.getMessage());
}
});
API 参考
Player.play- 开始播放Player.pause- 暂停播放Player.seekToScheduleTime- 跳转播放位置Player.setPlaybackSpeed- 设置播放速度Player.setObserverMode- 设置观看模式- POST 创建房间