1. 项目概述
在三维GIS应用开发中,Cesium作为领先的WebGL地球可视化引擎,常被用于无人机航迹规划、智慧城市等场景。当我们需要将三维场景的操作过程保存为视频时,浏览器原生的MediaRecorder API提供了完美的解决方案。本文将详细介绍在Vue3 + TypeScript环境下实现Cesium场景录制的完整技术方案。
这个方案的核心价值在于:
- 完全基于浏览器原生API,无需依赖第三方库
- 实现自适应渲染质量,根据帧率动态调整细节
- 提供完整的录制控制流程和视频下载功能
- 特别适合需要录制演示视频或保存操作记录的GIS应用场景
2. 技术选型与原理分析
2.1 MediaRecorder API工作机制
MediaRecorder是WebRTC技术栈的一部分,它可以直接捕获Canvas的输出流。其工作原理是:
- 通过
HTMLCanvasElement.captureStream()获取媒体流 - 配置编码参数(帧率、比特率、编码格式)
- 将视频数据分片存储在内存中
- 最终合并为完整视频文件
关键参数说明:
mimeType: 优先选择vp9编码,其次是vp8,最后回退到通用webm格式videoBitsPerSecond: 2.5Mbps在清晰度和文件大小间取得平衡frameRate: 30fps保证视频流畅性
2.2 Cesium性能优化策略
录制过程中需要特别注意性能问题:
- 帧率监控:通过
scene.preUpdate事件实时计算FPS - 动态调整:
- 帧率<25时降低地形细节(
maximumScreenSpaceError) - 帧率>45时提高画质并开启抗锯齿
- 帧率<25时降低地形细节(
- 按需渲染:启用
requestRenderMode减少不必要的渲染
3. 完整实现步骤
3.1 环境准备与初始化
首先确保项目已配置:
bash复制npm install cesium @cesium/engine vue-cesium
Cesium初始化关键配置:
typescript复制const viewer = new Cesium.Viewer("cesiumContainer", {
timeline: false,
shouldAnimate: true,
// 禁用不必要的控件以提升性能
homeButton: false,
navigationHelpButton: false,
baseLayerPicker: false
});
// 加载地形数据
viewer.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(terrainUrl, {
requestWaterMask: true,
requestVertexNormals: true
});
// 启用地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true;
3.2 录制功能实现
3.2.1 状态管理
typescript复制const isRecording = ref(false);
const mediaRecorder = ref<MediaRecorder | null>(null);
const recordedChunks = ref<Blob[]>([]);
const videoUrl = ref<string>("");
3.2.2 核心录制逻辑
typescript复制const startRecording = async () => {
const canvas = viewer.canvas;
const stream = canvas.captureStream(30);
const options = {
mimeType: 'video/webm;codecs=vp9',
videoBitsPerSecond: 2500000
};
// 编码格式回退策略
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options.mimeType = 'video/webm;codecs=vp8';
// 继续其他回退逻辑...
}
mediaRecorder.value = new MediaRecorder(stream, options);
mediaRecorder.value.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.value.push(event.data);
}
};
mediaRecorder.value.start(1000); // 每1秒收集数据
};
3.3 性能优化实践
3.3.1 帧率监控实现
typescript复制let frameRate = ref(0);
let lastFrameTime = 0;
let frameCount = 0;
viewer.scene.preUpdate.addEventListener(() => {
const now = performance.now();
if (lastFrameTime) {
frameCount++;
if (now - lastFrameTime >= 1000) {
frameRate.value = frameCount;
adjustQualityBasedOnFPS(frameRate.value);
frameCount = 0;
lastFrameTime = now;
}
} else {
lastFrameTime = now;
}
});
3.3.2 动态质量调整
typescript复制const adjustQualityBasedOnFPS = (fps: number) => {
if (fps < 25) {
// 降低画质
viewer.scene.globe.maximumScreenSpaceError = 4;
viewer.scene.postProcessStages.fxaa.enabled = false;
} else if (fps > 45) {
// 提高画质
viewer.scene.globe.maximumScreenSpaceError = 2;
viewer.scene.postProcessStages.fxaa.enabled = true;
}
};
4. 常见问题与解决方案
4.1 录制问题排查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 录制失败 | 浏览器不支持MediaRecorder | 检查浏览器兼容性,推荐Chrome/Firefox |
| 视频卡顿 | 帧率过低 | 降低地形细节,关闭抗锯齿 |
| 文件过大 | 比特率设置过高 | 调整videoBitsPerSecond到1-2Mbps |
| 视频绿屏 | 编码格式不支持 | 按vp9→vp8→webm顺序尝试不同mimeType |
4.2 实战经验分享
- 内存管理:长时间录制时,定期清理
recordedChunks数组避免内存溢出 - 文件命名:使用ISO时间格式生成有意义的文件名
typescript复制`recording_${new Date().toISOString().replace(/[:.]/g, '-')}.webm` - 跨浏览器兼容:Safari需要额外polyfill,建议提示用户使用Chrome
- 大文件处理:超过100MB的视频考虑分片上传到服务器
5. 扩展功能建议
-
录制区域选择:通过
getBoundingClientRect实现局部区域录制typescript复制const rect = element.getBoundingClientRect(); const stream = canvas.captureStream(30, { audio: false, video: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } }); -
音频合成:使用AudioContext添加解说音频
typescript复制const audioStream = audioContext.createMediaStreamDestination(); const mixedStream = new MediaStream([ ...videoStream.getVideoTracks(), ...audioStream.stream.getAudioTracks() ]); -
云存储集成:直接上传到AWS S3或阿里云OSS
typescript复制const uploadToS3 = async (blob: Blob) => { const formData = new FormData(); formData.append('file', blob); await axios.post(s3UploadUrl, formData); };
在实际项目中,我发现录制功能的稳定性很大程度上取决于硬件性能。在配备独立显卡的开发机上测试通过后,务必在目标用户的典型设备上进行验证。对于需要长时间录制的场景,建议增加录制时长提示和自动分段功能。