Skip to content

瀏覽器影片播放原理:video 標籤、MSE、EME、WebCodecs 完整解析

點開 YouTube 影片就開始播放。背後涉及 HTML5 video 標籤、MSE、EME、WebCodecs 等技術。這篇解析瀏覽器播放影片的底層原理。

瀏覽器影片播放技術演進

HTML5 video(2010)→ MSE(2013)→ EME(2017)→ WebCodecs(2022)
技術標準化年用途
HTML5 video2010基本影片播放
MSE2013動態串流(HLS、DASH)
EME2017DRM 保護
WebCodecs2022低階編解碼

HTML5 video 標籤

基本用法

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

多來源

html
<video controls>
  <source src="video.webm" type="video/webm">
  <source src="video.mp4" type="video/mp4">
  您的瀏覽器不支援 video 標籤。
</video>

瀏覽器依序嘗試,選擇支援的格式。

JavaScript 控制

javascript
const video = document.querySelector('video');

// 播放控制
video.play();
video.pause();
video.currentTime = 30;  // 跳到 30 秒

// 屬性
console.log(video.duration);  // 總時長
console.log(video.currentTime);  // 當前位置
console.log(video.volume);  // 音量(0-1)
console.log(video.playbackRate);  // 播放速度

// 事件
video.addEventListener('play', () => console.log('開始播放'));
video.addEventListener('pause', () => console.log('暫停'));
video.addEventListener('ended', () => console.log('播放結束'));
video.addEventListener('timeupdate', () => {
  console.log(`當前位置: ${video.currentTime}`);
});
video.addEventListener('error', (e) => console.error('錯誤:', e));

video 標籤限制

  • 只能播放完整檔案
  • 不支援 HLS、DASH 等串流協定
  • 不支援 DRM 保護內容
  • 無法動態控制緩衝

Media Source Extensions(MSE)

概述

MSE 是 W3C 標準,讓 JavaScript 能動態生成媒體串流餵給 video 元素。MSE 是 hls.js、dash.js 等串流播放器的基礎。

MSE 工作流程

1. 建立 MediaSource
2. 將 MediaSource 綁定到 video.src
3. 等 sourceopen 事件
4. 建立 SourceBuffer
5. 下載媒體片段
6. 用 appendBuffer() 餵給 SourceBuffer
7. 播放器播放

基本 MSE 實作

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

// 2. 等 sourceopen
mediaSource.addEventListener('sourceopen', () => {
  // 3. 建立 SourceBuffer
  const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E,mp4a.40.2"');
  
  // 4. 下載第一個片段
  fetch('segment1.mp4')
    .then(response => response.arrayBuffer())
    .then(data => {
      sourceBuffer.appendBuffer(data);
    });
  
  // 5. 監聽 updateend 繼續下載
  sourceBuffer.addEventListener('updateend', () => {
    if (!sourceBuffer.updating && hasMoreSegments) {
      fetchNextSegment();
    }
  });
});

// 6. 播放
video.play();

ABR 自適應位元率

MSE 可以實現 ABR:

javascript
// 依網路狀況選擇位元率
function selectBitrate(networkSpeed) {
  if (networkSpeed > 5000000) return '1080p';
  if (networkSpeed > 2500000) return '720p';
  return '480p';
}

// 切換位元率
function switchBitrate(newBitrate) {
  // 等待當前片段下載完成
  sourceBuffer.addEventListener('updateend', function switchSegment() {
    sourceBuffer.removeEventListener('updateend', switchSegment);
    // 下載新位元率的片段
    fetch(`segment_${newBitrate}.mp4`)
      .then(response => response.arrayBuffer())
      .then(data => sourceBuffer.appendBuffer(data));
  });
}

hls.js 內部用 MSE

hls.js 簡化了 MSE 的使用:

