录制文件保存:从 Blob 到下载

好,咱们接着聊。上一节我们把录制好的数据拿到了,但说实话,光有数据还不够——你得让用户能真正用上它。我刚开始做 WebRTC 录制时,就卡在这一步:数据拿到了,然后呢?

嗯,今天我们就来解决这个问题。说白了,就是把内存里的二进制数据,变成用户可以预览、下载、保存到本地的文件。这里面有几个关键环节:创建 Blob URL、实现预览播放、触发下载,以及自定义文件名和格式。

Blob URL 是什么?

先说说 Blob URL。你想想看,浏览器里的视频数据存在内存里,怎么让 <video> 标签去播放它?直接传二进制数据?不行。这时候就需要 Blob URL 来搭桥。

Blob URL 其实就是一个特殊的 URL,格式像这样:

blob:https://example.com/550e8400-e29b-41d4-a716-446655440000

它指向的是浏览器内存中的一段数据,而不是服务器上的文件。创建它只需要一行代码:

const blob = new Blob([recordedChunks], { type: 'video/webm' });
const url = URL.createObjectURL(blob);

这里要注意一个坑:Blob URL 是和页面生命周期绑定的。页面关了,URL 就失效了。我在项目中遇到过有人把 Blob URL 存到 localStorage 里,第二天打开页面想播放,结果啥也没有——嗯,白忙活一场。

⚠️ 重要提醒: Blob URL 用完记得释放。调用 URL.revokeObjectURL(url) 可以释放内存。如果不释放,页面会越来越卡,尤其是录制长视频时。

实现视频预览播放

有了 Blob URL,预览就简单了。直接把 URL 赋值给 <video> 标签的 src 属性就行。

const video = document.getElementById('preview');
video.src = url;
video.controls = true;
video.play();

我个人习惯在预览前先检查一下浏览器是否支持播放这个格式。你想想看,万一用户用的是 Safari,录的是 webm 格式,那播放就会出问题。所以我会加一段检测:

const canPlay = video.canPlayType('video/webm');
if (canPlay === 'probably' || canPlay === 'maybe') {
  video.src = url;
} else {
  alert('当前浏览器不支持播放此格式,请尝试下载后播放。');
}

这个检测不是万能的,但能避免大部分尴尬。我记得有一次给客户演示,录了 10 分钟的视频,结果预览时黑屏——就是因为没做格式检测。从那以后,我再也不敢省这一步了。

下载录制的视频文件

预览搞定了,接下来是下载。这里要用到 <a> 标签的 download 属性。核心逻辑就三步:

  1. 创建 Blob URL
  2. 创建一个隐藏的 <a> 标签
  3. 模拟点击触发下载

代码长这样:

function downloadVideo(blob, fileName) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

这里有个细节:一定要把 <a> 标签加到 DOM 里,否则某些浏览器(比如老版 Firefox)不会触发下载。我踩过这个坑,当时找了半天 bug,最后发现是少了 appendChild 这一步。

💡 小技巧: 如果你想让下载更流畅,可以在用户点击下载按钮时,先显示一个 loading 状态,等 Blob URL 创建完成再触发下载。尤其是大文件,创建 Blob URL 也需要时间。

自定义文件名与格式

默认下载的文件名通常是 blob 或者 download,这显然不够友好。我们需要让用户自定义文件名和格式。

我的做法是提供一个输入框让用户填文件名,再提供一个下拉框选择格式。格式这块要注意:录制的格式必须和实际数据匹配。比如你录的是 webm,就不能硬改成 mp4,否则播放器打不开。

function getFileName() {
  const name = document.getElementById('fileName').value.trim();
  const format = document.getElementById('fileFormat').value;
  return name ? `${name}.${format}` : `recording_${Date.now()}.${format}`;
}

我个人习惯给文件名加个时间戳,避免重名覆盖。你想想看,用户录了好几个视频,如果都叫 recording.webm,那后面下载的就会覆盖前面的。加个时间戳就安全多了。

格式选择上,我一般提供三个选项:

格式 适用场景 兼容性
webm Chrome、Firefox 录制 高(但 Safari 不支持)
mp4 需要广泛兼容 高(但录制时需额外转码)
ogg 音频录制为主 中等

嗯,这里要提醒一下:纯前端录制时,格式是由 MediaRecorder 的 mimeType 决定的。你不能随意改扩展名。比如你录的是 webm,但用户选了 mp4,那下载后的文件其实还是 webm 格式,只是名字叫 .mp4。这会导致播放器打不开。

✅ 最佳实践: 下载时,文件名中的扩展名应该和实际数据的 mimeType 保持一致。如果用户想转格式,那需要后端转码或者使用 WebAssembly 方案,这超出了本章范围。

完整流程梳理

说了这么多,咱们把整个流程串起来。我画了一张图,帮你理清思路:

录制文件保存完整流程 1. 获取录制数据 recordedChunks 数组 2. 创建 Blob 对象 new Blob(chunks, {type}) 3. 生成 Blob URL URL.createObjectURL() 预览播放 video.src = blobURL 下载文件 a.click() 触发下载 释放资源:URL.revokeObjectURL(blobURL) 避免内存泄漏,提升页面性能

从图上可以看得很清楚:数据从录制到保存,经历了三个关键步骤。预览和下载是两条并行路径,但最终都要记得释放 Blob URL 资源。

避坑指南

最后,我把自己踩过的坑整理一下,希望能帮你少走弯路:

  • Blob URL 不能跨页面使用——别想着存到 localStorage 里下次用,没用的。
  • 大文件下载前先检查内存——录制 1 小时以上的视频,Blob 对象可能占几百 MB,低端手机直接崩溃。我建议加个大小限制提示。
  • 文件名不要用特殊字符——比如 /\: 这些,Windows 系统会报错。我一般用正则过滤掉:fileName.replace(/[\\/:*?"<>|]/g, '')
  • 移动端 Safari 的下载行为不同——它不会自动下载,而是打开一个新标签页播放。嗯,这个目前没有完美解决方案,只能提示用户长按视频保存。

好了,关于录制文件保存的内容就这些。你只要把 Blob URL、预览、下载、自定义文件名这几个点串起来,就能做出一个完整的录制保存功能。代码量不大,但细节不少——尤其是那些边界情况,处理好了用户体验会提升一大截。

📌 核心要点回顾:
  • Blob URL 是浏览器内存数据的临时链接,用完要释放
  • 预览时先检测浏览器是否支持播放格式
  • 下载通过 <a> 标签的 download 属性实现
  • 文件名加时间戳避免重名,扩展名要和实际格式一致
公众号:蓝海资料掘金营,微信deep3321