Skip to content

浏览器视频播放技术原理:从video标签到MSE、EME、WebCodec完全拆解

为什么Chrome不能播M3U8?为什么HLS比MP4慢?为什么B站能播FLV?为什么Netflix的视频盗录不了?这些问题的答案都藏在浏览器的视频播放技术栈里。今天从最基础的video标签开始,一路拆到MSE、EME、WebCodecs,把浏览器视频播放的原理讲透。

浏览器视频播放的演进

浏览器视频播放经历了三代技术:

第一代:HTML5 video标签(2010年以前)

最简单的方式:

html
<video src="video.mp4" controls></video>

浏览器原生支持,直接播放MP4/WebM单文件。但有几个限制:

  • 只能播放"完整文件",不支持流式
  • 只支持浏览器原生支持的编码(H.264、VP8/9)
  • 不支持加密内容
  • 不支持直播

Flash插件填补了这些空白,但Flash已死。

第二代:MSE + EME(2013年至今)

Media Source Extensions(MSE) 解决了流式播放问题:

javascript
const video = document.querySelector('video');
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
  
  // 动态append切片数据
  fetch('chunk-1.m4s')
    .then(res => res.arrayBuffer())
    .then(data => sourceBuffer.appendBuffer(data));
});

Encrypted Media Extensions(EME) 解决了DRM加密问题,让Netflix等平台能在浏览器播放受版权保护的内容。

第三代:WebCodecs(2021年至今)

WebCodecs API 让JS能直接调用浏览器的硬件编解码器,不再局限于video标签的封装:

javascript
// 解码视频帧
const decoder = new VideoDecoder({
  output: (frame) => {
    // 拿到原始VideoFrame,可以做特效处理
    canvas.getContext('2d').drawImage(frame, 0, 0);
    frame.close();
  },
  error: (e) => console.error(e),
});

decoder.configure({
  codec: 'avc1.42E01E',
  hardwareAcceleration: 'prefer-hardware',
});

// 喂入编码数据
decoder.decode(new EncodedVideoChunk({
  type: 'key',
  timestamp: 0,
  data: h264Data,
}));

WebCodecs让浏览器可以做录屏、推流、视频特效、自定义播放器等以前做不到的事。

MSE:流式播放的核心

MSE是现代Web视频播放的基础,所有主流播放器库(hls.js、dash.js、flv.js、Shaka Player)都基于它。

MSE的工作原理

  1. 创建MediaSource对象:JS创建一个虚拟的"媒体源"。
  2. 绑定到video标签:通过 URL.createObjectURL() 生成blob URL,赋给video.src。
  3. 创建SourceBuffer:为每种流(视频、音频)创建独立的buffer。
  4. append切片数据:JS下载切片,转成ArrayBuffer,调用 sourceBuffer.appendBuffer()
  5. 浏览器内部解码播放:video标签从SourceBuffer拉取数据,解码并渲染。

MSE的限制

  • 必须使用fMP4或WebM:原生MSE不支持TS切片,hls.js会把TS转成fMP4再喂给MSE
  • codec必须提前声明:创建SourceBuffer时要指定编码,不能中途换
  • 内存管理:append的数据要手动remove释放内存,否则会爆
  • 顺序严格:必须按时间戳顺序append,否则报错

MSE支持的编码

编码ChromeFirefoxSafariEdge
H.264 (avc1)
H.265 (hevc)🟡🟡
VP9✅(iOS 14+)
AV1✅(85+)✅(68+)✅(16.4+)
AAC
Opus✅(部分)

hls.js、dash.js、flv.js的原理

hls.js:在非Safari浏览器播放HLS

hls.js是MSE的典型应用:

  1. 下载M3U8:fetch请求M3U8文件
  2. 解析M3U8:用JS解析纯文本格式,提取切片URL
  3. 下载TS切片:按顺序fetch每个.ts文件
  4. TS转fMP4:把MPEG-TS容器转成fMP4(关键步骤,因为MSE不认TS)
  5. append到MSE:调用 sourceBuffer.appendBuffer() 喂给video
  6. 自适应码率:监控下载速度和缓冲区,切换不同质量的M3U8

整个过程对开发者透明,引入hls.js后只需几行代码:

javascript
if (Hls.isSupported()) {
  const hls = new Hls();
  hls.loadSource('https://example.com/stream.m3u8');
  hls.attachMedia(video);
}

详细使用可以看本站 HLS.js完全指南

dash.js:在浏览器播放MPEG-DASH

dash.js的原理和hls.js类似,区别在于:

  • 解析的是MPD(XML格式)而非M3U8(文本格式)
  • 切片通常是fMP4而非TS,无需容器转换
  • 直接append fMP4到MSE

flv.js:在浏览器播放FLV

B站开源的flv.js 让Chrome能播放FLV流:

  1. 解析FLV容器头
  2. 提取FLV中的AAC音频流和H.264视频流
  3. 封装成fMP4格式
  4. append到MSE播放

详细原理可以看本站 flv.js RTMP/FLV播放器指南

EME:DRM加密播放

Netflix、Disney+等平台的视频无法盗录,靠的是 EME(Encrypted Media Extensions) + DRM系统。

