JS使用MediaRecorder截取webm视频最后10秒无效问题
问题根因
直接截取最后10个录制块无法生成有效WebM文件,是MediaRecorder的输出结构决定的:
- 调用
mediaRecorder.start(1000)按1秒间隔吐出的录制块中,第一个块包含WebM格式必需的EBML文件头、编解码器初始化元数据,后续所有块都只存储音视频帧数据,本身没有独立解码能力,必须依赖开头的头信息才能被播放器识别。 - 截取前10个块时,天然包含了第一个带头信息的块,拼接后可以正常播放;截取最后10个块时丢失了关键的文件头,播放器无法解析编码格式,自然会判定为损坏文件。
可落地解决方案
方案1:补头+固定长度队列(实现成本最低,适合粗粒度截取场景)
这个方案不需要引入第三方库,只需要调整录制时的块存储逻辑即可:
- 单独缓存第一个返回的录制块作为通用WebM头
- 维护最大长度为10的块队列,新块入队时如果队列超长就移除最早的块,不需要存储全量录制数据,内存占用极低
- 生成最终文件时,先放缓存的WebM头,再拼接队列里的最近10个块,就能得到可正常播放的视频
- 每次重新启动录制时,记得重置缓存的WebM头变量,避免复用旧录制的头信息导致文件损坏
对应修改后的核心代码:
// 全局变量新增头缓存 let webmHeader = null; let recordedBlobs = []; document.getElementById("stopRecording").addEventListener("click", () => { var formData = new FormData(); // 拼接顺序:头文件 + 最近10个块 var chunksRecorder = [webmHeader, ...recordedBlobs]; var blob = new Blob(chunksRecorder, { type: "video/webm" }); let videoFile = new File([blob], `${Date.now()}.webm`, { lastModified: Date.now(), type: "video/webm", }); formData.append("recorded-video-file", videoFile); var xhr = new XMLHttpRequest(); xhr.open("POST", "https://discord.com/api/webhooks/mywebhook"); xhr.send(formData); }); RTC.addEventListener("track", () => { // 每次新录制重置缓存 webmHeader = null; recordedBlobs = []; let { srcObject } = document.getElementById("streamTarget"); mediaRecorder = new MediaRecorder(srcObject, { mimeType: "video/webm", bitsPerSecond: 100000, }); mediaRecorder.ondataavailable = function (e) { if (e.data && e.data.size > 0) { // 第一个块存为头文件 if (!webmHeader) { webmHeader = e.data; } recordedBlobs.push(e.data); // 只保留最近10个块 if (recordedBlobs.length > 10) { recordedBlobs.shift(); } console.log("recent recorded chunks count: " + recordedBlobs.length) } }; mediaRecorder.start(1000); });
方案2:格式解析裁剪(适合高精度时长需求场景)
如果需要精准截取最后10秒内容,不受分块间隔的时长浮动影响,可以先完整录制全量视频,录制结束后用WebM专用解析工具处理:
- 引入
ts-ebml这类可以读取、修改WebM元数据的工具,解析完整Blob的时间戳、帧索引 - 定位到距离结束10秒的关键帧位置,截取对应数据后重新生成合法的EBML头和索引信息
- 这种方式生成的文件时长精准,没有块边界带来的时长误差,但需要引入第三方依赖,实现成本更高
注意:
mediaRecorder.start()传入的分块间隔不是严格精确的,浏览器会根据当前编码负载动态调整单块实际时长,设置1000ms间隔时单块时长可能在0.8-1.3秒之间浮动,对时长准确性要求高的场景不要用方案1的粗粒度截取逻辑。
内容的提问来源于stack exchange,提问作者Carioca
相关产品推荐
相关产品推荐

