[{"data":1,"prerenderedAt":1745},["ShallowReactive",2],{"blog-ja-dash-mpd-deep-dive-segmenttemplate-contentprotection":3,"blog-ja-dash-mpd-deep-dive-segmenttemplate-contentprotection-surround":1732},{"id":4,"title":5,"author":6,"body":7,"category":1715,"date":1716,"description":1717,"extension":1718,"image":1719,"lastModified":1716,"meta":1720,"navigation":228,"path":1721,"readingTime":701,"seo":1722,"stem":1723,"tags":1724,"__hash__":1731},"blog_ja\u002Fja\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection.md","DASH ディープ：SegmentTemplate、ContentProtection、視点切り替え MPD 構造","FlowPick チーム",{"type":8,"value":9,"toc":1698},"minimark",[10,34,37,42,45,55,58,66,70,115,118,147,153,388,398,403,412,434,439,493,499,503,509,559,583,586,608,611,619,622,626,710,713,739,745,751,758,762,765,799,803,828,831,845,848,851,862,866,914,920,923,926,929,1484,1491,1499,1502,1523,1547,1557,1563,1566,1601,1604,1607,1624,1635,1638,1641,1694],[11,12,13,18,19,23,24,23,27,23,30,33],"p",{},[14,15,17],"a",{"href":16},"\u002Fja\u002Fblog\u002Fwhat-is-dash-mpd-streaming","MPD 入門ガイド"," では MPD とは何か、なぜ MP4 ではなく 12KB の XML が出てくるのかを説明した。この記事はその続き——MPD を開いて、400 行の ",[20,21,22],"code",{},"\u003CSegmentTemplate>","、",[20,25,26],{},"\u003CSegmentTimeline>",[20,28,29],{},"\u003CContentProtection>",[20,31,32],{},"\u003CAdaptationSet>"," を見て、何が本当に使えるのかを知りたい人向け。",[11,35,36],{},"DASH パーサーを書いているか、ダウンロードツールが取れるストリームと取れないストリームがある理由を知りたいなら、この記事が地図になる。",[38,39,41],"h2",{"id":40},"mpd-の階層","MPD の階層",[11,43,44],{},"すべての MPD はこのネスト構造：",[46,47,52],"pre",{"className":48,"code":50,"language":51},[49],"language-text","MPD\n├── Period                  # コンテンツの 1 セグメント（本編、広告など）\n│   ├── AdaptationSet       # 交換可能なバージョンのグループ（映像、音声など）\n│   │   ├── Representation  # 具体的な 1 バージョン（1080p、720p など）\n│   │   │   ├── SegmentList     # セグメント URL を明示的に列挙\n│   │   │   ├── SegmentTemplate # セグメント URL をテンプレート化\n│   │   │   └── SegmentBase     # 単一セグメント + バイト範囲\n│   │   └── ContentProtection   # DRM シグナリング（Widevine、PlayReady）\n│   └── EventStream         # 帯域外イベント（ID3 のようなもの）\n","text",[20,53,50],{"__ignoreMap":54},"",[11,56,57],{},"典型的な映画の MPD は Period 1 つ、AdaptationSet 2 つ（映像 + 音声）、各セットに Representation が 5-10 個。広告付きライブは Period が 4 つ以上になる。マルチカメラのスポーツライブは各カメラに 1 つの AdaptationSet。",[11,59,60,61,65],{},"核心：",[62,63,64],"strong",{},"DASH はすべてのセグメント URL を列挙しないように設計されている。"," HLS は 1 つずつ列挙する。DASH はテンプレートを使う。",[38,67,69],{"id":68},"segmenttemplate90-のケース","SegmentTemplate：90% のケース",[46,71,75],{"className":72,"code":73,"language":74,"meta":54,"style":54},"language-xml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CSegmentTemplate\n  initialization=\"video\u002Finit.mp4\"\n  media=\"video\u002F$Number$.m4s\"\n  startNumber=\"1\"\n  timescale=\"1000\"\n  duration=\"6000\" \u002F>\n","xml",[20,76,77,85,91,97,103,109],{"__ignoreMap":54},[78,79,82],"span",{"class":80,"line":81},"line",1,[78,83,84],{},"\u003CSegmentTemplate\n",[78,86,88],{"class":80,"line":87},2,[78,89,90],{},"  initialization=\"video\u002Finit.mp4\"\n",[78,92,94],{"class":80,"line":93},3,[78,95,96],{},"  media=\"video\u002F$Number$.m4s\"\n",[78,98,100],{"class":80,"line":99},4,[78,101,102],{},"  startNumber=\"1\"\n",[78,104,106],{"class":80,"line":105},5,[78,107,108],{},"  timescale=\"1000\"\n",[78,110,112],{"class":80,"line":111},6,[78,113,114],{},"  duration=\"6000\" \u002F>\n",[11,116,117],{},"訳すと：",[119,120,121,129,140],"ul",{},[122,123,124,125,128],"li",{},"初期化セグメントは ",[20,126,127],{},"video\u002Finit.mp4","（fMP4 の初期化ボックス）",[122,130,131,132,135,136,139],{},"各メディアセグメントは ",[20,133,134],{},"video\u002F$Number$.m4s","。",[20,137,138],{},"$Number$"," は 1 から",[122,141,142,143,146],{},"各セグメントは 6000 ミリ秒（",[20,144,145],{},"timescale=1000"," なので 6 秒）",[11,148,149,150,152],{},"この Representation をダウンロードするには、",[20,151,138],{}," を置換して URL を生成する：",[46,154,158],{"className":155,"code":156,"language":157,"meta":54,"style":54},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const startNumber = 1\nconst duration = 6000        \u002F\u002F ms\nconst timescale = 1000       \u002F\u002F 1 単位 = 1 ms\nconst totalDuration = 7200000 \u002F\u002F ms——Representation の @duration 属性から\n\nconst segmentCount = Math.ceil(totalDuration * timescale \u002F (duration * timescale))\n\u002F\u002F = 1200 セグメント\n\nconst urls = []\nfor (let n = startNumber; n \u003C startNumber + segmentCount; n++) {\n  urls.push(`video\u002F${n}.m4s`)\n}\n","js",[20,159,160,178,194,209,224,230,268,274,279,292,343,382],{"__ignoreMap":54},[78,161,162,166,170,174],{"class":80,"line":81},[78,163,165],{"class":164},"spNyl","const",[78,167,169],{"class":168},"sTEyZ"," startNumber ",[78,171,173],{"class":172},"sMK4o","=",[78,175,177],{"class":176},"sbssI"," 1\n",[78,179,180,182,185,187,190],{"class":80,"line":87},[78,181,165],{"class":164},[78,183,184],{"class":168}," duration ",[78,186,173],{"class":172},[78,188,189],{"class":176}," 6000",[78,191,193],{"class":192},"sHwdD","        \u002F\u002F ms\n",[78,195,196,198,201,203,206],{"class":80,"line":93},[78,197,165],{"class":164},[78,199,200],{"class":168}," timescale ",[78,202,173],{"class":172},[78,204,205],{"class":176}," 1000",[78,207,208],{"class":192},"       \u002F\u002F 1 単位 = 1 ms\n",[78,210,211,213,216,218,221],{"class":80,"line":99},[78,212,165],{"class":164},[78,214,215],{"class":168}," totalDuration ",[78,217,173],{"class":172},[78,219,220],{"class":176}," 7200000",[78,222,223],{"class":192}," \u002F\u002F ms——Representation の @duration 属性から\n",[78,225,226],{"class":80,"line":105},[78,227,229],{"emptyLinePlaceholder":228},true,"\n",[78,231,232,234,237,239,242,245,249,252,255,257,260,263,265],{"class":80,"line":111},[78,233,165],{"class":164},[78,235,236],{"class":168}," segmentCount ",[78,238,173],{"class":172},[78,240,241],{"class":168}," Math",[78,243,244],{"class":172},".",[78,246,248],{"class":247},"s2Zo4","ceil",[78,250,251],{"class":168},"(totalDuration ",[78,253,254],{"class":172},"*",[78,256,200],{"class":168},[78,258,259],{"class":172},"\u002F",[78,261,262],{"class":168}," (duration ",[78,264,254],{"class":172},[78,266,267],{"class":168}," timescale))\n",[78,269,271],{"class":80,"line":270},7,[78,272,273],{"class":192},"\u002F\u002F = 1200 セグメント\n",[78,275,277],{"class":80,"line":276},8,[78,278,229],{"emptyLinePlaceholder":228},[78,280,282,284,287,289],{"class":80,"line":281},9,[78,283,165],{"class":164},[78,285,286],{"class":168}," urls ",[78,288,173],{"class":172},[78,290,291],{"class":168}," []\n",[78,293,295,299,302,305,308,310,313,316,318,321,323,326,329,331,334,337,340],{"class":80,"line":294},10,[78,296,298],{"class":297},"s7zQu","for",[78,300,301],{"class":168}," (",[78,303,304],{"class":164},"let",[78,306,307],{"class":168}," n ",[78,309,173],{"class":172},[78,311,312],{"class":168}," startNumber",[78,314,315],{"class":172},";",[78,317,307],{"class":168},[78,319,320],{"class":172},"\u003C",[78,322,169],{"class":168},[78,324,325],{"class":172},"+",[78,327,328],{"class":168}," segmentCount",[78,330,315],{"class":172},[78,332,333],{"class":168}," n",[78,335,336],{"class":172},"++",[78,338,339],{"class":168},") ",[78,341,342],{"class":172},"{\n",[78,344,346,349,351,354,358,361,365,368,371,374,377,379],{"class":80,"line":345},11,[78,347,348],{"class":168},"  urls",[78,350,244],{"class":172},[78,352,353],{"class":247},"push",[78,355,357],{"class":356},"swJcz","(",[78,359,360],{"class":172},"`",[78,362,364],{"class":363},"sfazB","video\u002F",[78,366,367],{"class":172},"${",[78,369,370],{"class":168},"n",[78,372,373],{"class":172},"}",[78,375,376],{"class":363},".m4s",[78,378,360],{"class":172},[78,380,381],{"class":356},")\n",[78,383,385],{"class":80,"line":384},12,[78,386,387],{"class":172},"}\n",[11,389,390,391,394,395,397],{},"HLS よりずっとコンパクト——1200 行の ",[20,392,393],{},"#EXTINF"," + URL が 1 つの ",[20,396,22],{}," に。代償はパーサーが自分で計算しないといけないことで、エッジケース（最後のセグメントが短い、セグメント長が変化する）に注意。",[399,400,402],"h3",{"id":401},"number-vs-time","$Number$ vs $Time$",[11,404,405,407,408,411],{},[20,406,138],{}," はセグメントインデックス（1、2、3……）。",[20,409,410],{},"$Time$"," はセグメントの開始時刻（timescale 単位）。両方使うもの、どちらかだけのものがある：",[46,413,415],{"className":72,"code":414,"language":74,"meta":54,"style":54},"\u003CSegmentTemplate\n  media=\"video\u002F$Time$.m4s\"\n  timescale=\"1000\"\n  duration=\"6000\" \u002F>\n",[20,416,417,421,426,430],{"__ignoreMap":54},[78,418,419],{"class":80,"line":81},[78,420,84],{},[78,422,423],{"class":80,"line":87},[78,424,425],{},"  media=\"video\u002F$Time$.m4s\"\n",[78,427,428],{"class":80,"line":93},[78,429,108],{},[78,431,432],{"class":80,"line":99},[78,433,114],{},[11,435,436,438],{},[20,437,410],{}," を解析するには、セグメントインデックスに長さを掛ける：",[46,440,442],{"className":155,"code":441,"language":157,"meta":54,"style":54},"const segmentTime = (n - startNumber) * duration\nconst url = `video\u002F${segmentTime}.m4s`\n",[20,443,444,467],{"__ignoreMap":54},[78,445,446,448,451,453,456,459,462,464],{"class":80,"line":81},[78,447,165],{"class":164},[78,449,450],{"class":168}," segmentTime ",[78,452,173],{"class":172},[78,454,455],{"class":168}," (n ",[78,457,458],{"class":172},"-",[78,460,461],{"class":168}," startNumber) ",[78,463,254],{"class":172},[78,465,466],{"class":168}," duration\n",[78,468,469,471,474,476,479,481,483,486,488,490],{"class":80,"line":87},[78,470,165],{"class":164},[78,472,473],{"class":168}," url ",[78,475,173],{"class":172},[78,477,478],{"class":172}," `",[78,480,364],{"class":363},[78,482,367],{"class":172},[78,484,485],{"class":168},"segmentTime",[78,487,373],{"class":172},[78,489,376],{"class":363},[78,491,492],{"class":172},"`\n",[11,494,495,498],{},[20,496,497],{},"timescale"," を掛け忘れると、URL が 1000 倍ずれて全部 404 になる。これが DASH パースで最も一般的なバグ。",[38,500,502],{"id":501},"segmenttimelineセグメント長が固定でない時","SegmentTimeline：セグメント長が固定でない時",[11,504,505,506,508],{},"ライブや動的コンテンツは ",[20,507,26],{}," を使う：",[46,510,512],{"className":72,"code":511,"language":74,"meta":54,"style":54},"\u003CSegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" media=\"$Number$.m4s\">\n  \u003CSegmentTimeline>\n    \u003CS t=\"0\" d=\"6000\" \u002F>\n    \u003CS t=\"6000\" d=\"6000\" \u002F>\n    \u003CS t=\"12000\" d=\"4000\" \u002F>\n    \u003CS t=\"16000\" d=\"6000\" r=\"3\" \u002F>  \u003C!-- r=3 は 3 回反復：計 4 セグメント -->\n    \u003CS t=\"40000\" d=\"6000\" \u002F>\n  \u003C\u002FSegmentTimeline>\n\u003C\u002FSegmentTemplate>\n",[20,513,514,519,524,529,534,539,544,549,554],{"__ignoreMap":54},[78,515,516],{"class":80,"line":81},[78,517,518],{},"\u003CSegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" media=\"$Number$.m4s\">\n",[78,520,521],{"class":80,"line":87},[78,522,523],{},"  \u003CSegmentTimeline>\n",[78,525,526],{"class":80,"line":93},[78,527,528],{},"    \u003CS t=\"0\" d=\"6000\" \u002F>\n",[78,530,531],{"class":80,"line":99},[78,532,533],{},"    \u003CS t=\"6000\" d=\"6000\" \u002F>\n",[78,535,536],{"class":80,"line":105},[78,537,538],{},"    \u003CS t=\"12000\" d=\"4000\" \u002F>\n",[78,540,541],{"class":80,"line":111},[78,542,543],{},"    \u003CS t=\"16000\" d=\"6000\" r=\"3\" \u002F>  \u003C!-- r=3 は 3 回反復：計 4 セグメント -->\n",[78,545,546],{"class":80,"line":270},[78,547,548],{},"    \u003CS t=\"40000\" d=\"6000\" \u002F>\n",[78,550,551],{"class":80,"line":276},[78,552,553],{},"  \u003C\u002FSegmentTimeline>\n",[78,555,556],{"class":80,"line":281},[78,557,558],{},"\u003C\u002FSegmentTemplate>\n",[119,560,561,567,573],{},[122,562,563,566],{},[20,564,565],{},"t","——開始時刻（timescale 単位）",[122,568,569,572],{},[20,570,571],{},"d","——長さ（timescale 単位）",[122,574,575,578,579,582],{},[20,576,577],{},"r","——反復回数（",[20,580,581],{},"r+1"," 個のセグメントを意味する）",[11,584,585],{},"ライブの timeline は時間と共に伸びる。典型的なフロー：",[587,588,589,596,599,602],"ol",{},[122,590,591,592,595],{},"クライアントが MPD を取得。セグメント 1-50 が見える。",[20,593,594],{},"minimumUpdatePeriod"," は 2 秒",[122,597,598],{},"クライアントは 1-50 を再生",[122,600,601],{},"クライアントは MPD を再取得。今は 1-52（timeline が伸びている）",[122,603,604,607],{},[20,605,606],{},"MPD@endOfAvailability"," またはストリーム終了まで繰り返し",[11,609,610],{},"ダウンローダーの選択肢：",[119,612,613,616],{},[122,614,615],{},"ストリームが完全に終わるのを待つ（VOD に切り替わる）——常に可能とは限らない",[122,617,618],{},"現在の timeline をスナップショットして、後から追加されるコンテンツは諦める",[11,620,621],{},"FlowPick はスナップショット方式：現在の timeline を取得、セグメントをダウンロード、マージ。ストリームがまだ生きていれば、ユーザーはもう一度トリガーして新しいセグメントを取得できる。",[38,623,625],{"id":624},"contentprotectiondrm-シグナリング","ContentProtection：DRM シグナリング",[46,627,629],{"className":72,"code":628,"language":74,"meta":54,"style":54},"\u003CContentProtection\n  schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\"\n  value=\"cenc\"\n  cenc:default_KID=\"12345678-1234-1234-1234-123456789012\" \u002F>\n\n\u003CContentProtection\n  schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n  value=\"Widevine\">\n  \u003Ccenc:pssh>AAAA...\u003C\u002Fcenc:pssh>\n\u003C\u002FContentProtection>\n\n\u003CContentProtection\n  schemeIdUri=\"urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95\"\n  value=\"PlayReady\">\n  \u003Ccenc:pssh>BBBB...\u003C\u002Fcenc:pssh>\n\u003C\u002FContentProtection>\n",[20,630,631,636,641,646,651,655,659,664,669,674,679,683,687,693,699,705],{"__ignoreMap":54},[78,632,633],{"class":80,"line":81},[78,634,635],{},"\u003CContentProtection\n",[78,637,638],{"class":80,"line":87},[78,639,640],{},"  schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\"\n",[78,642,643],{"class":80,"line":93},[78,644,645],{},"  value=\"cenc\"\n",[78,647,648],{"class":80,"line":99},[78,649,650],{},"  cenc:default_KID=\"12345678-1234-1234-1234-123456789012\" \u002F>\n",[78,652,653],{"class":80,"line":105},[78,654,229],{"emptyLinePlaceholder":228},[78,656,657],{"class":80,"line":111},[78,658,635],{},[78,660,661],{"class":80,"line":270},[78,662,663],{},"  schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n",[78,665,666],{"class":80,"line":276},[78,667,668],{},"  value=\"Widevine\">\n",[78,670,671],{"class":80,"line":281},[78,672,673],{},"  \u003Ccenc:pssh>AAAA...\u003C\u002Fcenc:pssh>\n",[78,675,676],{"class":80,"line":294},[78,677,678],{},"\u003C\u002FContentProtection>\n",[78,680,681],{"class":80,"line":345},[78,682,229],{"emptyLinePlaceholder":228},[78,684,685],{"class":80,"line":384},[78,686,635],{},[78,688,690],{"class":80,"line":689},13,[78,691,692],{},"  schemeIdUri=\"urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95\"\n",[78,694,696],{"class":80,"line":695},14,[78,697,698],{},"  value=\"PlayReady\">\n",[78,700,702],{"class":80,"line":701},15,[78,703,704],{},"  \u003Ccenc:pssh>BBBB...\u003C\u002Fcenc:pssh>\n",[78,706,708],{"class":80,"line":707},16,[78,709,678],{},[11,711,712],{},"UUID が DRM システムを識別する：",[119,714,715,721,727,733],{},[122,716,717,720],{},[20,718,719],{},"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed","——Widevine（Google）",[122,722,723,726],{},[20,724,725],{},"9a04f079-9840-4286-ab92-e65be0885f95","——PlayReady（Microsoft）",[122,728,729,732],{},[20,730,731],{},"f239e769-efa3-4850-9c16-a9036d3fe1c1","——Adobe Primetime",[122,734,735,738],{},[20,736,737],{},"94ce86fb-07ff-4f43-adb8-93d2fa968ca2","——FairPlay（Apple、DASH 文脈では）",[11,740,741,744],{},[20,742,743],{},"\u003Ccenc:pssh>"," は Protection System Specific Header——base64 の blob で、key ID とシステム固有データを含む。ブラウザはこれを EME（Encrypted Media Extensions）に渡し、EME がシステムの CDM（Content Decryption Module）と会話する。",[11,746,747,750],{},[62,748,749],{},"ここで線を引く："," FlowPick は DRM で保護された DASH コンテンツを解読しないし、しないし、できない。正当なブラウザ拡張もオープンソースツールもできない。CDM はブラックボックス——暗号化バッファを渡すと、表示用の復号フレームを返すが、エクスポートは永久にしない。これを迂回するのは反規避法（米国 DMCA §1201、他の地域も同等）に違反し、CDM の契約にも違反する。",[11,752,753,754,757],{},"DASH 仕様には「保護されていない」パスがある：ある AdaptationSet が ",[20,755,756],{},"ContentProtection"," を持たなければ、セグメントは暗号化されておらず、ダウンローダーは自由にフェッチしてマージできる。だから FlowPick は Bilibili、Udemy の無料コース、たいていの企業 LMS を処理できる。Netflix\u002FDisney+\u002FHBO Max は処理できない。",[399,759,761],{"id":760},"保護っぽいけど-drm-ではない一般的なパターン","「保護っぽい」けど DRM ではない一般的なパターン",[11,763,764],{},"「ダウンロードできない」ことがすべて DRM というわけではない。ダウンロードを難しくするが、実際には暗号化しないパターン：",[587,766,767,777,783],{},[122,768,769,772,773,776],{},[62,770,771],{},"短い TTL のトークン URL","——",[20,774,775],{},"?token=abc&expires=1234567890","。トークンが数秒で期限切れになるので、清単を解析したらすぐにセグメントをフェッチしないといけない。FlowPick はそうしている。「何時間も前に URL を解析して今からダウンロード」系のバッチダウンローダーは失敗する。",[122,778,779,782],{},[62,780,781],{},"CDN の CORS 制限","——でも拡張はページ origin からリクエストを送るので CORS に縛られず、スニッファーを実質止められない。",[122,784,785,791,792,795,796,798],{},[62,786,787,790],{},[20,788,789],{},"\u003CBaseURL>"," の書き換え","——MPD の ",[20,793,794],{},"BaseURL"," 要素は文書の途中で変えられる。各 ",[20,797,794],{}," は親に対して相対的に解決されるので、URL の連鎖を追跡する必要がある。これは DASH 版の HLS 相対 URI だが、エラーを起こしやすい。",[38,800,802],{"id":801},"マルチ-period-mpd広告とライブイベント","マルチ Period MPD：広告とライブイベント",[46,804,806],{"className":72,"code":805,"language":74,"meta":54,"style":54},"\u003CPeriod id=\"ad1\" start=\"PT0S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n\u003CPeriod id=\"main\" start=\"PT30S\">...\u003C\u002FPeriod>\n\u003CPeriod id=\"ad2\" start=\"PT1800S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n\u003CPeriod id=\"main2\" start=\"PT1830S\">...\u003C\u002FPeriod>\n",[20,807,808,813,818,823],{"__ignoreMap":54},[78,809,810],{"class":80,"line":81},[78,811,812],{},"\u003CPeriod id=\"ad1\" start=\"PT0S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[78,814,815],{"class":80,"line":87},[78,816,817],{},"\u003CPeriod id=\"main\" start=\"PT30S\">...\u003C\u002FPeriod>\n",[78,819,820],{"class":80,"line":93},[78,821,822],{},"\u003CPeriod id=\"ad2\" start=\"PT1800S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[78,824,825],{"class":80,"line":99},[78,826,827],{},"\u003CPeriod id=\"main2\" start=\"PT1830S\">...\u003C\u002FPeriod>\n",[11,829,830],{},"各 Period は独自の AdaptationSet を持つ。コンテンツ全体をダウンロードするには：",[587,832,833,839,842],{},[122,834,835,838],{},[20,836,837],{},"@start"," で Period をソート",[122,840,841],{},"各 Period で映像 + 音声セグメントをすべて取得",[122,843,844],{},"Period 間で結合——ただし codec が Period 間で変わる場合に注意",[11,846,847],{},"広告ブレークはよく別 codec（広告は H.264 baseline、本編は H.264 high）。無脑に結合すると、本編は普通に再生されるが広告部分でグリッチが出る。",[11,849,850],{},"修正方法：",[587,852,853,856,859],{},[122,854,855],{},"広告 Period をスキップ（FlowPick のアプローチ——ユーザーは広告を欲しくないので）",[122,857,858],{},"広告 Period を本編と同じ codec に再エンコード（遅い、トランスコードが必要）",[122,860,861],{},"より賢いマルチプレクサで codec 切り替えを処理（FFmpeg はできるが複雑）",[38,863,865],{"id":864},"視点切り替えと-trick-mode-adaptationset","視点切り替えと trick-mode AdaptationSet",[46,867,869],{"className":72,"code":868,"language":74,"meta":54,"style":54},"\u003CAdaptationSet id=\"1\" contentType=\"video\" group=\"1\">\n  \u003C!-- メインカメラ -->\n\u003C\u002FAdaptationSet>\n\u003CAdaptationSet id=\"2\" contentType=\"video\" group=\"1\">\n  \u003C!-- バックアップカメラ、同じ group -->\n\u003C\u002FAdaptationSet>\n\u003CAdaptationSet id=\"3\" contentType=\"video\" group=\"2\">\n  \u003C!-- Trick mode（シークバーのサムネイル） -->\n\u003C\u002FAdaptationSet>\n",[20,870,871,876,881,886,891,896,900,905,910],{"__ignoreMap":54},[78,872,873],{"class":80,"line":81},[78,874,875],{},"\u003CAdaptationSet id=\"1\" contentType=\"video\" group=\"1\">\n",[78,877,878],{"class":80,"line":87},[78,879,880],{},"  \u003C!-- メインカメラ -->\n",[78,882,883],{"class":80,"line":93},[78,884,885],{},"\u003C\u002FAdaptationSet>\n",[78,887,888],{"class":80,"line":99},[78,889,890],{},"\u003CAdaptationSet id=\"2\" contentType=\"video\" group=\"1\">\n",[78,892,893],{"class":80,"line":105},[78,894,895],{},"  \u003C!-- バックアップカメラ、同じ group -->\n",[78,897,898],{"class":80,"line":111},[78,899,885],{},[78,901,902],{"class":80,"line":270},[78,903,904],{},"\u003CAdaptationSet id=\"3\" contentType=\"video\" group=\"2\">\n",[78,906,907],{"class":80,"line":276},[78,908,909],{},"  \u003C!-- Trick mode（シークバーのサムネイル） -->\n",[78,911,912],{"class":80,"line":281},[78,913,885],{},[11,915,916,919],{},[20,917,918],{},"group=\"1\""," は同じコンテンツの異なる視点を意味する。プレイヤーはユーザーにカメラ切り替えを提供する。ダウンローダーはどれか 1 つを選ぶ——大抵は最初だが、理想はユーザーに選ばせる。",[11,921,922],{},"Trick-mode AdaptationSet（ここでは group=2）はシークバーでドラッグした時のプレビュー用、低ビットレートの低フレームレート版。これをダウンロードしても無意味——パーサーがこれを「映像」と誤認しないように。",[38,924,925],{"id":925},"プリフェッチと並列化",[11,927,928],{},"DASH ダウンロードで最も実用的な最適化：セグメントを並列フェッチ。",[46,930,932],{"className":155,"code":931,"language":157,"meta":54,"style":54},"async function downloadRepresentation(rep, concurrency = 6) {\n  const segmentUrls = computeSegmentUrls(rep)\n  const initBytes = await fetch(rep.initUrl).then(r => r.arrayBuffer())\n\n  const results = new Array(segmentUrls.length)\n  let nextIndex = 0\n\n  async function worker() {\n    while (nextIndex \u003C segmentUrls.length) {\n      const i = nextIndex++\n      const resp = await fetch(segmentUrls[i])\n      results[i] = new Uint8Array(await resp.arrayBuffer())\n    }\n  }\n\n  await Promise.all(Array.from({ length: concurrency }, () => worker()))\n\n  \u002F\u002F init + セグメントを順番に結合\n  const total = initBytes.byteLength + results.reduce((s, b) => s + b.byteLength, 0)\n  const out = new Uint8Array(total)\n  let offset = 0\n  out.set(new Uint8Array(initBytes), offset); offset += initBytes.byteLength\n  for (const buf of results) {\n    out.set(buf, offset); offset += buf.byteLength\n  }\n  return out\n}\n",[20,933,934,969,988,1037,1041,1068,1081,1085,1100,1123,1138,1164,1196,1201,1206,1210,1260,1265,1271,1332,1353,1365,1410,1432,1465,1470,1479],{"__ignoreMap":54},[78,935,936,939,942,945,947,951,954,957,960,963,966],{"class":80,"line":81},[78,937,938],{"class":164},"async",[78,940,941],{"class":164}," function",[78,943,944],{"class":247}," downloadRepresentation",[78,946,357],{"class":172},[78,948,950],{"class":949},"sHdIc","rep",[78,952,953],{"class":172},",",[78,955,956],{"class":949}," concurrency",[78,958,959],{"class":172}," =",[78,961,962],{"class":176}," 6",[78,964,965],{"class":172},")",[78,967,968],{"class":172}," {\n",[78,970,971,974,977,979,982,984,986],{"class":80,"line":87},[78,972,973],{"class":164},"  const",[78,975,976],{"class":168}," segmentUrls",[78,978,959],{"class":172},[78,980,981],{"class":247}," computeSegmentUrls",[78,983,357],{"class":356},[78,985,950],{"class":168},[78,987,381],{"class":356},[78,989,990,992,995,997,1000,1003,1005,1007,1009,1012,1014,1016,1019,1021,1023,1026,1029,1031,1034],{"class":80,"line":93},[78,991,973],{"class":164},[78,993,994],{"class":168}," initBytes",[78,996,959],{"class":172},[78,998,999],{"class":297}," await",[78,1001,1002],{"class":247}," fetch",[78,1004,357],{"class":356},[78,1006,950],{"class":168},[78,1008,244],{"class":172},[78,1010,1011],{"class":168},"initUrl",[78,1013,965],{"class":356},[78,1015,244],{"class":172},[78,1017,1018],{"class":247},"then",[78,1020,357],{"class":356},[78,1022,577],{"class":949},[78,1024,1025],{"class":164}," =>",[78,1027,1028],{"class":168}," r",[78,1030,244],{"class":172},[78,1032,1033],{"class":247},"arrayBuffer",[78,1035,1036],{"class":356},"())\n",[78,1038,1039],{"class":80,"line":99},[78,1040,229],{"emptyLinePlaceholder":228},[78,1042,1043,1045,1048,1050,1053,1056,1058,1061,1063,1066],{"class":80,"line":105},[78,1044,973],{"class":164},[78,1046,1047],{"class":168}," results",[78,1049,959],{"class":172},[78,1051,1052],{"class":172}," new",[78,1054,1055],{"class":247}," Array",[78,1057,357],{"class":356},[78,1059,1060],{"class":168},"segmentUrls",[78,1062,244],{"class":172},[78,1064,1065],{"class":168},"length",[78,1067,381],{"class":356},[78,1069,1070,1073,1076,1078],{"class":80,"line":111},[78,1071,1072],{"class":164},"  let",[78,1074,1075],{"class":168}," nextIndex",[78,1077,959],{"class":172},[78,1079,1080],{"class":176}," 0\n",[78,1082,1083],{"class":80,"line":270},[78,1084,229],{"emptyLinePlaceholder":228},[78,1086,1087,1090,1092,1095,1098],{"class":80,"line":276},[78,1088,1089],{"class":164},"  async",[78,1091,941],{"class":164},[78,1093,1094],{"class":247}," worker",[78,1096,1097],{"class":172},"()",[78,1099,968],{"class":172},[78,1101,1102,1105,1107,1110,1113,1115,1117,1119,1121],{"class":80,"line":281},[78,1103,1104],{"class":297},"    while",[78,1106,301],{"class":356},[78,1108,1109],{"class":168},"nextIndex",[78,1111,1112],{"class":172}," \u003C",[78,1114,976],{"class":168},[78,1116,244],{"class":172},[78,1118,1065],{"class":168},[78,1120,339],{"class":356},[78,1122,342],{"class":172},[78,1124,1125,1128,1131,1133,1135],{"class":80,"line":294},[78,1126,1127],{"class":164},"      const",[78,1129,1130],{"class":168}," i",[78,1132,959],{"class":172},[78,1134,1075],{"class":168},[78,1136,1137],{"class":172},"++\n",[78,1139,1140,1142,1145,1147,1149,1151,1153,1155,1158,1161],{"class":80,"line":345},[78,1141,1127],{"class":164},[78,1143,1144],{"class":168}," resp",[78,1146,959],{"class":172},[78,1148,999],{"class":297},[78,1150,1002],{"class":247},[78,1152,357],{"class":356},[78,1154,1060],{"class":168},[78,1156,1157],{"class":356},"[",[78,1159,1160],{"class":168},"i",[78,1162,1163],{"class":356},"])\n",[78,1165,1166,1169,1171,1173,1176,1178,1180,1183,1185,1188,1190,1192,1194],{"class":80,"line":384},[78,1167,1168],{"class":168},"      results",[78,1170,1157],{"class":356},[78,1172,1160],{"class":168},[78,1174,1175],{"class":356},"] ",[78,1177,173],{"class":172},[78,1179,1052],{"class":172},[78,1181,1182],{"class":247}," Uint8Array",[78,1184,357],{"class":356},[78,1186,1187],{"class":297},"await",[78,1189,1144],{"class":168},[78,1191,244],{"class":172},[78,1193,1033],{"class":247},[78,1195,1036],{"class":356},[78,1197,1198],{"class":80,"line":689},[78,1199,1200],{"class":172},"    }\n",[78,1202,1203],{"class":80,"line":695},[78,1204,1205],{"class":172},"  }\n",[78,1207,1208],{"class":80,"line":701},[78,1209,229],{"emptyLinePlaceholder":228},[78,1211,1212,1215,1219,1221,1224,1226,1229,1231,1234,1236,1239,1242,1245,1247,1250,1253,1255,1257],{"class":80,"line":707},[78,1213,1214],{"class":297},"  await",[78,1216,1218],{"class":1217},"sBMFI"," Promise",[78,1220,244],{"class":172},[78,1222,1223],{"class":247},"all",[78,1225,357],{"class":356},[78,1227,1228],{"class":168},"Array",[78,1230,244],{"class":172},[78,1232,1233],{"class":247},"from",[78,1235,357],{"class":356},[78,1237,1238],{"class":172},"{",[78,1240,1241],{"class":356}," length",[78,1243,1244],{"class":172},":",[78,1246,956],{"class":168},[78,1248,1249],{"class":172}," },",[78,1251,1252],{"class":172}," ()",[78,1254,1025],{"class":164},[78,1256,1094],{"class":247},[78,1258,1259],{"class":356},"()))\n",[78,1261,1263],{"class":80,"line":1262},17,[78,1264,229],{"emptyLinePlaceholder":228},[78,1266,1268],{"class":80,"line":1267},18,[78,1269,1270],{"class":192},"  \u002F\u002F init + セグメントを順番に結合\n",[78,1272,1274,1276,1279,1281,1283,1285,1288,1291,1293,1295,1298,1300,1302,1305,1307,1310,1312,1314,1317,1319,1321,1323,1325,1327,1330],{"class":80,"line":1273},19,[78,1275,973],{"class":164},[78,1277,1278],{"class":168}," total",[78,1280,959],{"class":172},[78,1282,994],{"class":168},[78,1284,244],{"class":172},[78,1286,1287],{"class":168},"byteLength",[78,1289,1290],{"class":172}," +",[78,1292,1047],{"class":168},[78,1294,244],{"class":172},[78,1296,1297],{"class":247},"reduce",[78,1299,357],{"class":356},[78,1301,357],{"class":172},[78,1303,1304],{"class":949},"s",[78,1306,953],{"class":172},[78,1308,1309],{"class":949}," b",[78,1311,965],{"class":172},[78,1313,1025],{"class":164},[78,1315,1316],{"class":168}," s",[78,1318,1290],{"class":172},[78,1320,1309],{"class":168},[78,1322,244],{"class":172},[78,1324,1287],{"class":168},[78,1326,953],{"class":172},[78,1328,1329],{"class":176}," 0",[78,1331,381],{"class":356},[78,1333,1335,1337,1340,1342,1344,1346,1348,1351],{"class":80,"line":1334},20,[78,1336,973],{"class":164},[78,1338,1339],{"class":168}," out",[78,1341,959],{"class":172},[78,1343,1052],{"class":172},[78,1345,1182],{"class":247},[78,1347,357],{"class":356},[78,1349,1350],{"class":168},"total",[78,1352,381],{"class":356},[78,1354,1356,1358,1361,1363],{"class":80,"line":1355},21,[78,1357,1072],{"class":164},[78,1359,1360],{"class":168}," offset",[78,1362,959],{"class":172},[78,1364,1080],{"class":176},[78,1366,1368,1371,1373,1376,1378,1381,1383,1385,1388,1390,1392,1394,1396,1398,1400,1403,1405,1407],{"class":80,"line":1367},22,[78,1369,1370],{"class":168},"  out",[78,1372,244],{"class":172},[78,1374,1375],{"class":247},"set",[78,1377,357],{"class":356},[78,1379,1380],{"class":172},"new",[78,1382,1182],{"class":247},[78,1384,357],{"class":356},[78,1386,1387],{"class":168},"initBytes",[78,1389,965],{"class":356},[78,1391,953],{"class":172},[78,1393,1360],{"class":168},[78,1395,965],{"class":356},[78,1397,315],{"class":172},[78,1399,1360],{"class":168},[78,1401,1402],{"class":172}," +=",[78,1404,994],{"class":168},[78,1406,244],{"class":172},[78,1408,1409],{"class":168},"byteLength\n",[78,1411,1413,1416,1418,1420,1423,1426,1428,1430],{"class":80,"line":1412},23,[78,1414,1415],{"class":297},"  for",[78,1417,301],{"class":356},[78,1419,165],{"class":164},[78,1421,1422],{"class":168}," buf",[78,1424,1425],{"class":172}," of",[78,1427,1047],{"class":168},[78,1429,339],{"class":356},[78,1431,342],{"class":172},[78,1433,1435,1438,1440,1442,1444,1447,1449,1451,1453,1455,1457,1459,1461,1463],{"class":80,"line":1434},24,[78,1436,1437],{"class":168},"    out",[78,1439,244],{"class":172},[78,1441,1375],{"class":247},[78,1443,357],{"class":356},[78,1445,1446],{"class":168},"buf",[78,1448,953],{"class":172},[78,1450,1360],{"class":168},[78,1452,965],{"class":356},[78,1454,315],{"class":172},[78,1456,1360],{"class":168},[78,1458,1402],{"class":172},[78,1460,1422],{"class":168},[78,1462,244],{"class":172},[78,1464,1409],{"class":168},[78,1466,1468],{"class":80,"line":1467},25,[78,1469,1205],{"class":172},[78,1471,1473,1476],{"class":80,"line":1472},26,[78,1474,1475],{"class":297},"  return",[78,1477,1478],{"class":168}," out\n",[78,1480,1482],{"class":80,"line":1481},27,[78,1483,387],{"class":172},[11,1485,1486,1487,1490],{},"6 並列接続がスイートスポット——それ以上は CDN のレート制限をトリガーし、以下は帯域を無駄にする。",[20,1488,1489],{},"Promise.all"," の worker プールパターンで、セグメントが順不同で完了しても正しい配列位置に落ちる。",[11,1492,1493,1494,1498],{},"これが FlowPick 内部で使っているパターン。",[14,1495,1497],{"href":1496},"\u002Fja\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","ブラウザ内マージの記事"," で最終バッファをどうマルチプレクスするかを説明している。",[38,1500,1501],{"id":1501},"踏んだ抜け",[11,1503,1504,1510,1511,1514,1515,1518,1519,1522],{},[62,1505,1506,1507,1509],{},"抜け 1：",[20,1508,794],{}," を無視。"," MPD が ",[20,1512,1513],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmanifest.mpd"," にあって、中身が ",[20,1516,1517],{},"\u003CBaseURL>https:\u002F\u002Fother-cdn.example.com\u002F\u003C\u002FBaseURL>"," なら、このセクションの相対 URL は ",[20,1520,1521],{},"other-cdn.example.com"," で解決される。MPD の origin ではない。これを忘れると「全部 404」バグの頭号原因。",[11,1524,1525,1531,1532,1535,1536,1539,1540,1543,1544,1546],{},[62,1526,1527,1528,1530],{},"抜け 2：",[20,1529,497],{}," の解析間違い。"," ",[20,1533,1534],{},"duration=\"6000\""," で ",[20,1537,1538],{},"timescale=\"1000\""," なら 6 秒。",[20,1541,1542],{},"timescale=\"90000\"","（映像でよくある）なら 0.067 秒。",[20,1545,497],{}," を必ず確認——同じ MPD 内で AdaptationSet ごとに異なることがある。",[11,1548,1549,1552,1553,1556],{},[62,1550,1551],{},"抜け 3：init セグメントの忘れ。"," HLS（TS の場合）は各セグメントが自己記述的だが、DASH の fMP4 セグメントは init セグメントがないと役立たず。「ダウンロード成功した」ように見えて再生できないファイルは、",[20,1554,1555],{},"initialization=\"init.mp4\""," を忘れていることが多い。",[11,1558,1559,1562],{},[62,1560,1561],{},"抜け 4：ライブ MPD は更新される。"," 一度解析してスナップショットでダウンロードすると、その後に追加されたセグメントを見逃す。VOD は問題ない。ライブは定期的に再取得するか、ストリームの終わりを待つ。",[38,1564,1565],{"id":1565},"参考資料",[119,1567,1568,1577,1585,1593],{},[122,1569,1570,1576],{},[14,1571,1575],{"href":1572,"rel":1573},"https:\u002F\u002Fwww.iso.org\u002Fstandard\u002F79329.html",[1574],"nofollow","ISO\u002FIEC 23009-1 — DASH 仕様","——正式仕様、有料だが権威",[122,1578,1579,1584],{},[14,1580,1583],{"href":1581,"rel":1582},"https:\u002F\u002Fdashif.org\u002Fguidelines\u002F",[1574],"DASH-IF 実装ガイドライン","——DASH Industry Forum のガイド、無料、ISO ドキュメントより実用的",[122,1586,1587,1592],{},[14,1588,1591],{"href":1589,"rel":1590},"https:\u002F\u002Fgpac.wp.imt.fr\u002Fmp4box\u002F",[1574],"GPAC MP4Box ドキュメント","——リファレンス級の MP4\u002FDASH ツール。パーサーの検証用",[122,1594,1595,1600],{},[14,1596,1599],{"href":1597,"rel":1598},"https:\u002F\u002Fgithub.com\u002Faxiomatic-systems\u002FBento4",[1574],"Bento4 MP4 ドキュメント","——オープンソースの MP4 ツールキット、コンテナフォーマットの理解に役立つ",[38,1602,1603],{"id":1603},"まとめ",[11,1605,1606],{},"DASH と HLS は同じ問題——HTTP ベースのアダプティブストリーミング——を逆の考え方で解決する。HLS は各セグメントを列挙。DASH はテンプレートを使う。HLS はデフォルトで MPEG-TS。DASH は fMP4。HLS の DRM は Apple 専有（FairPlay）。DASH の DRM はマルチベンダー（Widevine、PlayReady、FairPlay）で CENC を通る。",[11,1608,1609,1610,1612,1613,23,1616,23,1619,23,1621,1623],{},"ダウンローダーの観点では、DASH は構造的に扱いやすい：fMP4 セグメントは直接結合できる、init セグメントが codec 情報を明示する、",[20,1611,138],{}," 置換は清単を追加フェッチせずに URL を計算できる。複雑さは XML 解析にある——でも ",[20,1614,1615],{},"SegmentTemplate",[20,1617,1618],{},"SegmentTimeline",[20,1620,794],{},[20,1622,756],{}," を扱えれば、現実の MPD の 95% をカバーできる。",[11,1625,1626,1627,135],{},"次は ",[14,1628,1630,1631,1634],{"href":1629},"\u002Fja\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","fMP4 の結合がなぜ動くのか、",[20,1632,1633],{},"moof"," ボックスに何が入っているのか、なぜ「リマックスでトランスコードしない」が正しい哲学なのか",[1636,1637],"hr",{},[38,1639,1640],{"id":1640},"おすすめ記事",[119,1642,1643,1651,1660,1668,1677,1685],{},[122,1644,1645,1650],{},[62,1646,1647],{},[14,1648,1649],{"href":16},"DASH ストリーミングとは？MPD ファイルを解説","——この記事の入門版",[122,1652,1653,1659],{},[62,1654,1655],{},[14,1656,1658],{"href":1657},"\u002Fja\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack","HLS ディープ：暗号化、マルチトラック、覚えておくべき EXT-X タグ","——HLS 側の対応物",[122,1661,1662,1667],{},[62,1663,1664],{},[14,1665,1666],{"href":1629},"ブラウザでの動画リマックス：fMP4、ISOBMFF、トランスコードしない理由","——DASH セグメントをダウンロードした後、何が起きるか",[122,1669,1670,1676],{},[62,1671,1672],{},[14,1673,1675],{"href":1674},"\u002Fja\u002Fblog\u002Fis-it-legal-to-download-streaming-video","ストリーミングのダウンロードは合法か？技術と法律のチェックリスト","——ContentProtection タグがなぜハードな一線なのか",[122,1678,1679,1684],{},[62,1680,1681],{},[14,1682,1683],{"href":1496},"FlowPick はどうやってブラウザだけで数百の動画セグメントを 1 つの MP4 にまとめるのか","——ここでのパターンが FlowPick でどう実装されているか",[122,1686,1687,1693],{},[62,1688,1689],{},[14,1690,1692],{"href":1691},"\u002Fja\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS：ブラウザ動画処理実戦","——FFmpeg WASM を補完するより低レベルなブラウザ API",[1695,1696,1697],"style",{},"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);}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .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 .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":54,"searchDepth":87,"depth":87,"links":1699},[1700,1701,1704,1705,1708,1709,1710,1711,1712,1713,1714],{"id":40,"depth":87,"text":41},{"id":68,"depth":87,"text":69,"children":1702},[1703],{"id":401,"depth":93,"text":402},{"id":501,"depth":87,"text":502},{"id":624,"depth":87,"text":625,"children":1706},[1707],{"id":760,"depth":93,"text":761},{"id":801,"depth":87,"text":802},{"id":864,"depth":87,"text":865},{"id":925,"depth":87,"text":925},{"id":1501,"depth":87,"text":1501},{"id":1565,"depth":87,"text":1565},{"id":1603,"depth":87,"text":1603},{"id":1640,"depth":87,"text":1640},"tips","2026-08-04","MPD 入門のその先——SegmentTemplate vs SegmentTimeline、$Number$ vs $Time$、ContentProtection シグナリング、そして DASH が HLS より解析が難しいのにダウンロードしやすい構造的理由。","md","\u002Fscreenshots\u002Fdash-downloader.png",{},"\u002Fja\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",{"title":5,"description":1717},"ja\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",[1725,1726,1727,1728,1729,1730],"dash","mpd","ディープ","segmenttemplate","drm","ストリーミング","Zyq1Bm53sanPMGykFUNt0c4qQUmXB7Qb2FsUdqTdF4A",[1733,1738],{"title":1734,"path":1735,"stem":1736,"description":1737,"date":1716,"category":1715,"children":-1},"ブラウザ動画処理パフォーマンスベンチマーク：FFmpeg WASM vs WebCodecs vs ネイティブ","\u002Fja\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","ja\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","実際のハードウェアでの実際のデータ——1080p\u002F4K\u002F8K リマックスとデコードのベンチマーク。FFmpeg WASM、WebCodecs、ネイティブ FFmpeg を横断。方法論、生データ、数字が何を意味するか付き。",{"title":1739,"path":1740,"stem":1741,"description":1742,"date":1743,"category":1744,"children":-1},"FlowPick v1.0.0 リリースノート：初の公開ドロップ","\u002Fja\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","ja\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","FlowPick v1.0.0 リリース——HLS\u002FDASHストリーム、音声、画像を自動検出し、WebAssembly FFmpegでブラウザ内フラグメント結合、すべてローカル処理するブラウザ拡張。初日に出たものはこれ。","2025-04-20","updates",1787670591388]