javascript
const video = document.querySelector('video');
if (Hls.isSupported()) {
  const hls = new Hls();
  hls.loadSource('stream.m3u8');
  hls.attachMedia(video);
  hls.on(Hls.Events.MANIFEST_PARSED, () => {
    video.play();
  });
}

詳見 MPEG-DASH 完整指南 的 Dash.js 實作。

Encrypted Media Extensions(EME)

概述

EME 是 W3C 標準,讓瀏覽器播放 DRM 保護內容。Netflix、Disney+ 等串流服務用 EME 實現 DRM。

EME 工作流程

1. video 元素遇到加密內容
2. 觸發 encrypted 事件
3. JS 請求 MediaKeySystemAccess
4. 建立 MediaKeys
5. 從授權伺服器取得金鑰
6. 載入金鑰到瀏覽器
7. 瀏覽器內部 CDM 解密播放

基本 EME 實作

javascript
const video = document.querySelector('video');

// 1. 請求 DRM 系統
navigator.requestMediaKeySystemAccess('com.widevine.alpha', [{
  initDataTypes: ['cenc'],
  videoCapabilities: [{
    contentType: 'video/mp4; codecs="avc1.42E01E"',
    robustness: 'SW_SECURE_CRYPTO'
  }],
  audioCapabilities: [{
    contentType: 'audio/mp4; codecs="mp4a.40.2"',
    robustness: 'SW_SECURE_CRYPTO'
  }]
}])
.then(keySystemAccess => {
  // 2. 建立 MediaKeys
  return keySystemAccess.createMediaKeys();
})
.then(mediaKeys => {
  // 3. 綁定到 video
  video.setMediaKeys(mediaKeys);
  
  // 4. 監聽加密事件
  video.addEventListener('encrypted', (e) => {
    const session = mediaKeys.createSession();
    
    // 5. 向授權伺服器請求金鑰
    session.addEventListener('message', (event) => {
      // event.message 是挑戰,送到授權伺服器
      fetch('https://license-server.com/license', {
        method: 'POST',
        body: event.message
      })
      .then(response => response.arrayBuffer())
      .then(response => {
        // 6. 載入金鑰
        session.update(response);
      });
    });
    
    // 生成請求
    session.generateRequest(e.initDataType, e.initData);
  });
});

三大 DRM 系統

DRM瀏覽器
WidevineChrome、Firefox、Edge
FairPlaySafari、iOS
PlayReadyEdge、IE

詳見 常見影片下載問題 的 DRM 部分。

WebCodecs API

概述

WebCodecs 是 2022 年標準化的瀏覽器編解碼 API,讓 JS 直接存取編碼器和解碼器。比 MSE 更底層。

WebCodecs 用途

  • 低延遲直播:自己處理編碼,不用走 RTMP
  • 影片剪輯:逐幀存取
  • 視訊會議:自訂編碼策略
  • 機器學習:直接存取幀資料

VideoDecoder 基本

javascript
const decoder = new VideoDecoder({
  output: (frame) => {
    // 處理解碼後的幀
    console.log('解碼幀:', frame.timestamp);
    frame.close();  // 釋放記憶體
  },
  error: (e) => console.error('解碼錯誤:', e)
});

// 設定解碼器
decoder.configure({
  codec: 'avc1.42001E',  // H.264 Baseline
  codedWidth: 1920,
  codedHeight: 1080
});

// 解碼壓縮資料
const chunk = new EncodedVideoChunk({
  type: 'key',  // 或 'delta'
  timestamp: 0,
  data: compressedData  // Uint8Array
});

decoder.decode(chunk);

VideoEncoder 基本

javascript
const encoder = new VideoEncoder({
  output: (chunk, metadata) => {
    // 處理編碼後的資料
    console.log('編碼 chunk:', chunk.type, chunk.timestamp);
  },
  error: (e) => console.error('編碼錯誤:', e)
});

// 設定編碼器
encoder.configure({
  codec: 'avc1.42001E',  // H.264 Baseline
  width: 1920,
  height: 1080,
  bitrate: 5_000_000,  // 5 Mbps
  framerate: 30
});