EME的工作流程

  1. 浏览器请求加密内容:视频切片是加密的,无法直接解码
  2. JS调用EME API:通过 MediaKeySystemAccess.createMediaKeys() 创建DRM会话
  3. 生成许可证请求:浏览器向DRM授权服务器请求解密密钥
  4. 服务器验证用户身份:通过Cookie、Token等确认用户有权限
  5. 下发密钥:服务器返回加密的密钥
  6. 浏览器内部解密:在安全区域(CDM,Content Decryption Module)内解密切片
  7. 解码播放:解密后的数据送入解码器,正常播放

三大DRM系统

DRM厂商浏览器支持适用场景
WidevineGoogleChrome、Firefox、Edge、AndroidWeb首选
PlayReadyMicrosoftEdge、IE、XboxWindows生态
FairPlayAppleSafari、iOSApple生态

商业平台需要同时支持三种DRM,覆盖所有浏览器。开源方案可以参考 Shaka Player 的DRM实现。

WebCodecs:浏览器视频处理的新时代

WebCodecs让JS能直接控制编解码,突破了video标签的限制。

WebCodecs能做什么

  1. 录屏和屏幕共享:捕获屏幕帧,编码为H.264/VP9,推流或保存
  2. 视频特效:解码视频帧到Canvas,加滤镜后重新编码
  3. 低延迟播放:自定义播放器,绕过video标签的开销
  4. 视频转码:在浏览器内转码,无需服务器
  5. AI视频处理:把视频帧送入TensorFlow.js做识别

录屏推流示例

javascript
// 捕获屏幕
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });

// 用WebCodecs编码
const track = stream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();

const encoder = new VideoEncoder({
  output: (chunk, meta) => {
    // 把编码后的H.264数据通过WebSocket/WebRTC发送
    sendChunk(chunk);
  },
  error: (e) => console.error(e),
});

encoder.configure({
  codec: 'avc1.42E01E',
  width: 1920,
  height: 1080,
  bitrate: 4_000_000,
  framerate: 30,
});

while (true) {
  const { done, value: frame } = await reader.read();
  if (done) break;
  encoder.encode(frame, { keyFrame: false });
  frame.close();
}

视频播放卡顿的根本原因

理解原理后,能精准定位卡顿原因:

1. 网络瓶颈

  • 下载速度 < 视频码率:缓冲区被消耗光,必然卡顿
  • 诊断:用 video.buffered 查看缓冲时长,缓冲<2秒就危险
  • 解决:降码率、用ABR切换低清晰度、CDN加速

2. 解码瓶颈

  • CPU/GPU占用过高:解码速度跟不上播放速度
  • 诊断:用Chrome DevTools的Performance面板查看
  • 解决:换硬件解码、降分辨率、降帧率

3. MSE缓冲管理

  • SourceBuffer满了:append失败导致播放中断
  • 诊断:监听 sourceBuffer.updateend 事件,检查 sourceBuffer.buffered
  • 解决:定期 sourceBuffer.remove() 清理已播放内容

4. 主线程阻塞

  • JS执行阻塞了video渲染:大计算、长任务卡住主线程
  • 诊断:Performance面板查看长任务
  • 解决:把计算放到Web Worker、用 requestIdleCallback

5. GC停顿

  • 大量ArrayBuffer触发GC:append切片时分配大量内存
  • 诊断:Performance面板的Memory视图
  • 解决:复用ArrayBuffer、控制切片大小

浏览器视频播放的边界

Web视频播放能做到什么程度?

能力video标签MSEWebCodecs
播放MP4单文件
播放HLS流✅(hls.js)
播放DASH流✅(dash.js)
播放DRM内容✅(EME)
实时录屏推流
视频帧特效处理
自定义编解码
硬件加速解码

浏览器视频技术对比MP4原生播放

为什么HLS播放比MP4慢?从原理层面看:

维度MP4原生播放HLS(hls.js)
起播请求1次(直接拉MP4)2次(M3U8 + 第一切片)
起播延迟接近0(边下边播)2-10秒(等完整切片)
缓冲策略浏览器内部管理JS手动管理MSE
主线程开销极小较大(解析、转封装)
ABR支持
直播支持
加密支持✅(DRM)

简单场景用MP4原生播放最快最稳,复杂场景(直播、ABR、DRM)必须用MSE方案。

总结

浏览器视频播放从简陋的video标签发展到MSE+EME+WebCodecs三大API体系,已经能做几乎所有桌面播放器能做的事。

  • video标签:播放本地MP4/WebM,最简单
  • MSE:流式播放,hls.js/dash.js/flv.js都基于它
  • EME:DRM加密播放,Netflix/Disney+用的就是这套
  • WebCodecs:直接控制编解码,录屏推流视频特效的新时代

理解这套技术栈,能帮你精准定位播放问题,也能在做产品技术选型时不踩坑。

快速回顾:

  • video标签:MP4单文件播放
  • MSE:流式播放核心,hls.js/dash.js/flv.js底层
  • EME:DRM加密播放
  • WebCodecs:浏览器编解码新时代
  • 播放卡顿:网络/解码/MSE缓冲/主线程/GC五类原因

参考资料

Last updated: