tips

FlowPick 是怎麼在瀏覽器裡把幾百個影片分片合成 MP4 的

不靠伺服器、不裝 FFmpeg、不轉碼——拆解 FlowPick 在瀏覽器分頁內完成 HLS/DASH 分片合併的完整技術路徑。
FlowPick 團隊
13 分鐘閱讀
# 技術 # ffmpeg # wasm # hls # dash # 架構

你點擊 FlowPick 的下載按鈕,等幾分鐘,一個完整的 MP4 出現在下載資料夾裡。

這件事有點反直覺。瀏覽器不是下載工具,更不是影片處理工具。傳統的串流媒體下載方案無一例外依賴本機安裝的程式,或者把資料發到伺服器處理再發回來。

FlowPick 全程在瀏覽器分頁內完成,沒有伺服器,沒有 App,沒有命令列。這篇文章解釋為什麼這在技術上是可行的,以及實際是怎麼做到的。

先搞清楚問題是什麼

HLS 影片在傳輸時不是一個完整的檔案。它是一個播放清單檔案(M3U8)加上幾百個 2-10 秒的分片.ts 檔案)。播放器讀清單,按順序拉分片,即時拼接播放。

一個 30 分鐘的 1080P 影片,按 10 秒分片就是 180 個檔案。DASH 格式類似,只是分片是 .m4s,且音訊和影片在兩條獨立的串流裡傳輸。

「下載」這個影片意味著什麼?

  1. 解析 M3U8/MPD,拿到所有分片 URL
  2. 逐個下載幾百個分片
  3. 如果有 AES-128 加密,解密每個分片
  4. 把幾百個碎片拼成一個連續的檔案
  5. 如果是 HLS TS 分片,還要重封裝成 MP4(播放器友善的格式)
  6. DASH 的話,還要把影片軌和音訊軌合併

這不是簡單的檔案下載,是一條處理管線。傳統工具把這些交給 FFmpeg 命令列或伺服器,FlowPick 把整條管線搬進了瀏覽器。

偵測是第一步:webRequest API

在處理分片之前,FlowPick 需要知道頁面上有什麼影片。

瀏覽器擴充功能可以透過 webRequest API 在網路層監聽所有請求——不是 DOM 層,而是實際的 HTTP 請求層。每一個從當前分頁發出的網路請求,FlowPick 都能看到請求 URL 和回應標頭。

FlowPick 掃描 Content-Type 和 URL 特徵,把命中 HLS/DASH/直連影片/音訊/圖片特徵的請求記錄下來。當你打開 FlowPick 彈窗,看到的那個列表就是它截至此刻截獲的所有媒體資源。

這也解釋了為什麼影片沒開始播放,FlowPick 就看不到串流——M3U8 請求只在播放器初始化時發出,FlowPick 只能看到已經發生的請求,不能主動去探測。

核心難題:把 TS 分片變成 MP4

影片分片下載完,格式問題來了。

HLS 的分片是 MPEG-TS 格式(.ts)。直接把 180 個 .ts 檔案頭尾相接二進位拼起來,你會得到一個 .ts 大檔案——這個檔案部分播放器能播,但大多數裝置、影片編輯軟體、影片平台偏好 MP4。

TS 和 MP4 的核心區別是容器格式

MPEG-TSMP4
設計初衷廣播傳輸,丟包容忍檔案儲存,隨機存取
每個包188 位元組固定大小可變長 Box 結構
索引位置無獨立索引,只能順序讀moov atom,可放檔案頭
隨機跳轉麻煩直接用 moov 索引跳

把 TS 變成 MP4,本質上是提取裡面的影片和音訊裸資料,重新裝進 MP4 的 Box 結構。這個操作叫 remux(重封裝),不碰編解碼器,影片和音訊的每一個位元都原封不動,只是換了個「殼」。

重封裝不需要顯示卡,CPU 壓力也很小,主要時間花在檔案 I/O 上。這是 FlowPick 能在瀏覽器裡做它的技術前提——如果需要重新編碼(transcoding),瀏覽器的 CPU 算力根本應付不來。

兩套引擎的選擇邏輯

FlowPick 內部有兩套重封裝引擎,根據場景自動切換:

引擎一:FFmpeg WASM

FFmpeg 是行業標準的影片處理命令列工具,支援幾乎所有格式。WebAssembly(WASM) 是一種可以在瀏覽器裡執行接近原生速度的二進位格式。把 FFmpeg 編譯成 WASM,就能在瀏覽器分頁內呼叫 FFmpeg 的全部功能。

FlowPick 呼叫 FFmpeg WASM 時,實際執行的命令大概是這樣:

ffmpeg -f concat -safe 0 -i filelist.txt \
  -c copy \
  -movflags +faststart \
  -bsf:a aac_adtstoasc \
  output.mp4

參數分解:

參數作用
-f concat用拼接解多工器處理多個 TS 片段
-c copy串流複製,零重編碼
-movflags +faststart把索引移到檔案頭,支援邊下邊看
-bsf:a aac_adtstoasc轉換 AAC 封裝格式(TS 用 ADTS,MP4 用 ASC)

-c copy 是關鍵——它確保 FlowPick 做的是純重封裝,沒有任何品質損失,也不需要高 CPU 算力。

WASM 的 FFmpeg 跑在瀏覽器的 JavaScript 引擎裡,有效能損耗,但重封裝是 I/O 密集型任務,不是 CPU 密集型,所以實際表現不差。根據實測資料(Chrome + Intel i7-13700 + 16GB RAM):

影片分片數檔案大小FFmpeg WASM 重封裝耗時
10 分鐘 1080P~60~200 MB~3 秒
30 分鐘 1080P~180~600 MB~8 秒
60 分鐘 4K~360~2.5 GB~30 秒
120 分鐘 4K~720~5 GB~60 秒

這個速度對日常使用已經夠用了。

FFmpeg WASM 的限制: WASM 執行在 32 位元位址空間,理論記憶體上限 4GB。在實際場景下,FFmpeg WASM 需要把所有分片寫入虛擬檔案系統(WASM 記憶體),大檔案場景會吃掉大量記憶體,超過限制就會崩潰。

引擎二:TSToMP4Muxer

這是 FlowPick 自研的一套純 JavaScript 串流式重封裝器,專門處理 TS → MP4 這一個場景,但做法完全不同。

FFmpeg WASM 的模式是:先把所有分片集齊,再統一處理。TSToMP4Muxer 的模式是:邊下載邊處理邊寫盤

具體來說,TSToMP4Muxer 在分片下載回來的同時,即時解析 TS 資料包(每個包固定 188 位元組),提取出 PES(Packetized Elementary Stream)資料,即時轉換為 MP4 Box 格式,透過瀏覽器的 Streams API 持續寫到磁碟。

好處顯而易見:

  • 記憶體佔用與檔案大小無關 — 處理 5GB 影片和處理 200MB 影片記憶體佔用差不多
  • 不需要載入 FFmpeg WASM(30MB+ 的 WASM 檔案),啟動更快
  • 天然支援大檔案 — 記憶體裡同時只存一小段資料

代價是覆蓋場景更窄:只做單 TS → MP4,無法處理 DASH 音影片合併這類需要兩路輸入的任務。

FlowPick 的切換邏輯是:普通 HLS 優先走 TSToMP4Muxer;遇到 DASH 或者需要合併兩路串流的情況,走 FFmpeg WASM。檔案超過 2GB,也會優先使用 TSToMP4Muxer 或者直接輸出 TS 跳過重封裝,避免 WASM 記憶體溢位。

檔案寫到哪裡去:File System Access API

處理好的影片資料往哪放?在傳統下載工具裡這不是問題,但瀏覽器 JavaScript 預設沒有直接寫檔案系統的權限。

FlowPick 使用 Chrome 和 Edge 提供的 File System Access API(FSA)。使用者選擇儲存路徑後,FSA 提供一個可寫的檔案控制代碼,允許 JavaScript 以串流方式持續寫入,而不是先在記憶體裡湊齊所有資料再一次性寫出。

這意味著:哪怕是 5GB 的 4K 影片,記憶體裡同時只存當前正在處理的那一小塊資料。FlowPick 理論上沒有檔案大小上限(受限的是磁碟空間和 WASM 記憶體上限,後者透過 TSToMP4Muxer 繞開)。

Firefox 也支援,但走的是 Blob 方案,記憶體行為和 Chrome FSA 略有差異,超大檔案時穩定性稍弱。

AES-128 解密:Web Crypto API

部分 HLS 串流會給每個分片加密,M3U8 裡有這樣一行:

#EXT-X-KEY:METHOD=AES-128,URI="https://api.example.com/key?token=..."

FlowPick 偵測到這個標籤後,先向 Key URI 發請求拿金鑰(帶上你當前的 Cookie 和工作階段,因為是從瀏覽器擴充功能裡發的請求,就是你瀏覽器的真實工作階段)。拿到金鑰後,對每個下載完的分片,用瀏覽器內建的 Web Crypto API 做 AES-128-CBC 解密,再把解密後的資料送進重封裝流程。

