视频动辄几个G,网盘传不动、微信发不了、邮件直接被拒收。很多人第一反应是下载付费压缩软件,或者把视频传到某个在线压缩网站。先不说在线网站上传下载两趟的时间成本,单是“把私人视频传到别人服务器上”这件事,我就劝你慎重。其实现在的浏览器早就具备了在本地完成视频压缩的能力,不花钱、不装软件、不上传,全程用你电脑的算力跑完压缩流程。
这篇文章我就把我的实操过程、核心代码、以及踩过的坑全部拆开讲清楚。内容包括整体设计思路、两种可落地的压缩方案、具体实现代码、关键参数调优、以及一份真实环境下的问题排查速查表,希望能帮你在五分钟内跑出一版能用的本地压缩工具。
1. 整体设计与思路拆解
1.1 为什么选择浏览器端本地压缩
先明确一个观点:视频压缩的本质不是“把文件变小”,而是“用可接受的画质损失换体积”。所有压缩工具,不管是收费的、开源的、在线的,核心都在做同一件事——重新编码。既然是重新编码,就必然涉及 CPU 运算和参数取舍,所以不存在什么“无损压缩完体积小一半”的神话,别再被误导了。
那为什么选浏览器端?三个理由足够说服我:
第一,隐私安全。视频文件从头到尾只在你本机流转,不经过任何服务器,对于家里孩子的录像、身份证扫描视频、合同签署过程这类敏感内容,这种处理方式是唯一让人放心的。
第二,零成本零安装。Windows 自带工具、macOS 的 Compressor、Adobe Media Encoder,要么功能残缺,要么收费,要么学习成本高。浏览器人人都有,Chrome 也好 Edge 也好,打开一个 HTML 页面就能跑。
第三,跨平台一致。你在一台 Windows 机器上写的页面,拿到 macOS、Linux 上同样能跑,不用为每个平台单独写一套本地压缩工具。
1.2 技术选型:两条路线,看需求选
浏览器端做视频压缩,主流路子就两条,我分别给你拆解:
路线一:解码逐帧重绘(canvas + requestAnimationFrame + MediaRecorder)。把视频逐帧画到 canvas 上,再用 MediaRecorder 录制 canvas 画面。优点是可操作空间大,你能在每一帧上做滤镜、加文字、动态裁剪,甚至可以跳帧抽帧。缺点是性能开销大,因为每一帧都要经历“解码 → 绘制 → 编码”三个环节,4K 视频想实时跑基本没戏。
路线二:边播边录(video 元素播放 + MediaRecorder 录制)。直接把 video 元素作为录制源,用 MediaRecorder 按编码器参数录制,浏览器底层会直接读取解码后的画面帧进行重编码,性能比 canvas 路线好一个量级。这也是我最终采用的方案,后面我所有代码都基于这个思路。
提示:如果你的需求只是简单压缩,比如把 1080P 压到 720P,把 30Mbps 码率降到 8Mbps,那就走路线二,简单稳定省资源。如果你要做的是“给视频加日期水印”或“裁剪片段再压缩”这种复合操作,那就得用路线一,因为路线二没法在录制过程中叠加画面处理。
1.3 预期效果与适用边界
我用一个 5 分钟 1080P 30fps、原始大小 980MB 的航拍素材做过实测,使用路线二配合 2Mbps 视频码率、128kbps 音频码率,输出大小约 82MB,压缩率接近 92%,画质在手机上看几乎无差别,在 27 寸显示器上仔细看能发现树叶边缘轻微模糊。
这个结果不是上限,你完全可以通过调低分辨率、降低帧率、压低码率获得更激进的压缩比。但有一条边界要认清,码率设太低会出现马赛克效应,文件是小了,视频也废了。如果只是聊天发视频,码率降到 1Mbps 都行,如果是剪辑素材,建议至少保留 8Mbps。
2. 核心实现:基于 MediaRecorder 的本地压缩方案
2.1 页面基础框架
先搭一个能跑的最小 HTML 页面,不需要任何框架,一个文件就能干活:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>浏览器本地视频压缩</title> </head> <body> <h2>本地视频压缩工具</h2> <input type="file" id="videoFile" accept="video/*" /> <div> <label>压缩比例预设:</label> <select id="preset"> <option value="high">高质量(1080P / 8Mbps)</option> <option value="medium" selected>均衡(1080P / 4Mbps)</option> <option value="low">高压缩(720P / 1.5Mbps)</option> </select> </div> <button id="startBtn" disabled>开始压缩</button> <div id="progress">等待选择文件...</div> <video id="preview" controls style="max-width: 100%; margin-top: 12px; display: none;"></video> <a id="downloadLink" style="display: none;">下载压缩后视频</a> <script src="compressor.js"></script> </body> </html>这里的核心逻辑是:用户选择视频后先读元数据,拿到时长、分辨率、码率这些信息,再根据用户选择的预设方案计算出目标码率和分辨率,最后调用压缩流程。
2.2 读取视频元数据
开始压之前,必须先把视频的“底细”摸清楚。这一段代码用 video 元素加载文件,拿到 duration、videoWidth、videoHeight 和当前码率:
async function getVideoMeta(file) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const video = document.createElement('video'); video.preload = 'metadata'; video.src = url; video.onloadedmetadata = () => { const duration = video.duration; const width = video.videoWidth; const height = video.videoHeight; const sizeMB = file.size / (1024 * 1024); // 码率按 (文件大小 * 8) / 时长 估算,单位 kbps const bitrate = Math.round((file.size * 8) / duration / 1000); URL.revokeObjectURL(url); resolve({ duration, width, height, sizeMB, bitrate }); }; video.onerror = () => reject(new Error('读取视频元数据失败,请确认文件不是损坏的或格式受支持')); }); }2.3 按预设计算目标参数
压缩不是拿过来就瞎压,你得先想好目标分辨率、视频码率、音频码率这三件事。我用对象存储三档预设:
const PRESETS = { high: { width: 1920, videoBitrate: 8 * 1000 * 1000, audioBitrate: 192 * 1000 }, medium: { width: 1920, videoBitrate: 4 * 1000 * 1000, audioBitrate: 128 * 1000 }, low: { width: 1280, videoBitrate: 1.5 * 1000 * 1000, audioBitrate: 96 * 1000 } };计算目标尺寸时要注意保持宽高比。如果原视频是竖屏 1080x1920,你不能直接压成横屏 1920x1080,否则画面就变形了:
function computeTargetSize(srcWidth, srcHeight, maxWidth) { const ratio = Math.min(maxWidth / srcWidth, 1); return { width: Math.round(srcWidth * ratio / 2) * 2, height: Math.round(srcHeight * ratio / 2) * 2 }; }这里有一个细节:宽高各除以 2 再乘回 2,是为了保证最终尺寸是偶数。很多编码器对奇数分辨率会报错,或者自动补边导致出现绿边,这个坑我第一次踩的时候排查了半小时。如果你要压 720P,就是把 maxWidth 设为 1280。选定目标宽度 1920 的情况下,原视频如果只有 1280 宽,ratio 算出来小于 1,宽度不变,不放大,避免没必要的画质损失。
2.4 核心压缩逻辑
压缩的核心逻辑就是把视频放进隐藏的 video 元素播放,然后用 MediaRecorder 录制这个 video 的画面与声音。关键代码如下:
async function compressVideo(file, preset, onProgress) { const meta = await getVideoMeta(file); const target = PRESETS[preset]; const targetSize = computeTargetSize(meta.width, meta.height, target.width); return new Promise((resolve, reject) => { const video = document.createElement('video'); video.src = URL.createObjectURL(file); video.muted = true; // 注意:静音播放,防止录音时候的喇叭回环 video.playsInline = true; video.crossOrigin = 'anonymous'; // 先加载好,再开始录 video.onloadedmetadata = async () => { try { const stream = video.captureStream(); const mimeType = pickSupportedMimeType(); const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: target.videoBitrate, audioBitsPerSecond: target.audioBitrate }); const chunks = []; recorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); }; recorder.onstop = () => { const blob = new Blob(chunks, { type: mimeType.split(';')[0] }); URL.revokeObjectURL(video.src); resolve(blob); }; recorder.onerror = (e) => reject(e); // 放到页面上不可见地播放,才算“正在播放”,才能被录制 video.style.display = 'none'; document.body.appendChild(video); video.play(); // 播放进度用于驱动 progress video.ontimeupdate = () => { if (onProgress) onProgress(video.currentTime / meta.duration, meta); }; recorder.start(1000); } catch (err) { reject(err); } }; }); }这里有两个细节很关键:
video.muted = true不是为了让录制没声音,而是为了避免浏览器对“自动播放带声音视频”的拦截限制。captureStream 录制的音轨与 muted 状态无关,音频依然会被录进去。但个别浏览器版本上 muted 会影响音轨输出,保险做法是设置成 muted 后,录制完检查一下音频是否正常(后面常见问题里我会细说)。recorder.start(1000)这个参数是 timeslice,意思是让 MediaRecorder 每 1000ms 触发一次 ondataavailable,把数据分块吐出。不传这个参数的话,数据会攒到最后一次吐出来,一旦浏览器崩溃或你中途看了一眼 console 不小心关掉标签页,之前录的全部白费。传了之后,配合进度显示,至少能做到“已经录的能保住”。
2.5 挑选浏览器支持的封装格式
MediaRecorder 在不同浏览器上支持的 mimeType 差别很大,事先探测是必须的。探测方式很简单:
function pickSupportedMimeType() { const candidates = [ 'video/mp4;codecs="avc1.42E01E,mp4a.40.2"', 'video/mp4;codecs="avc1.42E01E"', 'video/webm;codecs="vp9,opus"', 'video/webm;codecs="vp8,opus"', 'video/webm' ]; for (const c of candidates) { if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported(c)) { return c; } } return ''; }需要说明的是,Chrome 目前对 mp4 封装的支持已经比较完善,Edge 和 Firefox 也都支持 webm。如果最终得到的是 webm 格式,在微信里直接播放可能有问题,这时候需要一个后续兼容策略(后面会讲)。
2.6 完整调用与下载
所有准备工作就绪后,调用逻辑非常简单:
startBtn.onclick = async () => { const file = videoFile.files[0]; if (!file) return; startBtn.disabled = true; startBtn.textContent = '压缩中...'; try { const blob = await compressVideo(file, presetSelect.value, (progress, meta) => { progressDiv.textContent = `压缩进度:${Math.round(progress * 100)}%`; }); const url = URL.createObjectURL(blob); preview.src = url; preview.style.display = 'block'; downloadLink.href = url; downloadLink.download = `compressed_${Date.now()}.${blob.type.includes('mp4') ? 'mp4' : 'webm'}`; downloadLink.style.display = 'inline-block'; } catch (err) { progressDiv.textContent = '压缩失败:' + err.message; } finally { startBtn.disabled = false; startBtn.textContent = '开始压缩'; } };下载链接的download属性要显式指定文件名后缀,否则浏览器可能直接播放或者用随机文件名下载。
3. 进阶:逐帧重绘方案(canvas 路线)
3.1 为什么还要走 canvas
MediaRecorder 路线简单,但有个硬伤——它只能“录制正在播放的画面”,没法对画面做任何加工。假设你的需求是:把一个横屏 1920x1080 的素材裁剪成朋友圈常见的 1080x1350 竖版,中间保持画面主体不变。这种需求下,canvas 路线就是唯一选择,因为你需要在每一帧上重新计算绘制区域。
另外,canvas 路线可以天然实现“丢帧压缩”。比如你有 60fps 的素材,人眼很难看出 30fps 与 60fps 的区别,那就可以每两帧画一帧,让最终视频变成 30fps,节省一半帧数对应的编码量。MediaRecorder 路线做不到这么精准的控制。
3.2 canvas 逐帧重绘代码
核心思路:用 requestAnimationFrame 把 video 的每一帧画到 canvas 上,再用另一个 MediaRecorder 录制 canvas 的 captureStream()。代码如下:
async function compressVideoWithCanvas(file, opts) { const video = document.createElement('video'); video.src = URL.createObjectURL(file); video.muted = true; await new Promise((resolve) => video.onloadedmetadata = resolve); const canvas = document.createElement('canvas'); canvas.width = opts.width; canvas.height = opts.height; const ctx = canvas.getContext('2d'); const stream = canvas.captureStream(opts.outputFps); const recorder = new MediaRecorder(stream, { mimeType: pickSupportedMimeType(), videoBitsPerSecond: opts.videoBitrate, audioBitsPerSecond: opts.audioBitrate }); const chunks = []; recorder.ondataavailable = (e) => e.data.size > 0 && chunks.push(e.data); video.play(); video.style.display = 'none'; document.body.appendChild(video); let lastTime = 0; function drawFrame(timestamp) { if (video.ended || video.paused) return; if (timestamp - lastTime >= 1000 / opts.outputFps) { // 计算绘制区域,实现居中裁剪 const srcRatio = video.videoWidth / video.videoHeight; const dstRatio = opts.width / opts.height; let srcX = 0, srcY = 0, srcW = video.videoWidth, srcH = video.videoHeight; if (srcRatio > dstRatio) { srcW = Math.floor(video.videoHeight * dstRatio); srcX = Math.floor((video.videoWidth - srcW) / 2); } else { srcH = Math.floor(video.videoWidth / dstRatio); srcY = Math.floor((video.videoHeight - srcH) / 2); } ctx.drawImage(video, srcX, srcY, srcW, srcH, 0, 0, opts.width, opts.height); lastTime = timestamp; } requestAnimationFrame(drawFrame); } requestAnimationFrame(drawFrame); recorder.start(1000); // 监听 video ended 之后停止录制 video.onended = () => recorder.stop(); // 返回一个 promise,recorder.onstop 时 resolve blob }3.3 canvas 路线的性能瓶颈与权衡
这条路线的性能瓶颈非常明显:每一帧都要经历一次 canvas 绘制和一次编码器调用,CPU 和 GPU 的负载远高于 MediaRecorder 直接录制。我用 4K 素材做过测试,canvas 路线连 15fps 都稳不住,画面会卡顿,而 MediaRecorder 路线可以轻松跑满 30fps。所以我的建议是:
- 素材本身低于 1080P、或者只做简单裁剪时,用 canvas 路线,灵活可控。
- 素材是 4K、或者对实时性要求高时,老老实实走 MediaRecorder 路线。
- 如果两者必须兼顾,用 offscreenCanvas + Web Worker 做离屏绘制,会让主线程不那么卡顿,但编码仍受限于浏览器底层的 MediaRecorder 实现,别指望它能快过桌面级压缩软件。
4. 参数调优与注意事项
4.1 码率设置的核心逻辑
码率是压缩的灵魂,但很多人只会抄参数,不明白背后的关系。码率与体积的关系很简单:体积(Byte) = 码率(bps) × 时长(秒) / 8。举个例子,10 分钟视频,视频码率 4Mbps,音频待会另算,那么视频部分体积就是 4,000,000 × 600 / 8 = 300,000,000 Byte,约 286MB。音频如果你设 128kbps,就是 128,000 × 600 / 8 = 9,600,000 Byte,约 9.2MB。所以音频在你控制到 128kbps 以下时,基本可以忽略,大头全在视频码率上。
视频码率本身也不是随便拍的。同一个码率下,静态画面(比如访谈节目)画质会很锐利,而高动态画面(比如航拍、体育比赛)就会糊成一团。这就是为什么那些在线网站老让你选“视频是被压缩了还是变模糊了”——模糊不是玄学,是码率不够覆盖画面复杂度。如果你手里的素材是快速切换的屏幕录制或者赛车比赛,建议码率至少比静态素材高 50%。
4.2 分辨率与帧率的取舍原则
分辨率降低可以显著减少数据量,但要注意一个原则:降分辨率比降帧率更划算。人眼对空间分辨率的敏感程度超过对帧率的敏感程度,同等码率下,720P/30fps 的观感通常比 1080P/15fps 好得多。原因很简单,15fps 下运动场景会明显卡顿,而 720P 只是细节少一点,但动作是连贯的。
实际操作中我的参考配置:
| 使用场景 | 目标分辨率 | 视频码率 | 音频码率 | 说明 |
|---|---|---|---|---|
| 微信发送 / 社交平台 | 720P | 1.5 - 2 Mbps | 96 - 128 kbps | 体积优先,画质够用 |
| 网盘备份 / 家庭录像 | 1080P | 4 - 6 Mbps | 128 kbps | 均衡方案 |
| 视频剪辑素材 | 1080P | 8 - 12 Mbps | 192 kbps | 保留较多细节,便于后期 |
| 个人收藏 / 精修作品 | 保持原分辨率 | 原码率的 60%-70% | 192 kbps | 质量优先,体积也明显下降 |
4.3 timeslice 与分块录制的作用
前面提过recorder.start(1000)里的 timeslice 参数,现在详细说一下。MediaRecorder 的 ondataavailable 回调触发时机有两种:要么在 stop() 时一次性触发,要么按 timeslice 周期性触发。你传了 timeslice,就相当于把录制的数据流按时间切片存储,每片通过一次回调交给你。好处有三个:
- 实时获取数据,可用于展示进度。
- 避免录制时间过长时内存暴涨,分块存储只需要维护 chunks 数组。
- 录制中途如果页面崩溃,至少保住了此前已触发的分块,不会全部丢。
我在实测中发现,timeslice 不是越小越好。设成 100ms 会频繁触发回调,拼接生成的 blob 有时会出现时间戳错乱,导致播放器拖动进度条时卡顿。设成 1000-2000ms 是最稳的区段,对播放器兼容性也最好。
4.4 音频处理细节
很多人压缩完发现没声音,第一反应是编码器坏了,其实是音频轨道在录制环节就丢了。原因通常是:
- video 元素没有被正确加载音频轨道。如果原始视频本身是音视频分离的(比如从某个剪辑软件导出时只选了视频轨),captureStream 里自然不会带音频。
- 某些浏览器在调用 captureStream 前要求 video 处于非静音状态才能捕获音频。但你一旦非静音,自动播放策略又会拦住播放请求。我的折中方案是:先在 muted 状态下开始播放,等 currentTime > 0 时再设置 video.muted = false,同时把 video 的音量设为 0。这样既能规避自动播放拦截,又能让音频轨道正常捕获。
video.muted = true; video.play(); video.addEventListener('timeupdate', function setUnmuted() { if (video.currentTime > 0.1) { video.muted = false; video.volume = 0; video.removeEventListener('timeupdate', setUnmuted); } });注意:volume = 0 和 muted = true 是两回事。浏览器自动播放策略只拦截有声播放,你 muted 开始后马上取消 muting 并把音量调零,既不影响输出音量的实际录制(captureStream 捕获的是解码后的 PCM 数据,不经过扬声器),又能绕开播放限制。
4.5 wasm 转码方案避坑
现在浏览器里已经有人尝试用 wasm 跑 ffmpeg,把完整的音视频解码、滤镜、编码流程搬到前端。听着很酷,但实际体验我只能说:demo 好用,项目慎用。我自己跑过几个开源 wasm-ffmpeg 项目,普遍存在三个问题:首次加载编译产物有几十 MB 的网络开销;处理大文件时内存占用经常冲破浏览器上限,标签页直接白屏;最终生成 mp4 时容易出现 moov 元数据位置问题,导致部分播放器无法拖动进度条。
如果只是压缩视频,建议绕开 wasm 方案,直接用 MediaRecorder。如果必须用 wasm 做转码(比如格式强转、加水印、硬字幕烧录),那要做好内存管理和失败降级预案。
5. 常见问题与排查技巧实录
5.1 压缩后没有声音
这是遇到最多的问题。排查顺序:
检测输出 mimeType 是否包含音频编码器。如果浏览器选择了
video/mp4;codecs="avc1.42E01E"(只有视频编码),那产生无声视频就是必然的。保险做法是选择带mp4a.40.2的候选者。检查源文件的音轨是否存在。用
video.mozHasAudio或video.webkitAudioDecodedByteCount判断(不同浏览器属性不同),或者简单粗暴听一下原视频有没有声音。检查播放时机。captureStream 在视频还没开始播放时,音频轨道可能处于“未激活”状态,录制开始时如果视频还在加载中,前面一两秒的音轨可能丢失。所以我在代码里加上了一个确认标识,等待视频 currentTime 大于 0 后再启动 recorder。
5.2 输出的 mp4 无法在微信 / 手机播放
MediaRecorder 生成的 mp4 属于“碎片化 mp4”,某些播放器对这类格式的兼容性不好。微信自带浏览器对 video 标签的能力不差,但如果你把 mp4 发到聊天窗口,用的是系统播放器,系统播放器对碎片化 mp4 的容忍度就低很多。我的处理办法有两个:
- 做兼容降级:优先输出 mp4,如果无法输出 mp4(比如 Firefox 全部走 webm),就额外提供一个“封装转 mp4”提示,让用户在电脑上用播放器播放,而不是直接丢手机。
- 用 ffmpeg.wasm 做 remux 修复:把 webm 转为标准 mp4 。不过 remux 和刚才说的转码不同,不重编码,只重新封装容器,速度快很多,内存压力也小,这个场景下 wasm 是可用的。
5.3 录制中断 / 卡顿怎么排查
录制中断最常见的诱因是内存不足。浏览器标签页的内存配额虽高,但当你加载一个 2GB 的视频文件到 video 元素,再额外开一个 canvas 复制画面时,内存压力会骤增。排查手段:录制前用performance.memory.usedJSHeapSize看一眼当前占用,如果超过 1.5GB,提示用户重启浏览器或换小文件。第二诱因是主线程阻塞,canvas 路线 + 高分辨率素材会让主线程卡到录制数据供应不上,MediaRecorder 会因此抛出异常。遇到这种情况,考虑把 canvas 绘制放进 Worker 里做,但千万别在 Worker 里创建 MediaRecorder,MediaRecorder 必须在主线程跑。
5.4 压缩后的时长变短了
MediaRecorder 录制过程中如果浏览器出现掉帧,输出的视频时长可能比原视频短,这是录制方案的通病。我实测 30 分钟视频会出现 2-8 秒的偏移。如果对时长一致性有要求,比如录屏类的演示视频,可以在输出后用 wasm 方式修复,或者在预期的编码阶段提升录制性能:关闭其他标签页、降低目标分辨率、降低编码帧率。
5.5 浏览器兼容性速查
我整理了主流浏览器的 MediaRecorder 支持情况:
| 浏览器 | 最低版本 | mp4 封装 | webm 封装 | 备注 |
|---|---|---|---|---|
| Chrome | 60+ | 支持(含音频) | 支持 | 音视频双支持最稳定 |
| Edge | 79+ | 支持 | 支持 | Chromium 同源,表现与 Chrome 一致 |
| Firefox | 25+ | 不支持 | 支持 | 只能输出 webm,注意兼容 |
| Safari | 14.1+ | 支持(部分版本有音频 bug) | 部分支持 | iOS 上 AudioContext 相关限制多 |
提示:在写这个工具时,最稳的浏览器组合是 Chrome 或 Edge。如果你开发完发给同事测试,对方用的是老版 Safari,大概率会卡在“能识别不能录制”的状态。应对策略是:启动时先跑一遍
MediaRecorder.isTypeSupported,如果全部不支持,直接提示升级浏览器,别让用户白选半天文件。
5.6 下载的文件后缀和实际格式不一致
出现这种问题,是因为你的下载链接后缀是根据blob.type.includes('mp4')判断的,但有些浏览器的 blob.type 会写video/x-matroska或者直接为空。稳妥做法:在压缩完成后,把 mimeType 和最终扩展名记下来,如果 mimeType 为空,就按 candidates 列表里第一个支持的类型补一个默认后缀。我后来直接改成了在压缩函数里返回container字段,强制让下载逻辑按它走,不再解析 blob.type。
6. 实操心得与最终建议
这个工具我从零到能跑通,花了大概三个晚上。第一版用 canvas 路线做,压一部电影 CPU 直接拉满,页面都成幻灯片了;第二版换 MediaRecorder 路线,流畅度立刻拉起来。后来我又加上了码率预设、进度条、格式探测、异常提示这些外围功能,才真正变成能放心给家人朋友用的工具。
几点实操体会供你参考:
第一,别迷信某一个参数组合。我上面给的预设值只是起点,不同素材在这些参数下的输出差异很大。建议你在工具里加一个“输出体积预估”显示,让用户在选择预设时就知道这个参数组合下大概会压到多大,直接省去反复试的时间。预估算法就是前面说的:体积 = 码率 × 时长 / 8。
第二,浏览器本地压缩不是万能的。它不是专业编码器,控制力和输出质量上限远低于桌面端工具。但它的价值在于:随开随用、绝对隐私、跨平台、零成本。这台“轻量压缩器”解决 80% 的日常需求是毫无问题的。
第三,如果你要压的视频非常多,建议做批量任务队列,一个个串行跑,避免多路 MediaRecorder 同时运行导致的内存溢出。我在实测中曾经一口气压五个 2GB 文件,第三个就能感觉到明显发热和卡顿,后面改成串行并输出每个文件的速度统计,整体反而更快。
最后分享一个小技巧:录制的过程中如果临时来电话或切走页面,MediaRecorder 会把 audio 轨搞丢一半。保险做法是把页面保持在前台,或者在检测到visibilitychange事件时自动暂停录制并提示用户回来后再继续。这个小细节能避免很多“压完了才发现音画不同步”的悲剧。
你可以现在就打开浏览器,把代码贴进一个 HTML 文件里跑一下,找一个 500MB 左右的普通视频试试。看着体积哗啦掉下来那一刻,你会理解为什么我坚持推荐这条路。