[{"data":1,"prerenderedAt":1004},["ShallowReactive",2],{"blog-zh-Hant-hls-m3u8-deep-dive-encryption-multitrack":3,"blog-zh-Hant-hls-m3u8-deep-dive-encryption-multitrack-surround":991},{"id":4,"title":5,"author":6,"body":7,"category":972,"date":973,"description":974,"extension":975,"image":976,"lastModified":973,"meta":977,"navigation":978,"path":979,"readingTime":980,"seo":981,"stem":982,"tags":983,"__hash__":990},"blog_zh_Hant\u002Fzh-Hant\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack.md","HLS 深度：加密、多音軌、值得記住的 EXT-X 標籤","FlowPick 團隊",{"type":8,"value":9,"toc":956},"minimark",[10,28,31,36,39,49,52,58,65,70,76,104,126,130,133,139,146,373,379,394,418,424,428,442,445,471,480,495,503,506,512,515,523,526,545,549,627,638,642,648,671,674,697,700,704,707,727,737,740,762,767,773,784,802,808,811,849,852,875,894,897,900,952],[11,12,13,18,19,23,24,27],"p",{},[14,15,17],"a",{"href":16},"\u002Fzh-Hant\u002Fblog\u002Fgetting-started-with-hls-m3u8-download","HLS 入門指南"," 講了「什麼是 M3U8、怎麼下載」。這篇是另一篇——給那些試著下過一個流、拿到一個 200KB 的 ",[20,21,22],"code",{},".m3u8","、裡面引用了 8 條音軌、4 種字幕、還有一個讓 VLC 吐垃圾的 ",[20,25,26],{},"#EXT-X-KEY"," 標籤的人。",[11,29,30],{},"你開啟過 Netflix、Disney+ 或付費課程平台的真實 HLS 清單，納悶為什麼你的下載器下出來的是無聲影片或損壞檔案，原因就在這裡。",[32,33,35],"h2",{"id":34},"真實-hls-清單長什麼樣","真實 HLS 清單長什麼樣",[11,37,38],{},"多數教學用這個當例子：",[40,41,46],"pre",{"className":42,"code":44,"language":45},[43],"language-text","#EXTM3U\n#EXT-X-TARGETDURATION:6\n#EXT-X-VERSION:3\n#EXTINF:6.0,\nsegment0.ts\n#EXTINF:6.0,\nsegment1.ts\n#EXTINF:4.2,\nsegment2.ts\n#EXT-X-ENDLIST\n","text",[20,47,44],{"__ignoreMap":48},"",[11,50,51],{},"可愛。真實清單不長這樣。這是一個主流平台的真實 master playlist，精簡過但能代表全貌：",[40,53,56],{"className":54,"code":55,"language":45},[43],"#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-INDEPENDENT-SEGMENTS:yes\n\n# 音訊群組\n#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac-64\",NAME=\"English\",LANGUAGE=\"en\",DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\",URI=\"audio\u002Feng_64.m3u8\"\n#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac-128\",NAME=\"English\",LANGUAGE=\"en\",DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\",URI=\"audio\u002Feng_128.m3u8\"\n#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac-64\",NAME=\"Español\",LANGUAGE=\"es\",DEFAULT=NO,AUTOSELECT=YES,CHANNELS=\"2\",URI=\"audio\u002Fspa_64.m3u8\"\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"English\",LANGUAGE=\"en\",DEFAULT=NO,AUTOSELECT=YES,URI=\"subs\u002Fen.m3u8\"\n#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc\",NAME=\"CC\",LANGUAGE=\"en\",DEFAULT=NO,AUTOSELECT=YES,INSTREAM-ID=\"CC1\"\n\n# 影片變體\n#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=854x480,CODECS=\"avc1.64001f,mp4a.40.2\",AUDIO=\"aac-64\",SUBTITLES=\"subs\",CLOSED-CAPTIONS=\"cc\"\nv_480.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aac-128\",SUBTITLES=\"subs\",CLOSED-CAPTIONS=\"cc\"\nv_720.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080,CODECS=\"avc1.640028,mp4a.40.2\",AUDIO=\"aac-128\",SUBTITLES=\"subs\",CLOSED-CAPTIONS=\"cc\"\nv_1080.m3u8\n",[20,57,55],{"__ignoreMap":48},[11,59,60,61,64],{},"實際媒體分片根本不在這裡。master playlist 指向變體 playlist，每個變體再指向分片。音訊還單獨在自己的 playlist 裡。你的下載器要是只抓 ",[20,62,63],{},"v_1080.m3u8"," 然後拼接分片，得到的就是無聲影片。這是「HLS 下載沒聲音」回饋的頭號原因。",[66,67,69],"h3",{"id":68},"codecs-欄位不是裝飾","CODECS 欄位不是裝飾",[11,71,72,75],{},[20,73,74],{},"CODECS=\"avc1.640028,mp4a.40.2\""," 不是寫著好看：",[77,78,79,98],"ul",{},[80,81,82,85,86,89,90,93,94,97],"li",{},[20,83,84],{},"avc1.640028"," — H.264 High profile、level 4.0。",[20,87,88],{},"avc1."," 後面前兩個十六進位位編碼 profile（",[20,91,92],{},"64"," = High）、constraint flag、level（",[20,95,96],{},"28"," = 4.0）。",[80,99,100,103],{},[20,101,102],{},"mp4a.40.2"," — AAC-LC。",[11,105,106,107,109,110,113,114,117,118,121,122,125],{},"為什麼重要：某個變體宣告 ",[20,108,84],{},"，你卻把分片封裝進一個 codec box 寫錯的 MP4（",[20,111,112],{},"hvc1"," 而不是 ",[20,115,116],{},"avc1","），檔案不能播。",[20,119,120],{},".ts"," 分片裸拼接能避開這個問題，因為 MPEG-TS 自帶 codec 資訊；封裝進 MP4 就得解析這個欄位、寫對 ",[20,123,124],{},"stsd"," box。",[32,127,129],{"id":128},"ext-x-key加密以及直接解密不是一個句子","EXT-X-KEY：加密，以及「直接解密」不是一個句子",[11,131,132],{},"清單裡有這行：",[40,134,137],{"className":135,"code":136,"language":45},[43],"#EXT-X-KEY:METHOD=AES-128,URI=\"https:\u002F\u002Fcdn.example.com\u002Fkey.bin\",IV=0x1a2b3c4d5e6f7890abcdef1234567890\n",[20,138,136],{"__ignoreMap":48},[11,140,141,142,145],{},"意味著之後每個分片都用 ",[20,143,144],{},"URI"," 處的金鑰和指定的 IV 做 AES-128-CBC 加密。解密：",[40,147,151],{"className":148,"code":149,"language":150,"meta":48,"style":48},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const keyResp = await fetch(keyUri)\nconst keyBytes = await keyResp.arrayBuffer()  \u002F\u002F 16 位元組\nconst iv = new Uint8Array(16)\n\u002F\u002F IV 可能是字面值（如上），也可能從序號推導\nconst decrypted = await crypto.subtle.decrypt(\n  { name: 'AES-CBC', iv },\n  await crypto.subtle.importKey('raw', keyBytes, 'AES-CBC', false, ['decrypt']),\n  encryptedSegment\n)\n","js",[20,152,153,181,209,235,241,269,300,362,368],{"__ignoreMap":48},[154,155,158,162,166,170,174,178],"span",{"class":156,"line":157},"line",1,[154,159,161],{"class":160},"spNyl","const",[154,163,165],{"class":164},"sTEyZ"," keyResp ",[154,167,169],{"class":168},"sMK4o","=",[154,171,173],{"class":172},"s7zQu"," await",[154,175,177],{"class":176},"s2Zo4"," fetch",[154,179,180],{"class":164},"(keyUri)\n",[154,182,184,186,189,191,193,196,199,202,205],{"class":156,"line":183},2,[154,185,161],{"class":160},[154,187,188],{"class":164}," keyBytes ",[154,190,169],{"class":168},[154,192,173],{"class":172},[154,194,195],{"class":164}," keyResp",[154,197,198],{"class":168},".",[154,200,201],{"class":176},"arrayBuffer",[154,203,204],{"class":164},"()  ",[154,206,208],{"class":207},"sHwdD","\u002F\u002F 16 位元組\n",[154,210,212,214,217,219,222,225,228,232],{"class":156,"line":211},3,[154,213,161],{"class":160},[154,215,216],{"class":164}," iv ",[154,218,169],{"class":168},[154,220,221],{"class":168}," new",[154,223,224],{"class":176}," Uint8Array",[154,226,227],{"class":164},"(",[154,229,231],{"class":230},"sbssI","16",[154,233,234],{"class":164},")\n",[154,236,238],{"class":156,"line":237},4,[154,239,240],{"class":207},"\u002F\u002F IV 可能是字面值（如上），也可能從序號推導\n",[154,242,244,246,249,251,253,256,258,261,263,266],{"class":156,"line":243},5,[154,245,161],{"class":160},[154,247,248],{"class":164}," decrypted ",[154,250,169],{"class":168},[154,252,173],{"class":172},[154,254,255],{"class":164}," crypto",[154,257,198],{"class":168},[154,259,260],{"class":164},"subtle",[154,262,198],{"class":168},[154,264,265],{"class":176},"decrypt",[154,267,268],{"class":164},"(\n",[154,270,272,275,279,282,285,289,292,295,297],{"class":156,"line":271},6,[154,273,274],{"class":168},"  {",[154,276,278],{"class":277},"swJcz"," name",[154,280,281],{"class":168},":",[154,283,284],{"class":168}," '",[154,286,288],{"class":287},"sfazB","AES-CBC",[154,290,291],{"class":168},"'",[154,293,294],{"class":168},",",[154,296,216],{"class":164},[154,298,299],{"class":168},"},\n",[154,301,303,306,308,310,312,314,317,319,321,324,326,328,331,333,335,337,339,341,345,347,350,352,354,356,359],{"class":156,"line":302},7,[154,304,305],{"class":172},"  await",[154,307,255],{"class":164},[154,309,198],{"class":168},[154,311,260],{"class":164},[154,313,198],{"class":168},[154,315,316],{"class":176},"importKey",[154,318,227],{"class":164},[154,320,291],{"class":168},[154,322,323],{"class":287},"raw",[154,325,291],{"class":168},[154,327,294],{"class":168},[154,329,330],{"class":164}," keyBytes",[154,332,294],{"class":168},[154,334,284],{"class":168},[154,336,288],{"class":287},[154,338,291],{"class":168},[154,340,294],{"class":168},[154,342,344],{"class":343},"sfNiH"," false",[154,346,294],{"class":168},[154,348,349],{"class":164}," [",[154,351,291],{"class":168},[154,353,265],{"class":287},[154,355,291],{"class":168},[154,357,358],{"class":164},"])",[154,360,361],{"class":168},",\n",[154,363,365],{"class":156,"line":364},8,[154,366,367],{"class":164},"  encryptedSegment\n",[154,369,371],{"class":156,"line":370},9,[154,372,234],{"class":164},[11,374,375,378],{},[20,376,377],{},"crypto.subtle.decrypt"," 是瀏覽器裡的正確工具——不需要 WASM 寫 AES，原生 WebCrypto 又快又是 constant-time。",[11,380,381,382,389,390,393],{},"教學不會告訴你的是：",[383,384,385,388],"strong",{},[20,386,387],{},"METHOD=AES-128"," 是簡單情況。"," 大多數主流平台用 ",[20,391,392],{},"METHOD=SAMPLE-AES","，蘋果的 FairPlay DRM 鄰近方案。SAMPLE-AES：",[77,395,396,399,406],{},[80,397,398],{},"每個分片只有部分被加密（影片 NAL unit，音訊和 PES 頭不加密）",[80,400,401,402,405],{},"key URI 通常返回的是一個被 FairPlay license 交換包過的 ",[20,403,404],{},"EXT-X-KEY"," 回應——不是裸 16 位元組金鑰",[80,407,408,409,412,413,417],{},"瀏覽器分頁裡",[383,410,411],{},"無法","解密。這就是 DRM，「FlowPick 下不了這個 Netflix 影片」是特性不是 bug。我們在 ",[14,414,416],{"href":415},"\u002Fzh-Hant\u002Fblog\u002Fis-it-legal-to-download-streaming-video","串流媒體下載合法性指南"," 裡講了這條法律和技術底線。",[11,419,420,423],{},[383,421,422],{},"我的看法："," 哪個下載器聲稱能處理 FairPlay、Widevine 或 PlayReady，要嘛在撒謊，要嘛在大多數司法管轄區違法。FlowPick 明確拒絕碰 SAMPLE-AES 內容。正確行為是大聲報錯，不是靜默產出損壞檔案。",[32,425,427],{"id":426},"ext-x-media音訊和字幕軌道","EXT-X-MEDIA：音訊和字幕軌道",[11,429,430,433,434,437,438,441],{},[20,431,432],{},"EXT-X-MEDIA"," 標籤是 HLS 多音軌的機制。每個 ",[20,435,436],{},"GROUP-ID"," 是一組可互換的音訊版本；變體 playlist 的 ",[20,439,440],{},"AUDIO"," 屬性指向它用的那組。",[11,443,444],{},"對下載器意味著：一次「完整下載」不只是影片分片。是：",[446,447,448,451,461,468],"ol",{},[80,449,450],{},"選定變體 playlist 的影片分片",[80,452,453,454,456,457,460],{},"一條 ",[20,455,432],{}," 條目的音訊分片（通常是 ",[20,458,459],{},"DEFAULT=YES"," 那條，但應該讓使用者選）",[80,462,463,464,467],{},"可選的，",[20,465,466],{},"TYPE=SUBTITLES"," 的字幕分片",[80,469,470],{},"把它們封裝進一個 MP4，寫正確的軌道元資料",[11,472,473,474,476,477,479],{},"封裝這一步最容易翻車。來自不同 playlist 的 MPEG-TS 分片各有自己的 PTS 時間線；把只有影片的 ",[20,475,120],{}," 和只有音訊的 ",[20,478,120],{}," 拼進一個 MP4，你得：",[77,481,482,485,488],{},[80,483,484],{},"從每個 TS 封包解析 PES 頭抽 PTS",[80,486,487],{},"按 PTS 排序封包",[80,489,490,491,494],{},"把它們寫進 MP4，每個軌道的 ",[20,492,493],{},"mdia\u002Fminf\u002Fstbl"," 結構要正確",[11,496,497,498,502],{},"這是 FlowPick 的 WebAssembly FFmpeg 幹的活——見 ",[14,499,501],{"href":500},"\u002Fzh-Hant\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","瀏覽器內合併深度篇","。重點：「拼檔案」在出現獨立音軌那一刻就不靈了。",[66,504,505],{"id":505},"不連續性標籤",[40,507,510],{"className":508,"code":509,"language":45},[43],"#EXT-X-DISCONTINUITY\n",[20,511,509],{"__ignoreMap":48},[11,513,514],{},"意思是「時間戳在這之後重設」。常見於：",[77,516,517,520],{},[80,518,519],{},"帶廣告位的直播（廣告有自己的時間線）",[80,521,522],{},"不同編碼器拼接的清單",[11,524,525],{},"無腦拼接分片的下載器，在不連續點會讓播放器時間倒流。要嘛：",[446,527,528,531,534],{},[80,529,530],{},"用會處理不連續性的重封裝器（FFmpeg 行）",[80,532,533],{},"把不連續性 strip 掉，接受時間戳漂移",[80,535,536,537,539,540,544],{},"直接跳過廣告分片（FlowPick 預設這樣做，反正廣告多是 2 秒的 ",[20,538,120],{}," 污染流——見 ",[14,541,543],{"href":542},"\u002Fzh-Hant\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","v1.0.0 發布說明"," 裡的尺寸過濾器）",[32,546,548],{"id":547},"ext-x-version值得記住的版本標籤","EXT-X-VERSION：值得記住的版本標籤",[550,551,552,568],"table",{},[553,554,555],"thead",{},[556,557,558,562,565],"tr",{},[559,560,561],"th",{},"版本",[559,563,564],{},"加了什麼",[559,566,567],{},"為什麼你在意",[569,570,571,583,594,605,616],"tbody",{},[556,572,573,577,580],{},[574,575,576],"td",{},"2",[574,578,579],{},"I-frame playlist",[574,581,582],{},"Trick mode（快進\u002F倒退）支援",[556,584,585,588,591],{},[574,586,587],{},"3",[574,589,590],{},"浮點 EXTINF",[574,592,593],{},"低延遲流的次秒級時長",[556,595,596,599,602],{},[574,597,598],{},"4",[574,600,601],{},"獨立音訊\u002F字幕的 EXT-X-MEDIA",[574,603,604],{},"多音軌——見上",[556,606,607,610,613],{},[574,608,609],{},"5",[574,611,612],{},"EXT-X-KEY 的 IV 可選",[574,614,615],{},"金鑰能從序號推導 IV",[556,617,618,621,624],{},[574,619,620],{},"6",[574,622,623],{},"EXT-X-MAP",[574,625,626],{},"fMP4 的 init 分片——見下一節",[11,628,629,630,633,634,637],{},"看到 ",[20,631,632],{},"#EXT-X-VERSION:6"," 而變體 playlist 裡沒有 ",[20,635,636],{},"#EXT-X-MAP","，哪裡怪怪的。fMP4 HLS 必須有 init 分片；沒有它，分片不能播。",[32,639,641],{"id":640},"ext-x-map-與-fragmented-mp4","EXT-X-MAP 與 fragmented MP4",[40,643,646],{"className":644,"code":645,"language":45},[43],"#EXT-X-MAP:URI=\"init.mp4\"\n",[20,647,645],{"__ignoreMap":48},[11,649,650,651,654,655,658,659,662,663,666,667,670],{},"意思是分片不是 MPEG-TS——是 fragmented MP4（fMP4），",[20,652,653],{},"init.mp4"," 含解讀它們所需的 ",[20,656,657],{},"ftyp"," 和 ",[20,660,661],{},"moov"," box。沒有 init 分片，你手裡就是裸 ",[20,664,665],{},"moof","\u002F",[20,668,669],{},"mdat"," box，沒有 codec 資訊。",[11,672,673],{},"合併 fMP4 HLS：",[446,675,676,682,690],{},[80,677,678,679,681],{},"拉 ",[20,680,653],{},"——這是輸出檔案的檔案頭",[80,683,684,685,666,687,689],{},"按序號順序拼接分片的 ",[20,686,665],{},[20,688,669],{}," 對",[80,691,692,693,696],{},"想要 seek 索引的話可選重寫 ",[20,694,695],{},"sidx"," box",[11,698,699],{},"這比 TS 重封裝簡單得多，因為 fMP4 就是為拼接設計的。蘋果的 LL-HLS 現在幾乎全用 fMP4。",[32,701,703],{"id":702},"low-latency-hlsll-hls","Low-Latency HLS（LL-HLS）",[11,705,706],{},"蘋果 2019 年的規範擴展加了：",[77,708,709,715,721],{},[80,710,711,714],{},[20,712,713],{},"EXT-X-PART"," — 部分分片（通常 200ms 而不是 6s）",[80,716,717,720],{},[20,718,719],{},"EXT-X-PRELOAD-HINT"," — 告訴客戶端開始抓還不存在的分片",[80,722,723,726],{},[20,724,725],{},"EXT-X-RENDITION-REPORT"," — 讓變體 playlist 直接互相引用",[11,728,729,730,733,734,736],{},"對下載器來說，LL-HLS 主要意味著「更多請求、更小檔案」。合併邏輯跟普通 HLS 一樣——只是除了 ",[20,731,732],{},"EXTINF"," 還要處理 ",[20,735,713],{},"。FlowPick 處理得了；你自己撸的下載器不處理，就會漏掉部分分片。",[32,738,739],{"id":739},"我真見過的坑",[11,741,742,745,746,749,750,753,754,757,758,761],{},[383,743,744],{},"坑 1：相對 URI。"," M3U8 大量用相對 URI。master playlist 在 ",[20,747,748],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmaster.m3u8","，裡面寫 ",[20,751,752],{},"v_720.m3u8","，變體就是 ",[20,755,756],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fv_720.m3u8","。但 master 在 ",[20,759,760],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmaster.m3u8?token=abc","，token 不會自動傳過去。query string 繼承要手動處理。",[11,763,764],{},[383,765,766],{},"坑 2：位元組範圍定址。",[40,768,771],{"className":769,"code":770,"language":45},[43],"#EXT-X-BYTERANGE:522752@1024\n",[20,772,770],{"__ignoreMap":48},[11,774,775,776,779,780,783],{},"意思是「分片是大檔案裡的一個位元組範圍」。不帶 ",[20,777,778],{},"Range"," 頭去 ",[20,781,782],{},"fetch"," 這個 URI，你會拿到整個檔案。很多 CDN 託管的 HLS playlist 對封存內容這麼做——不是幾千個小檔案，而是一個大檔案加位元組範圍。",[11,785,786,789,790,793,794,797,798,801],{},[383,787,788],{},"坑 3：按 CODECS 選變體。"," master playlist 同時有 ",[20,791,792],{},"avc1.*","（H.264）和 ",[20,795,796],{},"hvc1.*","（HEVC）變體，你的播放器可能選 HEVC。你的下載器只抓第一個變體，可能拿到 HEVC 而你想要的是 H.264。一定讓使用者選，並解析 ",[20,799,800],{},"CODECS"," 給變體打標籤。",[11,803,804,807],{},[383,805,806],{},"坑 4：token 過期。"," URI 裡的直播 token 通常幾分鐘過期。一小時前抓的清單 URL 現在去下，全是 403。FlowPick 在下載時重拉清單，這就是「先抓 URL 後下」在直播內容上經常失敗的原因。",[32,809,810],{"id":810},"參考資料",[77,812,813,822,830,841],{},[80,814,815,821],{},[14,816,820],{"href":817,"rel":818},"https:\u002F\u002Fdatatracker.ietf.org\u002Fdoc\u002Fhtml\u002Frfc8216",[819],"nofollow","RFC 8216 — HTTP Live Streaming, 2nd Edition"," — 正式規範，107 頁",[80,823,824,829],{},[14,825,828],{"href":826,"rel":827},"https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fhttp_live_streaming\u002Fhls_authoring_specification_for_apple_devices",[819],"Apple HLS 創作規範"," — 蘋果 App Store 上架要求",[80,831,832,837,838,840],{},[14,833,836],{"href":834,"rel":835},"https:\u002F\u002Fffmpeg.org\u002Fffmpeg-formats.html#mpegts",[819],"FFmpeg MPEG-TS 格式文件"," — 理解 ",[20,839,120],{}," 檔案裡到底有什麼",[80,842,843,848],{},[14,844,847],{"href":845,"rel":846},"https:\u002F\u002Fmp4ra.org\u002F",[819],"MP4RA — MP4 註冊機構"," — CODECS 屬性的 codec 到 FourCC 對應",[32,850,851],{"id":851},"小結",[11,853,854,855,857,858,857,860,857,862,857,865,868,869,874],{},"M3U8 入門敘事——「它是個 playlist，抓分片，拼起來」——覆蓋大概 60% 的真實流。剩下 40% 涉及多音軌、加密、fMP4 init 分片、不連續性、位元組範圍、過期 token。一個穩健的 HLS 下載器得正確解析 ",[20,856,432],{},"、",[20,859,404],{},[20,861,623],{},[20,863,864],{},"EXT-X-BYTERANGE",[20,866,867],{},"EXT-X-DISCONTINUITY","。「正確」長什麼樣，看 ",[14,870,873],{"href":871,"rel":872},"https:\u002F\u002Fgithub.com\u002Fezwebtools\u002Fflowpick",[819],"FlowPick 原始碼","。",[11,876,877,878,882,883,857,886,889,890,893],{},"下一篇是 ",[14,879,881],{"href":880},"\u002Fzh-Hant\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection","DASH\u002FMPD 深度篇","，講 MPEG-DASH 對應的機制——",[20,884,885],{},"SegmentTemplate",[20,887,888],{},"$Number$"," 替換、",[20,891,892],{},"ContentProtection","，以及為什麼 DASH 結構上比 HLS 複雜、實操上卻更好下。",[895,896],"hr",{},[32,898,899],{"id":899},"推薦閱讀",[77,901,902,910,918,926,934,943],{},[80,903,904,909],{},[383,905,906],{},[14,907,908],{"href":16},"如何下載 M3U8\u002FHLS 流：完整入門指南"," — 這篇 60% 的簡單情況，那篇做基礎",[80,911,912,917],{},[383,913,914],{},[14,915,916],{"href":500},"FlowPick 是怎麼在瀏覽器裡把幾百個影片分片合成 MP4 的"," — FlowPick 怎麼處理多音軌+影片的封裝",[80,919,920,925],{},[383,921,922],{},[14,923,924],{"href":880},"DASH 深度：SegmentTemplate、ContentProtection 與多視角 MPD"," — 這篇的 DASH 對應物",[80,927,928,933],{},[383,929,930],{},[14,931,932],{"href":415},"串流媒體下載到底合不合法？一份技術和法律清單"," — 為什麼 FlowPick 拒絕 SAMPLE-AES \u002F DRM 內容",[80,935,936,942],{},[383,937,938],{},[14,939,941],{"href":940},"\u002Fzh-Hant\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","瀏覽器端影片重封裝：fMP4、ISOBMFF 和不轉碼的理由"," — 容器格式深度篇",[80,944,945,951],{},[383,946,947],{},[14,948,950],{"href":949},"\u002Fzh-Hant\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS：瀏覽器影片處理實戰"," — FFmpeg WASM 不合適時的更底層瀏覽器 API",[953,954,955],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":48,"searchDepth":183,"depth":183,"links":957},[958,961,962,965,966,967,968,969,970,971],{"id":34,"depth":183,"text":35,"children":959},[960],{"id":68,"depth":211,"text":69},{"id":128,"depth":183,"text":129},{"id":426,"depth":183,"text":427,"children":963},[964],{"id":505,"depth":211,"text":505},{"id":547,"depth":183,"text":548},{"id":640,"depth":183,"text":641},{"id":702,"depth":183,"text":703},{"id":739,"depth":183,"text":739},{"id":810,"depth":183,"text":810},{"id":851,"depth":183,"text":851},{"id":899,"depth":183,"text":899},"tips","2026-08-04","M3U8 入門之後的進階——EXT-X-KEY、EXT-X-MEDIA、不連續性、以及那些能讓樸素 HLS 下載器翻車的邊界情況。含真實清單片段和 FlowPick 的處理邏輯。","md","\u002Fscreenshots\u002Fhls-detection.png",{},true,"\u002Fzh-hant\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack",16,{"title":5,"description":974},"zh-Hant\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack",[984,985,986,987,988,989],"hls","m3u8","深度","加密","多音軌","串流","N-EO-IiPFxBpXJBSKSM3-MqyEJ3OK450vaemSLeqaoA",[992,999],{"title":993,"path":994,"stem":995,"description":996,"date":997,"category":998,"children":-1},"如何下載 M3U8\u002FHLS 串流媒體：完整入門指南","\u002Fzh-hant\u002Fblog\u002Fgetting-started-with-hls-m3u8-download","zh-Hant\u002Fblog\u002Fgetting-started-with-hls-m3u8-download","學習如何在瀏覽器中下載 M3U8 與 HLS 串流，找出播放清單網址、選擇 MP4 或 TS、處理跨域與分片問題，並使用 FlowPick 儲存至本機。","2026-07-01","tutorials",{"title":916,"path":1000,"stem":1001,"description":1002,"date":1003,"category":972,"children":-1},"\u002Fzh-hant\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","zh-Hant\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","不靠伺服器、不裝 FFmpeg、不轉碼——拆解 FlowPick 在瀏覽器分頁內完成 HLS\u002FDASH 分片合併的完整技術路徑。","2026-07-02",1787670590178]