[{"data":1,"prerenderedAt":1001},["ShallowReactive",2],{"blog-ja-hls-m3u8-deep-dive-encryption-multitrack":3,"blog-ja-hls-m3u8-deep-dive-encryption-multitrack-surround":989},{"id":4,"title":5,"author":6,"body":7,"category":970,"date":971,"description":972,"extension":973,"image":974,"lastModified":971,"meta":975,"navigation":976,"path":977,"readingTime":978,"seo":979,"stem":980,"tags":981,"__hash__":988},"blog_ja\u002Fja\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack.md","HLS ディープ：暗号化、マルチトラック、覚えておくべき EXT-X タグ","FlowPick チーム",{"type":8,"value":9,"toc":954},"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,637,641,647,670,673,695,698,702,705,725,735,738,760,765,771,782,800,806,809,847,850,873,892,895,898,950],[11,12,13,18,19,23,24,27],"p",{},[14,15,17],"a",{"href":16},"\u002Fja\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."," の後の最初の 2 桁の 16 進数が 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],{},"avc1"," ではなく ",[20,115,116],{},"hvc1","）にマルチプレクスすると、ファイルは再生できない。",[20,119,120],{},".ts"," セグメントの生の結合はこの問題を回避できる——MPEG-TS は自己記述的だから。MP4 にマルチプレクスするなら、このフィールドを解析して ",[20,123,124],{},"stsd"," ボックスを正しく書く必要がある。",[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"," がブラウザでの正しいツール——AES を WASM で書く必要はなく、ネイティブ 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"," を使う——Apple の FairPlay DRM に近いもの。SAMPLE-AES は：",[77,395,396,399,406],{},[80,397,398],{},"各セグメントの一部だけが暗号化（映像 NAL unit、音声と PES ヘッダーは暗号化なし）",[80,400,401,402,405],{},"key URI は通常、生の 16 バイト鍵ではなく、FairPlay license 交換で包まれた ",[20,403,404],{},"EXT-X-KEY"," レスポンスを返す",[80,407,408,409,412,413,417],{},"ブラウザタブでは",[383,410,411],{},"復号できない","。これが DRM であり、「FlowPick がこの Netflix 動画をダウンロードできない」のはバグではなく仕様。",[14,414,416],{"href":415},"\u002Fja\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],{},"1 つの ",[20,455,432],{}," エントリの音声セグメント（通常は ",[20,458,459],{},"DEFAULT=YES"," のもの、ただしユーザーに選ばせるべき）",[80,462,463,464,467],{},"オプションで ",[20,465,466],{},"TYPE=SUBTITLES"," の字幕セグメント",[80,469,470],{},"これらを 1 つの MP4 にマルチプレクスし、正しいトラックメタデータを書く",[11,472,473,474,476,477,479],{},"マルチプレクスが一番失敗しやすい。異なる playlist の MPEG-TS セグメントはそれぞれ独自の PTS タイムラインを持つ。映像だけの ",[20,475,120],{}," と音声だけの ",[20,478,120],{}," を 1 つの MP4 にまとめるには：",[77,481,482,485,488],{},[80,483,484],{},"各 TS パケットの PES ヘッダーを解析して PTS を抽出",[80,486,487],{},"PTS でパケットをソート",[80,489,490,491,494],{},"各トラックの ",[20,492,493],{},"mdia\u002Fminf\u002Fstbl"," 構造を正しく書く",[11,496,497,498,502],{},"これが FlowPick の WebAssembly FFmpeg の仕事——",[14,499,501],{"href":500},"\u002Fja\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 は OK）",[80,532,533],{},"不連続性を strip し、タイムスタンプのドリフトを受け入れる",[80,535,536,537,539,540,544],{},"広告セグメントを完全にスキップ（FlowPick のデフォルト——広告はたいてい 2 秒の ",[20,538,120],{}," セグメントでストリームを汚すだけなので、",[14,541,543],{"href":542},"\u002Fja\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],{},"トリックモード（早送り\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,632,633,636],{},[20,630,631],{},"#EXT-X-VERSION:6"," なのにバリアント playlist に ",[20,634,635],{},"#EXT-X-MAP"," がない場合、何か変。fMP4 HLS には init セグメントが必須。それがないとセグメントは再生できない。",[32,638,640],{"id":639},"ext-x-map-と-fragmented-mp4","EXT-X-MAP と fragmented MP4",[40,642,645],{"className":643,"code":644,"language":45},[43],"#EXT-X-MAP:URI=\"init.mp4\"\n",[20,646,644],{"__ignoreMap":48},[11,648,649,650,653,654,657,658,661,662,665,666,669],{},"意味：セグメントは MPEG-TS ではなく fragmented MP4（fMP4）。",[20,651,652],{},"init.mp4"," が解釈に必要な ",[20,655,656],{},"ftyp"," と ",[20,659,660],{},"moov"," ボックスを含む。init セグメントがないと、手元にあるのは生の ",[20,663,664],{},"moof","\u002F",[20,667,668],{},"mdat"," ボックスだけで、codec 情報がない。",[11,671,672],{},"fMP4 HLS をマージ：",[446,674,675,680,688],{},[80,676,677,679],{},[20,678,652],{}," を取得——これが出力ファイルのヘッダー",[80,681,682,683,665,685,687],{},"セグメントの ",[20,684,664],{},[20,686,668],{}," ペアを番号順に結合",[80,689,690,691,694],{},"シークインデックスが必要なら ",[20,692,693],{},"sidx"," ボックスをオプションで書き直す",[11,696,697],{},"TS リマックスよりずっと簡単——fMP4 は結合のために設計されている。Apple の LL-HLS は今やほぼ完全に fMP4 を使う。",[32,699,701],{"id":700},"low-latency-hlsll-hls","Low-Latency HLS（LL-HLS）",[11,703,704],{},"Apple の 2019 年の仕様拡張が追加したもの：",[77,706,707,713,719],{},[80,708,709,712],{},[20,710,711],{},"EXT-X-PART","——部分セグメント（通常 200ms、6s ではなく）",[80,714,715,718],{},[20,716,717],{},"EXT-X-PRELOAD-HINT","——存在しないセグメントをフェッチし始めるようクライアントに伝える",[80,720,721,724],{},[20,722,723],{},"EXT-X-RENDITION-REPORT","——バリアント playlist 同士が直接互いを参照",[11,726,727,728,731,732,734],{},"ダウンローダーにとって、LL-HLS は主に「リクエスト数が多く、ファイルサイズが小さい」を意味する。マージロジックは通常の HLS と同じ——",[20,729,730],{},"EXTINF"," に加えて ",[20,733,711],{}," を処理するだけ。FlowPick は処理可能。自分で書いたダウンローダーがこれを処理しないと、部分セグメントを見逃す。",[32,736,737],{"id":737},"実際に見た抜け",[11,739,740,743,744,747,748,751,752,755,756,759],{},[383,741,742],{},"抜け 1：相対 URI。"," M3U8 は相対 URI を大量に使う。master playlist が ",[20,745,746],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmaster.m3u8"," にあり、中に ",[20,749,750],{},"v_720.m3u8"," と書かれていれば、バリアントは ",[20,753,754],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fv_720.m3u8","。でも master が ",[20,757,758],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmaster.m3u8?token=abc"," にあっても、token は自動では引き継がれない。query string の継承は手動で処理する。",[11,761,762],{},[383,763,764],{},"抜け 2：バイト範囲アドレッシング。",[40,766,769],{"className":767,"code":768,"language":45},[43],"#EXT-X-BYTERANGE:522752@1024\n",[20,770,768],{"__ignoreMap":48},[11,772,773,774,777,778,781],{},"意味：「セグメントは大きなファイルの 1 つのバイト範囲」。",[20,775,776],{},"Range"," ヘッダーなしでこの URI を ",[20,779,780],{},"fetch"," すると、ファイル全体を取得する。多くの CDN ホスト HLS playlist はアーカイブコンテンツに対してこうする——数千の小さなファイルではなく、1 つの大きなファイルとバイト範囲。",[11,783,784,787,788,791,792,795,796,799],{},[383,785,786],{},"抜け 3：CODECS でバリアントを選ぶ。"," master playlist が ",[20,789,790],{},"avc1.*","（H.264）と ",[20,793,794],{},"hvc1.*","（HEVC）の両方のバリアントを持つ場合、プレイヤーは HEVC を選ぶかもしれない。ダウンローダーが最初のバリアントだけを取得すると、HEVC が来るが H.264 が欲しいかもしれない。必ずユーザーに選ばせ、",[20,797,798],{},"CODECS"," を解析してバリアントにラベルを付ける。",[11,801,802,805],{},[383,803,804],{},"抜け 4：トークンの期限切れ。"," ライブの URI のトークンはたいてい数分で期限切れ。1 時間前に取得した清単 URL を今からダウンロードすると、全部 403。FlowPick はダウンロード時に清単を再取得する。これが「URL を先に取得して後でダウンロード」がライブコンテンツでよく失敗する理由。",[32,807,808],{"id":808},"参考資料",[77,810,811,820,828,839],{},[80,812,813,819],{},[14,814,818],{"href":815,"rel":816},"https:\u002F\u002Fdatatracker.ietf.org\u002Fdoc\u002Fhtml\u002Frfc8216",[817],"nofollow","RFC 8216 — HTTP Live Streaming, 2nd Edition","——正式仕様、107 ページ",[80,821,822,827],{},[14,823,826],{"href":824,"rel":825},"https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fhttp_live_streaming\u002Fhls_authoring_specification_for_apple_devices",[817],"Apple HLS オーサリング仕様","——Apple App Store 承認要件",[80,829,830,835,836,838],{},[14,831,834],{"href":832,"rel":833},"https:\u002F\u002Fffmpeg.org\u002Fffmpeg-formats.html#mpegts",[817],"FFmpeg MPEG-TS フォーマットドキュメント","——",[20,837,120],{}," ファイルの中身を理解する",[80,840,841,846],{},[14,842,845],{"href":843,"rel":844},"https:\u002F\u002Fmp4ra.org\u002F",[817],"MP4RA — MP4 レジストリ","——CODECS 属性の codec から FourCC へのマッピング",[32,848,849],{"id":849},"まとめ",[11,851,852,853,855,856,855,858,855,860,855,863,866,867,872],{},"M3U8 入門の物語——「プレイリストで、セグメントを取って、結合する」——は実際のストリームの約 60% をカバーする。残り 40% はマルチ音声、暗号化、fMP4 init セグメント、不連続性、バイト範囲、期限切れトークンを含む。堅牢な HLS ダウンローダーは ",[20,854,432],{},"、",[20,857,404],{},[20,859,623],{},[20,861,862],{},"EXT-X-BYTERANGE",[20,864,865],{},"EXT-X-DISCONTINUITY"," を正しく解析する必要がある。「正しい」がどんな見た目かは ",[14,868,871],{"href":869,"rel":870},"https:\u002F\u002Fgithub.com\u002Fezwebtools\u002Fflowpick",[817],"FlowPick のソースコード"," で見られる。",[11,874,875,876,880,881,855,884,887,888,891],{},"次は ",[14,877,879],{"href":878},"\u002Fja\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection","DASH\u002FMPD ディープ記事"," で、MPEG-DASH の対応メカニズム——",[20,882,883],{},"SegmentTemplate",[20,885,886],{},"$Number$"," 置換、",[20,889,890],{},"ContentProtection","、そして DASH が構造的に HLS より複雑なのにダウンロードしやすい理由を説明。",[893,894],"hr",{},[32,896,897],{"id":897},"おすすめ記事",[77,899,900,908,916,924,932,941],{},[80,901,902,907],{},[383,903,904],{},[14,905,906],{"href":16},"M3U8\u002FHLS ストリームのダウンロード方法：完全入門ガイド","——この記事の 60% の簡単なケース、あちらが基礎",[80,909,910,915],{},[383,911,912],{},[14,913,914],{"href":500},"FlowPick はどうやってブラウザだけで数百の動画セグメントを 1 つの MP4 にまとめるのか","——FlowPick がどうやってマルチ音声+映像のマルチプレクスを処理するか",[80,917,918,923],{},[383,919,920],{},[14,921,922],{"href":878},"DASH ディープ：SegmentTemplate、ContentProtection、視点切り替え MPD","——この記事の DASH 版",[80,925,926,931],{},[383,927,928],{},[14,929,930],{"href":415},"ストリーミングのダウンロードは合法か？技術と法律のチェックリスト","——なぜ FlowPick が SAMPLE-AES \u002F DRM コンテンツを拒否するか",[80,933,934,940],{},[383,935,936],{},[14,937,939],{"href":938},"\u002Fja\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","ブラウザでの動画リマックス：fMP4、ISOBMFF、トランスコードしない理由","——コンテナフォーマットのディープ記事",[80,942,943,949],{},[383,944,945],{},[14,946,948],{"href":947},"\u002Fja\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS：ブラウザ動画処理実戦","——FFmpeg WASM が適さない時のより低レベルなブラウザ API",[951,952,953],"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":955},[956,959,960,963,964,965,966,967,968,969],{"id":34,"depth":183,"text":35,"children":957},[958],{"id":68,"depth":211,"text":69},{"id":128,"depth":183,"text":129},{"id":426,"depth":183,"text":427,"children":961},[962],{"id":505,"depth":211,"text":505},{"id":547,"depth":183,"text":548},{"id":639,"depth":183,"text":640},{"id":700,"depth":183,"text":701},{"id":737,"depth":183,"text":737},{"id":808,"depth":183,"text":808},{"id":849,"depth":183,"text":849},{"id":897,"depth":183,"text":897},"tips","2026-08-04","M3U8 入門のその先——EXT-X-KEY、EXT-X-MEDIA、不連続性、そして素朴な HLS ダウンローダーをクラッシュさせる境界ケース。実際の清単スニペットと FlowPick の処理ロジック付き。","md","\u002Fscreenshots\u002Fhls-detection.png",{},true,"\u002Fja\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack",16,{"title":5,"description":972},"ja\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack",[982,983,984,985,986,987],"hls","m3u8","ディープ","暗号化","マルチトラック","ストリーミング","F4FjBbbUisw7Xg1jzN4ucIBCt0JAqLu_K-xvDXTSwVA",[990,996],{"title":991,"path":16,"stem":992,"description":993,"date":994,"category":995,"children":-1},"M3U8\u002FHLSストリーミングのダウンロード：完全初心者ガイド","ja\u002Fblog\u002Fgetting-started-with-hls-m3u8-download","ブラウザでM3U8・HLS動画を保存する方法、プレイリストURLの確認、MP4とTSの選び方、よくある失敗の対処法を解説します。","2026-07-01","tutorials",{"title":997,"path":500,"stem":998,"description":999,"date":1000,"category":970,"children":-1},"FlowPickがブラウザ内で数百の動画セグメントをMP4に結合する仕組み","ja\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","サーバー不要、FFmpegインストール不要、トランスコード不要——FlowPickがHLS\u002FDASHセグメント結合をブラウザタブ内で完結させる技術パスを完全解説。","2026-07-02",1787670591459]