
本文介绍如何使用 canvas.captureStream() 与 MediaRecorder 按序录制画布内容(如幻灯片)和摄像头画面,并通过 MultiStreamsMixer 实现高质量、时间对齐的多段音视频合成,最终导出为单个可下载的 WebM/MP4 视频。
本文介绍如何使用 canvas.capturestream() 与 mediarecorder 按序录制画布内容(如幻灯片)和摄像头画面,并通过 multistreamsmixer 实现高质量、时间对齐的多段音视频合成,最终导出为单个可下载的 webm/mp4 视频。
在浏览器端实现“幻灯片 + 摄像头 + 幻灯片 + 摄像头”式视频合成,核心难点在于:
✅ 各段媒体流需独立录制、精确时长控制;
✅ 多段视频必须帧率一致、时间轴对齐,否则直接拼接 Blob 会导致播放卡顿、音画不同步或仅首两段生效;
✅ 原生 Blob.concat() 或简单数组合并无法处理容器格式(如 WebM 的 header、cluster、index 等结构),因此 <video></video> 可播放但下载后常损坏或仅部分有效。
你当前代码中的关键问题正是源于此:
const superBlob = new Blob(recordedSegments, { type: 'video/webm' });这行代码只是将多个 WebM 文件的二进制数据线性拼接,而非按规范合并容器结构——结果是无效的 WebM 文件(多数播放器仅识别首段,后续丢弃)。
✅ 正确方案:使用 MultiStreamsMixer(推荐)
MultiStreamsMixer 是专为浏览器设计的轻量级音视频合成库,支持:
- 多路
MediaStream(Canvas Stream + Webcam Stream)同步混流; - 自动处理帧率对齐、时间戳重映射(PTS/DTS);
- 输出标准 WebM 或 MP4(需搭配 ffmpeg.wasm 进阶转码);
- 无服务端依赖,纯前端运行。
▶️ 改造步骤(精简版)
-
引入库(CDN):
<script src="https://cdn.jsdelivr.net/npm/multistreamsmixer@2.0.0/MultiStreamsMixer.min.js"></script>
-
重构录制逻辑:统一采集为 MediaStream,暂存为数组
const segments = []; // 存储 { stream: MediaStream, duration: number } 对象
// 录制画布(2秒幻灯片) function recordCanvas(content, duration = 2000) { const canvasStream = canvas.captureStream(frameRate); segments.push({ stream: canvasStream, duration }); }
// 录制摄像头(3秒实拍) function recordWebcam(duration = 3000) { segments.push({ stream: webcam.srcObject, duration }); }
// 调用序列(严格按顺序) async function prepareSegments() { recordCanvas('Slide 1', 2000); recordWebcam(3000); recordCanvas('Slide 2', 2000); recordWebcam(3000); recordCanvas('Slide 3', 2000); }
3. **使用 MultiStreamsMixer 合成并导出**
```js
async function mergeAndDownload() {
if (segments.length === 0) return;
const mixer = new MultiStreamsMixer();
segments.forEach(({ stream, duration }) => {
mixer.addStream(stream, { duration }); // 自动按 duration 截取并同步
});
// 开始混流(返回 MediaStream)
const mixedStream = await mixer.getMixedStream();
// 录制合成后的流
const recorder = new MediaRecorder(mixedStream, {
mimeType: 'video/webm; codecs=vp9'
});
const chunks = [];
recorder.ondataavailable = e => chunks.push(e.data);
recorder.start();
// 等待所有片段总时长后停止
const totalDuration = segments.reduce((sum, s) => sum + s.duration, 0);
setTimeout(() => {
recorder.stop();
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
downLoadVideo(blob); // 复用你的下载函数
};
}, totalDuration + 500); // +500ms 容错
}⚠️ 注意事项:
- 所有
MediaStream必须具有相同分辨率与帧率(建议统一设为640×480@30fps),否则 Mixer 会自动缩放/插帧,影响质量;canvas.captureStream()在 Safari 中需启用实验性标志(chrome://flags/#enable-experimental-web-platform-features),生产环境建议 fallback 到requestAnimationFrame + createImageBitmap截帧方案;- 若需 MP4 格式(兼容性更好),可在混流后接入
ffmpeg.wasm进行转封装:const ffmpeg = FFmpeg.createFFmpeg({ corePath: 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/ffmpeg-core.js' }); await ffmpeg.load(); ffmpeg.FS('writeFile', 'input.webm', await fetchFile(blob)); await ffmpeg.run('-i', 'input.webm', '-c:v', 'libx264', '-crf', '23', 'output.mp4'); const mp4Blob = new Blob([ffmpeg.FS('readFile', 'output.mp4')], { type: 'video/mp4' });
✅ 总结
| 方案 | 是否推荐 | 原因 |
|---|---|---|
❌ 直接 Blob.concat()
|
否 | 破坏容器结构,生成无效文件 |
| ❌ 服务端 FFmpeg(如 Node.js) | 不适用本场景 | 题干明确要求纯浏览器端 |
| ✅ MultiStreamsMixer | 强烈推荐 | 专为浏览器流合成设计,零配置、高兼容、时间精准 |
| ⚙️ FFmpeg.wasm + Mixer | 进阶推荐 | 需要 MP4 输出或高级编码控制时选用 |
最终,只需调用 prepareSegments().then(mergeAndDownload),即可生成结构完整、顺序清晰、可直接下载播放的混合视频——真正实现「Slide + Video + Slide + Video…」的交互式视频创作体验。


