// 從 canvas 取得幀並編碼
const canvas = document.querySelector('canvas');
const frame = new VideoFrame(canvas, { timestamp: 0 });
encoder.encode(frame, { keyFrame: true });
frame.close();

WebCodecs 用於低延遲直播

javascript
// 從攝影機取得影像
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();

// 編碼器
const encoder = new VideoEncoder({
  output: (chunk) => {
    // 透過 WebRTC 或 WebSocket 發送
    sendChunk(chunk);
  },
  error: (e) => console.error(e)
});

encoder.configure({
  codec: 'avc1.42001E',
  width: 1280,
  height: 720,
  bitrate: 2_000_000,
  latencyMode: 'realtime'  // 低延遲模式
});

// 持續編碼
while (true) {
  const { done, value: frame } = await reader.read();
  if (done) break;
  encoder.encode(frame, { keyFrame: isKeyFrameTime() });
  frame.close();
}

瀏覽器編碼器支援

視訊編碼器

編碼器ChromeFirefoxSafariEdge
H.264
H.265部分部分部分
VP8部分
VP9部分
AV1部分

音訊編碼器

編碼器ChromeFirefoxSafariEdge
AAC
MP3
Opus部分
FLAC

詳見 H.264 vs H.265 vs AV1 編碼器比較

各技術使用場景

1. 基本影片播放

用 HTML5 video:

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

2. HLS/DASH 串流

用 MSE(透過 hls.js 或 dash.js):

javascript
// HLS
const hls = new Hls();
hls.loadSource('stream.m3u8');
hls.attachMedia(video);

// DASH
const player = dashjs.MediaPlayer().create();
player.initialize(video, 'stream.mpd', true);

詳見 MP4 轉 M3U8 教學

3. DRM 保護內容

用 EME:

javascript
// 請求 Widevine
navigator.requestMediaKeySystemAccess('com.widevine.alpha', config)
  .then(keySystemAccess => keySystemAccess.createMediaKeys())
  .then(mediaKeys => video.setMediaKeys(mediaKeys));

4. 低延遲直播

用 WebCodecs:

javascript
// 自己處理編碼,延遲可低於 500ms
const encoder = new VideoEncoder({ /* ... */ });
encoder.configure({ latencyMode: 'realtime', /* ... */ });

5. 視訊會議

用 WebRTC(內部用 WebCodecs):

javascript
// WebRTC 封裝了 WebCodecs
const pc = new RTCPeerConnection();
pc.addTrack(track);

常見問題

1. video 標籤播放 MP4 失敗

原因: 編碼器或容器不相容

解決方案: 用 H.264 視訊 + AAC 音訊 + yuv420p 像素格式

2. MSE appendBuffer 失敗

原因: 編碼器不相容或資料格式錯誤

解決方案: 確認 SourceBuffer 的 MIME 類型正確

3. EME 初始化失敗

原因: DRM 系統不支援或伺服器設定問題

解決方案: 確認瀏覽器支援對應 DRM 系統

4. WebCodecs 不可用

原因: 瀏覽器版本太舊

解決方案: 用最新版 Chrome 或 Firefox

總結

瀏覽器影片播放技術:

  • HTML5 video:基本播放
  • MSE:動態串流(HLS、DASH)
  • EME:DRM 保護
  • WebCodecs:低階編解碼

選擇指南:

  • 簡單播放: HTML5 video
  • 串流播放: MSE + hls.js/dash.js
  • DRM 內容: EME
  • 低延遲直播: WebCodecs
  • 視訊會議: WebRTC

快速參考:

  • MSE: new MediaSource() + addSourceBuffer()
  • EME: requestMediaKeySystemAccess() + MediaKeys
  • WebCodecs: new VideoDecoder() + new VideoEncoder()

參考資料

Last updated: