FlowPick 是怎麼在瀏覽器裡把幾百個影片分片合成 MP4 的
你點擊 FlowPick 的下載按鈕,等幾分鐘,一個完整的 MP4 出現在下載資料夾裡。
這件事有點反直覺。瀏覽器不是下載工具,更不是影片處理工具。傳統的串流媒體下載方案無一例外依賴本機安裝的程式,或者把資料發到伺服器處理再發回來。
FlowPick 全程在瀏覽器分頁內完成,沒有伺服器,沒有 App,沒有命令列。這篇文章解釋為什麼這在技術上是可行的,以及實際是怎麼做到的。
先搞清楚問題是什麼
HLS 影片在傳輸時不是一個完整的檔案。它是一個播放清單檔案(M3U8)加上幾百個 2-10 秒的分片(.ts 檔案)。播放器讀清單,按順序拉分片,即時拼接播放。
一個 30 分鐘的 1080P 影片,按 10 秒分片就是 180 個檔案。DASH 格式類似,只是分片是 .m4s,且音訊和影片在兩條獨立的串流裡傳輸。
「下載」這個影片意味著什麼?
- 解析 M3U8/MPD,拿到所有分片 URL
- 逐個下載幾百個分片
- 如果有 AES-128 加密,解密每個分片
- 把幾百個碎片拼成一個連續的檔案
- 如果是 HLS TS 分片,還要重封裝成 MP4(播放器友善的格式)
- 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-TS | MP4 | |
|---|---|---|
| 設計初衷 | 廣播傳輸,丟包容忍 | 檔案儲存,隨機存取 |
| 每個包 | 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 的處理:
- 解析 MPD XML,識別影片 AdaptationSet 和音訊 AdaptationSet
- 同時下載兩路分片(並行)
- 各自下載 init segment(包含編解碼器參數,合併時必須有)
- 把兩路資料送進 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 解密。沒有哪一個是「黑魔法」,都是現代瀏覽器標準能力的直接應用。
推薦閱讀
- 如何下載 M3U8/HLS 串流:完整入門指南 — 原理懂了,操作看這篇
- 程式碼開源了!三個月肝出 FlowPick 的完整歷程 — 這些原理是怎麼被逼出來的,開發故事版
- 什麼是 DASH 串流媒體?MPD 檔案解析 — DASH 的音影片分離設計為什麼要存在
- 如何下載 Bilibili 影片到本機 — B 站的 DASH 是上述技術的典型真實案例
- 如何批次下載網頁上的所有圖片 — 同一個擴充功能,圖片也能批次搞定
- FlowPick 與 Video DownloadHelper 對比 — 為什麼「不需要 CoApp」是個大事