整個金鑰獲取和解密過程完全在瀏覽器本機發生,金鑰不會經過 FlowPick 的任何伺服器。

FlowPick 處理不了的: Widevine、PlayReady、FairPlay 等 DRM 體系。這類內容保護把解密金鑰綁定在專用硬體或作業系統模組裡,JavaScript 存取不到,這是技術限制,不是功能取捨。

DASH:音影片分開下載再合併

DASH 比 HLS 多一層複雜度——音訊和影片是兩條獨立的串流,分別分片,分別傳輸。你下載的不是一堆完整影片分片,而是一堆純影片分片(沒有聲音)加上一堆純音訊分片(沒有畫面)。

FlowPick 對 DASH 的處理:

  1. 解析 MPD XML,識別影片 AdaptationSet 和音訊 AdaptationSet
  2. 同時下載兩路分片(並行)
  3. 各自下載 init segment(包含編解碼器參數,合併時必須有)
  4. 把兩路資料送進 FFmpeg WASM,執行音影片合併

合併命令大概是:

ffmpeg \
  -f concat -safe 0 -i video_list.txt \
  -f concat -safe 0 -i audio_list.txt \
  -c:v copy -c:a copy \
  -movflags +faststart \
  output.mp4

同樣是 -c copy,零重編碼。主要時間花在並行下載兩路分片和最後的合併 I/O 上。

並行下載:分片不用一個個來

幾百個分片如果序列下載,速度完全由單個 HTTP 請求的延遲決定。FlowPick 使用多執行緒並行下載,預設 2 個並行(保守值,避免觸發伺服器端限速),設定裡可以調到 4-6 個。

幾個分片並行下載,哪個先完成就先處理哪個,但寫入檔案時要按序號重排,確保拼接順序正確。FlowPick 內部維護一個下載佇列和有序寫入緩衝區處理這個細節。

下載失敗的分片會自動重試(預設 3 次)。分片粒度小,單個失敗重試代價很小,不需要從頭來。

輸出格式的實際差異

FlowPick 支援兩種輸出:

MP4:重封裝後的標準 MP4,moov atom 放在檔案頭(faststart),相容性最好,適合長期儲存、上傳、影片編輯。代價是需要重封裝這一步,大影片要多等幾十秒。

TS:把所有分片直接二進位拼接,零 CPU 開銷,幾乎瞬間完成。但檔案比 MP4 大 5-15%(TS 容器本身的開銷),且部分裝置和平台對 TS 支援不如 MP4 好。

如果你下完準備傳 B 站或者匯入剪映,選 MP4。如果你只是暫存一下或者打算繼續用 FFmpeg 命令列處理,選 TS,省時間。

在瀏覽器裡做這件事有什麼代價

說完能做什麼,也說清楚做不到什麼:

沒有重編碼。 FlowPick 只換容器,不碰編解碼器。H.265 的影片出來還是 H.265,老播放器不支援 H.265 就還是放不了,FlowPick 幫不了這個。

FFmpeg WASM 記憶體有上限。 4GB 的 32 位元 WASM 位址空間,加上瀏覽器自身的開銷,實際上能用的比 4GB 少。2GB 以上的檔案自動切換策略(TSToMP4Muxer 或直接輸出 TS)就是為了繞開這個限制。

WASM 比原生 FFmpeg 慢。 WASM 不是原生程式碼,有效能損耗。60 分鐘 4K 影片 FFmpeg WASM 需要約 30 秒完成重封裝,原生 FFmpeg 可能在 5 秒內搞定。對於日常使用,30 秒可以接受;如果你每天處理大量內容,原生 FFmpeg 命令列更合適。

沒有硬體加速。 FFmpeg 原生版本可以呼叫 NVENC/QSV/VideoToolbox 做 GPU 加速編解碼,WASM 版本沒有這個能力。不過 -c copy 不涉及編解碼,所以這個限制在 FlowPick 的使用場景裡影響不大。

DRM 繞不開。 這是硬限制,不是技術取捨。Widevine 的設計目標就是讓瀏覽器擴充功能存取不到金鑰。


整體來看,FlowPick 能在瀏覽器裡完整處理 HLS/DASH 下載,靠的是幾個關鍵技術點的組合:WebAssembly 讓 FFmpeg 能在瀏覽器裡跑,-c copy 把處理成本壓到只有 I/O,TSToMP4Muxer 解決大檔案記憶體問題,File System Access API 解決大檔案寫盤問題,Web Crypto API 處理 AES-128 解密。沒有哪一個是「黑魔法」,都是現代瀏覽器標準能力的直接應用。


推薦閱讀