[{"data":1,"prerenderedAt":1770},["ShallowReactive",2],{"blog-en-dash-mpd-deep-dive-segmenttemplate-contentprotection":3,"blog-en-dash-mpd-deep-dive-segmenttemplate-contentprotection-surround":1757},{"id":4,"title":5,"author":6,"body":7,"category":1740,"date":1741,"description":1742,"extension":1743,"image":1744,"lastModified":1741,"meta":1745,"navigation":230,"path":1746,"readingTime":709,"seo":1747,"stem":1748,"tags":1749,"__hash__":1756},"blog\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection.md","DASH Deep Dive: SegmentTemplate, ContentProtection, and Multi-View MPD Structure","FlowPick Team",{"type":8,"value":9,"toc":1722},"minimark",[10,36,39,44,47,57,60,68,72,117,120,149,155,390,400,405,414,436,442,496,503,507,516,566,590,593,616,619,627,630,634,718,721,747,753,759,766,770,773,807,811,836,839,853,856,859,870,874,922,928,931,935,938,1491,1498,1506,1510,1530,1553,1562,1568,1572,1607,1611,1614,1632,1643,1646,1650,1686,1690,1718],[11,12,13,14,19,20,24,25,24,28,31,32,35],"p",{},"The ",[15,16,18],"a",{"href":17},"\u002Fblog\u002Fwhat-is-dash-mpd-streaming","beginner DASH guide"," explains what an MPD is and why a 12KB XML file shows up instead of an MP4. This is the follow-up — for people who've opened an MPD, seen 400 lines of XML with ",[21,22,23],"code",{},"\u003CSegmentTemplate>",", ",[21,26,27],{},"\u003CSegmentTimeline>",[21,29,30],{},"\u003CContentProtection>"," and ",[21,33,34],{},"\u003CAdaptationSet>"," and wondered which parts actually matter.",[11,37,38],{},"If you're building a DASH parser or trying to understand why your downloader gets some streams but not others, this is the structure.",[40,41,43],"h2",{"id":42},"the-mpd-hierarchy","The MPD hierarchy",[11,45,46],{},"Every MPD has the same nested structure:",[48,49,54],"pre",{"className":50,"code":52,"language":53},[51],"language-text","MPD\n├── Period                  # A chunk of presentation (e.g., main content, ad)\n│   ├── AdaptationSet       # A set of interchangeable renditions (video, audio, etc.)\n│   │   ├── Representation  # A specific rendition (1080p, 720p, etc.)\n│   │   │   ├── SegmentList     # Explicit segment URLs\n│   │   │   ├── SegmentTemplate # Templated segment URLs\n│   │   │   └── SegmentBase     # Single segment with byte ranges\n│   │   └── ContentProtection   # DRM signaling (Widevine, PlayReady)\n│   └── EventStream         # Out-of-band events (ID3-like)\n","text",[21,55,52],{"__ignoreMap":56},"",[11,58,59],{},"A typical movie MPD has 1 Period, 2 AdaptationSets (video + audio), 5-10 Representations per set. A live stream with ad breaks might have 4+ Periods. A multi-angle sports stream might have 1 AdaptationSet per camera angle.",[11,61,62,63,67],{},"The key insight: ",[64,65,66],"strong",{},"everything in DASH is about avoiding the need to list every segment URL."," HLS lists every segment. DASH prefers templates.",[40,69,71],{"id":70},"segmenttemplate-the-90-case","SegmentTemplate: the 90% case",[48,73,77],{"className":74,"code":75,"language":76,"meta":56,"style":56},"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",[21,78,79,87,93,99,105,111],{"__ignoreMap":56},[80,81,84],"span",{"class":82,"line":83},"line",1,[80,85,86],{},"\u003CSegmentTemplate\n",[80,88,90],{"class":82,"line":89},2,[80,91,92],{},"  initialization=\"video\u002Finit.mp4\"\n",[80,94,96],{"class":82,"line":95},3,[80,97,98],{},"  media=\"video\u002F$Number$.m4s\"\n",[80,100,102],{"class":82,"line":101},4,[80,103,104],{},"  startNumber=\"1\"\n",[80,106,108],{"class":82,"line":107},5,[80,109,110],{},"  timescale=\"1000\"\n",[80,112,114],{"class":82,"line":113},6,[80,115,116],{},"  duration=\"6000\" \u002F>\n",[11,118,119],{},"Translation:",[121,122,123,131,142],"ul",{},[124,125,126,127,130],"li",{},"The init segment is ",[21,128,129],{},"video\u002Finit.mp4"," (the fMP4 initialization box)",[124,132,133,134,137,138,141],{},"Each media segment follows the pattern ",[21,135,136],{},"video\u002F$Number$.m4s"," where ",[21,139,140],{},"$Number$"," starts at 1",[124,143,144,145,148],{},"Each segment is 6000 milliseconds (6 seconds, given ",[21,146,147],{},"timescale=1000",")",[11,150,151,152,154],{},"To download this Representation, you generate URLs by substituting ",[21,153,140],{},":",[48,156,160],{"className":157,"code":158,"language":159,"meta":56,"style":56},"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 unit = 1 ms\nconst totalDuration = 7200000 \u002F\u002F ms — from the Representation's @duration attribute\n\nconst segmentCount = Math.ceil(totalDuration * timescale \u002F (duration * timescale))\n\u002F\u002F = 1200 segments\n\nconst urls = []\nfor (let n = startNumber; n \u003C startNumber + segmentCount; n++) {\n  urls.push(`video\u002F${n}.m4s`)\n}\n","js",[21,161,162,180,196,211,226,232,270,276,281,294,345,384],{"__ignoreMap":56},[80,163,164,168,172,176],{"class":82,"line":83},[80,165,167],{"class":166},"spNyl","const",[80,169,171],{"class":170},"sTEyZ"," startNumber ",[80,173,175],{"class":174},"sMK4o","=",[80,177,179],{"class":178},"sbssI"," 1\n",[80,181,182,184,187,189,192],{"class":82,"line":89},[80,183,167],{"class":166},[80,185,186],{"class":170}," duration ",[80,188,175],{"class":174},[80,190,191],{"class":178}," 6000",[80,193,195],{"class":194},"sHwdD","        \u002F\u002F ms\n",[80,197,198,200,203,205,208],{"class":82,"line":95},[80,199,167],{"class":166},[80,201,202],{"class":170}," timescale ",[80,204,175],{"class":174},[80,206,207],{"class":178}," 1000",[80,209,210],{"class":194},"       \u002F\u002F 1 unit = 1 ms\n",[80,212,213,215,218,220,223],{"class":82,"line":101},[80,214,167],{"class":166},[80,216,217],{"class":170}," totalDuration ",[80,219,175],{"class":174},[80,221,222],{"class":178}," 7200000",[80,224,225],{"class":194}," \u002F\u002F ms — from the Representation's @duration attribute\n",[80,227,228],{"class":82,"line":107},[80,229,231],{"emptyLinePlaceholder":230},true,"\n",[80,233,234,236,239,241,244,247,251,254,257,259,262,265,267],{"class":82,"line":113},[80,235,167],{"class":166},[80,237,238],{"class":170}," segmentCount ",[80,240,175],{"class":174},[80,242,243],{"class":170}," Math",[80,245,246],{"class":174},".",[80,248,250],{"class":249},"s2Zo4","ceil",[80,252,253],{"class":170},"(totalDuration ",[80,255,256],{"class":174},"*",[80,258,202],{"class":170},[80,260,261],{"class":174},"\u002F",[80,263,264],{"class":170}," (duration ",[80,266,256],{"class":174},[80,268,269],{"class":170}," timescale))\n",[80,271,273],{"class":82,"line":272},7,[80,274,275],{"class":194},"\u002F\u002F = 1200 segments\n",[80,277,279],{"class":82,"line":278},8,[80,280,231],{"emptyLinePlaceholder":230},[80,282,284,286,289,291],{"class":82,"line":283},9,[80,285,167],{"class":166},[80,287,288],{"class":170}," urls ",[80,290,175],{"class":174},[80,292,293],{"class":170}," []\n",[80,295,297,301,304,307,310,312,315,318,320,323,325,328,331,333,336,339,342],{"class":82,"line":296},10,[80,298,300],{"class":299},"s7zQu","for",[80,302,303],{"class":170}," (",[80,305,306],{"class":166},"let",[80,308,309],{"class":170}," n ",[80,311,175],{"class":174},[80,313,314],{"class":170}," startNumber",[80,316,317],{"class":174},";",[80,319,309],{"class":170},[80,321,322],{"class":174},"\u003C",[80,324,171],{"class":170},[80,326,327],{"class":174},"+",[80,329,330],{"class":170}," segmentCount",[80,332,317],{"class":174},[80,334,335],{"class":170}," n",[80,337,338],{"class":174},"++",[80,340,341],{"class":170},") ",[80,343,344],{"class":174},"{\n",[80,346,348,351,353,356,360,363,367,370,373,376,379,381],{"class":82,"line":347},11,[80,349,350],{"class":170},"  urls",[80,352,246],{"class":174},[80,354,355],{"class":249},"push",[80,357,359],{"class":358},"swJcz","(",[80,361,362],{"class":174},"`",[80,364,366],{"class":365},"sfazB","video\u002F",[80,368,369],{"class":174},"${",[80,371,372],{"class":170},"n",[80,374,375],{"class":174},"}",[80,377,378],{"class":365},".m4s",[80,380,362],{"class":174},[80,382,383],{"class":358},")\n",[80,385,387],{"class":82,"line":386},12,[80,388,389],{"class":174},"}\n",[11,391,392,393,396,397,399],{},"This is dramatically more compact than HLS — instead of 1200 lines of ",[21,394,395],{},"#EXTINF"," + URL, you have one ",[21,398,23],{}," element. The trade-off: the parser has to do the math, and edge cases (last segment is shorter, segment duration varies) require care.",[401,402,404],"h3",{"id":403},"number-vs-time","$Number$ vs $Time$",[11,406,407,409,410,413],{},[21,408,140],{}," is the segment index (1, 2, 3, ...). ",[21,411,412],{},"$Time$"," is the segment's start time in timescale units. Some manifests use one, some use both:",[48,415,417],{"className":74,"code":416,"language":76,"meta":56,"style":56},"\u003CSegmentTemplate\n  media=\"video\u002F$Time$.m4s\"\n  timescale=\"1000\"\n  duration=\"6000\" \u002F>\n",[21,418,419,423,428,432],{"__ignoreMap":56},[80,420,421],{"class":82,"line":83},[80,422,86],{},[80,424,425],{"class":82,"line":89},[80,426,427],{},"  media=\"video\u002F$Time$.m4s\"\n",[80,429,430],{"class":82,"line":95},[80,431,110],{},[80,433,434],{"class":82,"line":101},[80,435,116],{},[11,437,438,439,441],{},"To resolve ",[21,440,412],{},", multiply the segment index by the duration:",[48,443,445],{"className":157,"code":444,"language":159,"meta":56,"style":56},"const segmentTime = (n - startNumber) * duration\nconst url = `video\u002F${segmentTime}.m4s`\n",[21,446,447,470],{"__ignoreMap":56},[80,448,449,451,454,456,459,462,465,467],{"class":82,"line":83},[80,450,167],{"class":166},[80,452,453],{"class":170}," segmentTime ",[80,455,175],{"class":174},[80,457,458],{"class":170}," (n ",[80,460,461],{"class":174},"-",[80,463,464],{"class":170}," startNumber) ",[80,466,256],{"class":174},[80,468,469],{"class":170}," duration\n",[80,471,472,474,477,479,482,484,486,489,491,493],{"class":82,"line":89},[80,473,167],{"class":166},[80,475,476],{"class":170}," url ",[80,478,175],{"class":174},[80,480,481],{"class":174}," `",[80,483,366],{"class":365},[80,485,369],{"class":174},[80,487,488],{"class":170},"segmentTime",[80,490,375],{"class":174},[80,492,378],{"class":365},[80,494,495],{"class":174},"`\n",[11,497,498,499,502],{},"If you forget to multiply by ",[21,500,501],{},"timescale",", your URLs are off by a factor of 1000 and you get 404s. This is the most common DASH parsing bug.",[40,504,506],{"id":505},"segmenttimeline-when-durations-vary","SegmentTimeline: when durations vary",[11,508,509,510,512,513,154],{},"Live streams and dynamic content use ",[21,511,27],{}," instead of fixed ",[21,514,515],{},"duration",[48,517,519],{"className":74,"code":518,"language":76,"meta":56,"style":56},"\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 means repeat 3 times: 4 segments total -->\n    \u003CS t=\"40000\" d=\"6000\" \u002F>\n  \u003C\u002FSegmentTimeline>\n\u003C\u002FSegmentTemplate>\n",[21,520,521,526,531,536,541,546,551,556,561],{"__ignoreMap":56},[80,522,523],{"class":82,"line":83},[80,524,525],{},"\u003CSegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" media=\"$Number$.m4s\">\n",[80,527,528],{"class":82,"line":89},[80,529,530],{},"  \u003CSegmentTimeline>\n",[80,532,533],{"class":82,"line":95},[80,534,535],{},"    \u003CS t=\"0\" d=\"6000\" \u002F>\n",[80,537,538],{"class":82,"line":101},[80,539,540],{},"    \u003CS t=\"6000\" d=\"6000\" \u002F>\n",[80,542,543],{"class":82,"line":107},[80,544,545],{},"    \u003CS t=\"12000\" d=\"4000\" \u002F>\n",[80,547,548],{"class":82,"line":113},[80,549,550],{},"    \u003CS t=\"16000\" d=\"6000\" r=\"3\" \u002F>  \u003C!-- r=3 means repeat 3 times: 4 segments total -->\n",[80,552,553],{"class":82,"line":272},[80,554,555],{},"    \u003CS t=\"40000\" d=\"6000\" \u002F>\n",[80,557,558],{"class":82,"line":278},[80,559,560],{},"  \u003C\u002FSegmentTimeline>\n",[80,562,563],{"class":82,"line":283},[80,564,565],{},"\u003C\u002FSegmentTemplate>\n",[121,567,568,574,580],{},[124,569,570,573],{},[21,571,572],{},"t"," — start time in timescale units",[124,575,576,579],{},[21,577,578],{},"d"," — duration in timescale units",[124,581,582,585,586,589],{},[21,583,584],{},"r"," — repeat count (this entry represents ",[21,587,588],{},"r+1"," segments)",[11,591,592],{},"For live streams, this timeline grows over time. A typical pattern:",[594,595,596,603,606,609],"ol",{},[124,597,598,599,602],{},"Client fetches MPD, sees segments 1-50 with a ",[21,600,601],{},"minimumUpdatePeriod"," of 2 seconds",[124,604,605],{},"Client plays through segments 1-50",[124,607,608],{},"Client re-fetches MPD; now it shows segments 1-52 (timeline grew)",[124,610,611,612,615],{},"Repeat until ",[21,613,614],{},"MPD@endOfAvailability"," or the stream ends",[11,617,618],{},"For a downloader, you typically want to either:",[121,620,621,624],{},[124,622,623],{},"Wait until the stream ends (VOD conversion) — not always possible",[124,625,626],{},"Snapshot the timeline as-is and accept you'll miss content added later",[11,628,629],{},"FlowPick takes the snapshot approach: capture the current timeline, download those segments, merge. If the stream is still live, the user can re-trigger to get newer segments.",[40,631,633],{"id":632},"contentprotection-drm-signaling","ContentProtection: DRM signaling",[48,635,637],{"className":74,"code":636,"language":76,"meta":56,"style":56},"\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",[21,638,639,644,649,654,659,663,667,672,677,682,687,691,695,701,707,713],{"__ignoreMap":56},[80,640,641],{"class":82,"line":83},[80,642,643],{},"\u003CContentProtection\n",[80,645,646],{"class":82,"line":89},[80,647,648],{},"  schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\"\n",[80,650,651],{"class":82,"line":95},[80,652,653],{},"  value=\"cenc\"\n",[80,655,656],{"class":82,"line":101},[80,657,658],{},"  cenc:default_KID=\"12345678-1234-1234-1234-123456789012\" \u002F>\n",[80,660,661],{"class":82,"line":107},[80,662,231],{"emptyLinePlaceholder":230},[80,664,665],{"class":82,"line":113},[80,666,643],{},[80,668,669],{"class":82,"line":272},[80,670,671],{},"  schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n",[80,673,674],{"class":82,"line":278},[80,675,676],{},"  value=\"Widevine\">\n",[80,678,679],{"class":82,"line":283},[80,680,681],{},"  \u003Ccenc:pssh>AAAA...\u003C\u002Fcenc:pssh>\n",[80,683,684],{"class":82,"line":296},[80,685,686],{},"\u003C\u002FContentProtection>\n",[80,688,689],{"class":82,"line":347},[80,690,231],{"emptyLinePlaceholder":230},[80,692,693],{"class":82,"line":386},[80,694,643],{},[80,696,698],{"class":82,"line":697},13,[80,699,700],{},"  schemeIdUri=\"urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95\"\n",[80,702,704],{"class":82,"line":703},14,[80,705,706],{},"  value=\"PlayReady\">\n",[80,708,710],{"class":82,"line":709},15,[80,711,712],{},"  \u003Ccenc:pssh>BBBB...\u003C\u002Fcenc:pssh>\n",[80,714,716],{"class":82,"line":715},16,[80,717,686],{},[11,719,720],{},"The UUIDs identify DRM systems:",[121,722,723,729,735,741],{},[124,724,725,728],{},[21,726,727],{},"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"," — Widevine (Google)",[124,730,731,734],{},[21,732,733],{},"9a04f079-9840-4286-ab92-e65be0885f95"," — PlayReady (Microsoft)",[124,736,737,740],{},[21,738,739],{},"f239e769-efa3-4850-9c16-a9036d3fe1c1"," — Adobe Primetime",[124,742,743,746],{},[21,744,745],{},"94ce86fb-07ff-4f43-adb8-93d2fa968ca2"," — FairPlay (Apple, in DASH context)",[11,748,13,749,752],{},[21,750,751],{},"\u003Ccenc:pssh>"," element is a Protection System Specific Header — a base64 blob containing the key ID and system-specific data. Browsers pass this to EME (Encrypted Media Extensions) which talks to the platform's CDM (Content Decryption Module).",[11,754,755,758],{},[64,756,757],{},"Here's the line:"," FlowPick does not, will not, and cannot decrypt DRM-protected DASH content. Neither can any legitimate browser extension or open-source tool. The CDM is a black box — you give it an encrypted buffer, it gives you decrypted frames for display only, never for export. Bypassing this is illegal under anti-circumvention laws (DMCA §1201 in the US, similar elsewhere) and breaks the CDM's contract.",[11,760,761,762,765],{},"The DASH spec includes an \"unprotected\" path: if ",[21,763,764],{},"ContentProtection"," is absent from an AdaptationSet, segments are unencrypted and a downloader can fetch and merge them freely. This is why FlowPick works on Bilibili, Udemy-free, and most corporate LMS streams, but not on Netflix\u002FDisney+\u002FHBO Max.",[401,767,769],{"id":768},"common-drm-free-but-protected-feeling-patterns","Common DRM-free but \"protected-feeling\" patterns",[11,771,772],{},"Not every \"can't download this\" is DRM. Some patterns make download hard without actual encryption:",[594,774,775,785,791],{},[124,776,777,780,781,784],{},[64,778,779],{},"Tokenized URLs with short TTL"," — ",[21,782,783],{},"?token=abc&expires=1234567890",". Tokens expire in seconds; you have to fetch segments immediately after parsing the manifest. FlowPick does this; batch downloaders that parse URLs hours in advance fail.",[124,786,787,790],{},[64,788,789],{},"CORS restrictions on the segment CDN"," — but extensions aren't subject to CORS for fetches from the page's origin, so this rarely stops a sniffer.",[124,792,793,799,800,803,804,806],{},[64,794,795,798],{},[21,796,797],{},"\u003CBaseURL>"," rewriting"," — the MPD's ",[21,801,802],{},"BaseURL"," element can change mid-document. Each ",[21,805,802],{}," is resolved relative to its parent, so you have to track the URL chain. This is the DASH equivalent of HLS relative URIs, but more error-prone.",[40,808,810],{"id":809},"multi-period-mpds-ads-and-live-events","Multi-period MPDs: ads and live events",[48,812,814],{"className":74,"code":813,"language":76,"meta":56,"style":56},"\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",[21,815,816,821,826,831],{"__ignoreMap":56},[80,817,818],{"class":82,"line":83},[80,819,820],{},"\u003CPeriod id=\"ad1\" start=\"PT0S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[80,822,823],{"class":82,"line":89},[80,824,825],{},"\u003CPeriod id=\"main\" start=\"PT30S\">...\u003C\u002FPeriod>\n",[80,827,828],{"class":82,"line":95},[80,829,830],{},"\u003CPeriod id=\"ad2\" start=\"PT1800S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[80,832,833],{"class":82,"line":101},[80,834,835],{},"\u003CPeriod id=\"main2\" start=\"PT1830S\">...\u003C\u002FPeriod>\n",[11,837,838],{},"Each Period has its own AdaptationSets. To download the full presentation:",[594,840,841,847,850],{},[124,842,843,844],{},"Iterate periods in order of ",[21,845,846],{},"@start",[124,848,849],{},"For each period, download all video + audio segments",[124,851,852],{},"Concatenate the periods — but watch for codec changes between periods",[11,854,855],{},"Ad breaks commonly use different codecs than the main content (the ad might be H.264 baseline while the movie is H.264 high). A naive concatenation produces an MP4 that plays the main content fine but shows corruption during ads.",[11,857,858],{},"The fix is either:",[594,860,861,864,867],{},[124,862,863],{},"Skip ad periods entirely (FlowPick's approach — most users don't want ads in their download anyway)",[124,865,866],{},"Re-encode ad periods to match the main content (slow, requires transcoding)",[124,868,869],{},"Use a smarter muxer that handles codec switches (FFmpeg does, but it's complex)",[40,871,873],{"id":872},"multi-view-and-trick-mode-adaptationsets","Multi-view and trick-mode AdaptationSets",[48,875,877],{"className":74,"code":876,"language":76,"meta":56,"style":56},"\u003CAdaptationSet id=\"1\" contentType=\"video\" group=\"1\">\n  \u003C!-- Main camera angles -->\n\u003C\u002FAdaptationSet>\n\u003CAdaptationSet id=\"2\" contentType=\"video\" group=\"1\">\n  \u003C!-- Alternate camera angles, same group -->\n\u003C\u002FAdaptationSet>\n\u003CAdaptationSet id=\"3\" contentType=\"video\" group=\"2\">\n  \u003C!-- Trick mode (thumbnails for seek bar) -->\n\u003C\u002FAdaptationSet>\n",[21,878,879,884,889,894,899,904,908,913,918],{"__ignoreMap":56},[80,880,881],{"class":82,"line":83},[80,882,883],{},"\u003CAdaptationSet id=\"1\" contentType=\"video\" group=\"1\">\n",[80,885,886],{"class":82,"line":89},[80,887,888],{},"  \u003C!-- Main camera angles -->\n",[80,890,891],{"class":82,"line":95},[80,892,893],{},"\u003C\u002FAdaptationSet>\n",[80,895,896],{"class":82,"line":101},[80,897,898],{},"\u003CAdaptationSet id=\"2\" contentType=\"video\" group=\"1\">\n",[80,900,901],{"class":82,"line":107},[80,902,903],{},"  \u003C!-- Alternate camera angles, same group -->\n",[80,905,906],{"class":82,"line":113},[80,907,893],{},[80,909,910],{"class":82,"line":272},[80,911,912],{},"\u003CAdaptationSet id=\"3\" contentType=\"video\" group=\"2\">\n",[80,914,915],{"class":82,"line":278},[80,916,917],{},"  \u003C!-- Trick mode (thumbnails for seek bar) -->\n",[80,919,920],{"class":82,"line":283},[80,921,893],{},[11,923,924,927],{},[21,925,926],{},"group=\"1\""," means these AdaptationSets are interchangeable views of the same content. A player lets the user switch angles. A downloader needs to pick one — usually the first, but ideally let the user choose.",[11,929,930],{},"Trick-mode AdaptationSets (group=2 here) are tiny low-FPS versions used for the scrub preview on the seek bar. They're useless for a download — make sure your parser doesn't accidentally grab them as the \"video.\"",[40,932,934],{"id":933},"pre-fetching-and-parallelism","Pre-fetching and parallelism",[11,936,937],{},"The biggest practical win in DASH download speed: fetch segments in parallel.",[48,939,941],{"className":157,"code":940,"language":159,"meta":56,"style":56},"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 Concatenate: init + segments in order\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",[21,942,943,977,996,1045,1049,1076,1089,1093,1108,1131,1146,1172,1204,1209,1214,1218,1267,1272,1278,1339,1360,1372,1417,1439,1472,1477,1486],{"__ignoreMap":56},[80,944,945,948,951,954,956,960,963,966,969,972,974],{"class":82,"line":83},[80,946,947],{"class":166},"async",[80,949,950],{"class":166}," function",[80,952,953],{"class":249}," downloadRepresentation",[80,955,359],{"class":174},[80,957,959],{"class":958},"sHdIc","rep",[80,961,962],{"class":174},",",[80,964,965],{"class":958}," concurrency",[80,967,968],{"class":174}," =",[80,970,971],{"class":178}," 6",[80,973,148],{"class":174},[80,975,976],{"class":174}," {\n",[80,978,979,982,985,987,990,992,994],{"class":82,"line":89},[80,980,981],{"class":166},"  const",[80,983,984],{"class":170}," segmentUrls",[80,986,968],{"class":174},[80,988,989],{"class":249}," computeSegmentUrls",[80,991,359],{"class":358},[80,993,959],{"class":170},[80,995,383],{"class":358},[80,997,998,1000,1003,1005,1008,1011,1013,1015,1017,1020,1022,1024,1027,1029,1031,1034,1037,1039,1042],{"class":82,"line":95},[80,999,981],{"class":166},[80,1001,1002],{"class":170}," initBytes",[80,1004,968],{"class":174},[80,1006,1007],{"class":299}," await",[80,1009,1010],{"class":249}," fetch",[80,1012,359],{"class":358},[80,1014,959],{"class":170},[80,1016,246],{"class":174},[80,1018,1019],{"class":170},"initUrl",[80,1021,148],{"class":358},[80,1023,246],{"class":174},[80,1025,1026],{"class":249},"then",[80,1028,359],{"class":358},[80,1030,584],{"class":958},[80,1032,1033],{"class":166}," =>",[80,1035,1036],{"class":170}," r",[80,1038,246],{"class":174},[80,1040,1041],{"class":249},"arrayBuffer",[80,1043,1044],{"class":358},"())\n",[80,1046,1047],{"class":82,"line":101},[80,1048,231],{"emptyLinePlaceholder":230},[80,1050,1051,1053,1056,1058,1061,1064,1066,1069,1071,1074],{"class":82,"line":107},[80,1052,981],{"class":166},[80,1054,1055],{"class":170}," results",[80,1057,968],{"class":174},[80,1059,1060],{"class":174}," new",[80,1062,1063],{"class":249}," Array",[80,1065,359],{"class":358},[80,1067,1068],{"class":170},"segmentUrls",[80,1070,246],{"class":174},[80,1072,1073],{"class":170},"length",[80,1075,383],{"class":358},[80,1077,1078,1081,1084,1086],{"class":82,"line":113},[80,1079,1080],{"class":166},"  let",[80,1082,1083],{"class":170}," nextIndex",[80,1085,968],{"class":174},[80,1087,1088],{"class":178}," 0\n",[80,1090,1091],{"class":82,"line":272},[80,1092,231],{"emptyLinePlaceholder":230},[80,1094,1095,1098,1100,1103,1106],{"class":82,"line":278},[80,1096,1097],{"class":166},"  async",[80,1099,950],{"class":166},[80,1101,1102],{"class":249}," worker",[80,1104,1105],{"class":174},"()",[80,1107,976],{"class":174},[80,1109,1110,1113,1115,1118,1121,1123,1125,1127,1129],{"class":82,"line":283},[80,1111,1112],{"class":299},"    while",[80,1114,303],{"class":358},[80,1116,1117],{"class":170},"nextIndex",[80,1119,1120],{"class":174}," \u003C",[80,1122,984],{"class":170},[80,1124,246],{"class":174},[80,1126,1073],{"class":170},[80,1128,341],{"class":358},[80,1130,344],{"class":174},[80,1132,1133,1136,1139,1141,1143],{"class":82,"line":296},[80,1134,1135],{"class":166},"      const",[80,1137,1138],{"class":170}," i",[80,1140,968],{"class":174},[80,1142,1083],{"class":170},[80,1144,1145],{"class":174},"++\n",[80,1147,1148,1150,1153,1155,1157,1159,1161,1163,1166,1169],{"class":82,"line":347},[80,1149,1135],{"class":166},[80,1151,1152],{"class":170}," resp",[80,1154,968],{"class":174},[80,1156,1007],{"class":299},[80,1158,1010],{"class":249},[80,1160,359],{"class":358},[80,1162,1068],{"class":170},[80,1164,1165],{"class":358},"[",[80,1167,1168],{"class":170},"i",[80,1170,1171],{"class":358},"])\n",[80,1173,1174,1177,1179,1181,1184,1186,1188,1191,1193,1196,1198,1200,1202],{"class":82,"line":386},[80,1175,1176],{"class":170},"      results",[80,1178,1165],{"class":358},[80,1180,1168],{"class":170},[80,1182,1183],{"class":358},"] ",[80,1185,175],{"class":174},[80,1187,1060],{"class":174},[80,1189,1190],{"class":249}," Uint8Array",[80,1192,359],{"class":358},[80,1194,1195],{"class":299},"await",[80,1197,1152],{"class":170},[80,1199,246],{"class":174},[80,1201,1041],{"class":249},[80,1203,1044],{"class":358},[80,1205,1206],{"class":82,"line":697},[80,1207,1208],{"class":174},"    }\n",[80,1210,1211],{"class":82,"line":703},[80,1212,1213],{"class":174},"  }\n",[80,1215,1216],{"class":82,"line":709},[80,1217,231],{"emptyLinePlaceholder":230},[80,1219,1220,1223,1227,1229,1232,1234,1237,1239,1242,1244,1247,1250,1252,1254,1257,1260,1262,1264],{"class":82,"line":715},[80,1221,1222],{"class":299},"  await",[80,1224,1226],{"class":1225},"sBMFI"," Promise",[80,1228,246],{"class":174},[80,1230,1231],{"class":249},"all",[80,1233,359],{"class":358},[80,1235,1236],{"class":170},"Array",[80,1238,246],{"class":174},[80,1240,1241],{"class":249},"from",[80,1243,359],{"class":358},[80,1245,1246],{"class":174},"{",[80,1248,1249],{"class":358}," length",[80,1251,154],{"class":174},[80,1253,965],{"class":170},[80,1255,1256],{"class":174}," },",[80,1258,1259],{"class":174}," ()",[80,1261,1033],{"class":166},[80,1263,1102],{"class":249},[80,1265,1266],{"class":358},"()))\n",[80,1268,1270],{"class":82,"line":1269},17,[80,1271,231],{"emptyLinePlaceholder":230},[80,1273,1275],{"class":82,"line":1274},18,[80,1276,1277],{"class":194},"  \u002F\u002F Concatenate: init + segments in order\n",[80,1279,1281,1283,1286,1288,1290,1292,1295,1298,1300,1302,1305,1307,1309,1312,1314,1317,1319,1321,1324,1326,1328,1330,1332,1334,1337],{"class":82,"line":1280},19,[80,1282,981],{"class":166},[80,1284,1285],{"class":170}," total",[80,1287,968],{"class":174},[80,1289,1002],{"class":170},[80,1291,246],{"class":174},[80,1293,1294],{"class":170},"byteLength",[80,1296,1297],{"class":174}," +",[80,1299,1055],{"class":170},[80,1301,246],{"class":174},[80,1303,1304],{"class":249},"reduce",[80,1306,359],{"class":358},[80,1308,359],{"class":174},[80,1310,1311],{"class":958},"s",[80,1313,962],{"class":174},[80,1315,1316],{"class":958}," b",[80,1318,148],{"class":174},[80,1320,1033],{"class":166},[80,1322,1323],{"class":170}," s",[80,1325,1297],{"class":174},[80,1327,1316],{"class":170},[80,1329,246],{"class":174},[80,1331,1294],{"class":170},[80,1333,962],{"class":174},[80,1335,1336],{"class":178}," 0",[80,1338,383],{"class":358},[80,1340,1342,1344,1347,1349,1351,1353,1355,1358],{"class":82,"line":1341},20,[80,1343,981],{"class":166},[80,1345,1346],{"class":170}," out",[80,1348,968],{"class":174},[80,1350,1060],{"class":174},[80,1352,1190],{"class":249},[80,1354,359],{"class":358},[80,1356,1357],{"class":170},"total",[80,1359,383],{"class":358},[80,1361,1363,1365,1368,1370],{"class":82,"line":1362},21,[80,1364,1080],{"class":166},[80,1366,1367],{"class":170}," offset",[80,1369,968],{"class":174},[80,1371,1088],{"class":178},[80,1373,1375,1378,1380,1383,1385,1388,1390,1392,1395,1397,1399,1401,1403,1405,1407,1410,1412,1414],{"class":82,"line":1374},22,[80,1376,1377],{"class":170},"  out",[80,1379,246],{"class":174},[80,1381,1382],{"class":249},"set",[80,1384,359],{"class":358},[80,1386,1387],{"class":174},"new",[80,1389,1190],{"class":249},[80,1391,359],{"class":358},[80,1393,1394],{"class":170},"initBytes",[80,1396,148],{"class":358},[80,1398,962],{"class":174},[80,1400,1367],{"class":170},[80,1402,148],{"class":358},[80,1404,317],{"class":174},[80,1406,1367],{"class":170},[80,1408,1409],{"class":174}," +=",[80,1411,1002],{"class":170},[80,1413,246],{"class":174},[80,1415,1416],{"class":170},"byteLength\n",[80,1418,1420,1423,1425,1427,1430,1433,1435,1437],{"class":82,"line":1419},23,[80,1421,1422],{"class":299},"  for",[80,1424,303],{"class":358},[80,1426,167],{"class":166},[80,1428,1429],{"class":170}," buf",[80,1431,1432],{"class":174}," of",[80,1434,1055],{"class":170},[80,1436,341],{"class":358},[80,1438,344],{"class":174},[80,1440,1442,1445,1447,1449,1451,1454,1456,1458,1460,1462,1464,1466,1468,1470],{"class":82,"line":1441},24,[80,1443,1444],{"class":170},"    out",[80,1446,246],{"class":174},[80,1448,1382],{"class":249},[80,1450,359],{"class":358},[80,1452,1453],{"class":170},"buf",[80,1455,962],{"class":174},[80,1457,1367],{"class":170},[80,1459,148],{"class":358},[80,1461,317],{"class":174},[80,1463,1367],{"class":170},[80,1465,1409],{"class":174},[80,1467,1429],{"class":170},[80,1469,246],{"class":174},[80,1471,1416],{"class":170},[80,1473,1475],{"class":82,"line":1474},25,[80,1476,1213],{"class":174},[80,1478,1480,1483],{"class":82,"line":1479},26,[80,1481,1482],{"class":299},"  return",[80,1484,1485],{"class":170}," out\n",[80,1487,1489],{"class":82,"line":1488},27,[80,1490,389],{"class":174},[11,1492,1493,1494,1497],{},"Six concurrent connections is the sweet spot — more triggers CDN rate limiting, fewer leaves bandwidth on the table. The ",[21,1495,1496],{},"Promise.all"," worker pool pattern ensures segments land in the right array slots even if they complete out of order.",[11,1499,1500,1501,1505],{},"This is exactly the pattern FlowPick uses; the ",[15,1502,1504],{"href":1503},"\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","in-browser merge writeup"," covers how the resulting buffer gets muxed.",[40,1507,1509],{"id":1508},"common-pitfalls","Common pitfalls",[11,1511,1512,1517,1518,1521,1522,1525,1526,1529],{},[64,1513,1514,1515,246],{},"Pitfall 1: ignoring ",[21,1516,802],{}," If the MPD is at ",[21,1519,1520],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmanifest.mpd"," and contains ",[21,1523,1524],{},"\u003CBaseURL>https:\u002F\u002Fother-cdn.example.com\u002F\u003C\u002FBaseURL>",", all relative URLs in that section resolve against ",[21,1527,1528],{},"other-cdn.example.com",", not the MPD's origin. Forgetting this is the #1 cause of \"404 on every segment\" bugs.",[11,1531,1532,1537,1538,1541,1542,1545,1546,1549,1550,1552],{},[64,1533,1534,1535,246],{},"Pitfall 2: misparsing ",[21,1536,501],{}," ",[21,1539,1540],{},"duration=\"6000\""," with ",[21,1543,1544],{},"timescale=\"1000\""," means 6 seconds. With ",[21,1547,1548],{},"timescale=\"90000\""," (common for video), it means 0.067 seconds. Always check ",[21,1551,501],{}," — it can differ between AdaptationSets in the same MPD.",[11,1554,1555,1558,1559,246],{},[64,1556,1557],{},"Pitfall 3: forgetting the init segment."," Unlike HLS where (in the TS case) each segment is self-describing, DASH fMP4 segments are useless without the init segment. A download that \"works\" but produces an unplayable file usually missed ",[21,1560,1561],{},"initialization=\"init.mp4\"",[11,1563,1564,1567],{},[64,1565,1566],{},"Pitfall 4: live MPDs that update."," If you parse the MPD once and download based on that snapshot, you'll miss segments added after your parse. For VOD, this is fine. For live, you have to either re-fetch periodically or wait for the stream to end.",[40,1569,1571],{"id":1570},"references","References",[121,1573,1574,1583,1591,1599],{},[124,1575,1576,1582],{},[15,1577,1581],{"href":1578,"rel":1579},"https:\u002F\u002Fwww.iso.org\u002Fstandard\u002F79329.html",[1580],"nofollow","ISO\u002FIEC 23009-1 — DASH specification"," — The actual spec, paywalled but the definitive source",[124,1584,1585,1590],{},[15,1586,1589],{"href":1587,"rel":1588},"https:\u002F\u002Fdashif.org\u002Fguidelines\u002F",[1580],"DASH-IF Guidelines"," — Implementation guide from the DASH Industry Forum; free and more practical than the ISO doc",[124,1592,1593,1598],{},[15,1594,1597],{"href":1595,"rel":1596},"https:\u002F\u002Fgpac.wp.imt.fr\u002Fmp4box\u002F",[1580],"GPAC MP4Box docs"," — The reference MP4\u002FDASH tool; useful for validating your parser against",[124,1600,1601,1606],{},[15,1602,1605],{"href":1603,"rel":1604},"https:\u002F\u002Fgithub.com\u002Faxiomatic-systems\u002FBento4",[1580],"Bento4 MP4 docs"," — Open source MP4 toolkit, useful for understanding the container format",[40,1608,1610],{"id":1609},"summary","Summary",[11,1612,1613],{},"DASH and HLS solve the same problem — adaptive streaming over HTTP — with opposite philosophies. HLS lists every segment explicitly; DASH prefers templates. HLS uses MPEG-TS by default; DASH uses fMP4. HLS DRM is Apple-specific (FairPlay); DASH DRM is multi-vendor (Widevine, PlayReady, FairPlay) via CENC.",[11,1615,1616,1617,1619,1620,24,1623,24,1626,1628,1629,1631],{},"For a downloader, DASH is structurally easier: fMP4 segments concatenate cleanly, init segments make codec info explicit, and ",[21,1618,140],{}," substitution means you can compute URLs without fetching additional playlists. The complexity is in the XML parsing — but once you have ",[21,1621,1622],{},"SegmentTemplate",[21,1624,1625],{},"SegmentTimeline",[21,1627,802],{},", and ",[21,1630,764],{}," handled, you've covered 95% of real-world MPDs.",[11,1633,1634,1635,246],{},"The next article in this series gets into the container format itself — ",[15,1636,1638,1639,1642],{"href":1637},"\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","why fMP4 concatenation works, what's in an ",[21,1640,1641],{},"moof"," box, and why \"remux not transcode\" is the right philosophy",[1644,1645],"hr",{},[40,1647,1649],{"id":1648},"related-articles","Related articles",[121,1651,1652,1660,1669,1677],{},[124,1653,1654,1659],{},[64,1655,1656],{},[15,1657,1658],{"href":17},"What Is DASH Streaming? MPD File Explained"," — The beginner version of this article",[124,1661,1662,1668],{},[64,1663,1664],{},[15,1665,1667],{"href":1666},"\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack","HLS Deep Dive: Encryption, Multi-Track Audio, EXT-X Tags"," — The HLS counterpart",[124,1670,1671,1676],{},[64,1672,1673],{},[15,1674,1675],{"href":1637},"Browser-Side Video Remuxing: fMP4, ISOBMFF, Why We Don't Transcode"," — What happens to DASH segments after download",[124,1678,1679,1685],{},[64,1680,1681],{},[15,1682,1684],{"href":1683},"\u002Fblog\u002Fis-it-legal-to-download-streaming-video","Is It Legal to Download Streaming Video?"," — Why ContentProtection tags are a hard stop",[40,1687,1689],{"id":1688},"recommended-reading","Recommended reading",[121,1691,1692,1700,1709],{},[124,1693,1694,1699],{},[64,1695,1696],{},[15,1697,1698],{"href":1503},"How FlowPick Merges Hundreds of Video Segments in Your Browser"," — FlowPick's implementation of the patterns described here",[124,1701,1702,1708],{},[64,1703,1704],{},[15,1705,1707],{"href":1706},"\u002Fblog\u002Fhow-to-download-bilibili-videos","How to Download Bilibili Videos"," — A real DASH stream in the wild",[124,1710,1711,1717],{},[64,1712,1713],{},[15,1714,1716],{"href":1715},"\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS: Practical Video Processing"," — Lower-level browser APIs that complement FFmpeg WASM",[1719,1720,1721],"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":56,"searchDepth":89,"depth":89,"links":1723},[1724,1725,1728,1729,1732,1733,1734,1735,1736,1737,1738,1739],{"id":42,"depth":89,"text":43},{"id":70,"depth":89,"text":71,"children":1726},[1727],{"id":403,"depth":95,"text":404},{"id":505,"depth":89,"text":506},{"id":632,"depth":89,"text":633,"children":1730},[1731],{"id":768,"depth":95,"text":769},{"id":809,"depth":89,"text":810},{"id":872,"depth":89,"text":873},{"id":933,"depth":89,"text":934},{"id":1508,"depth":89,"text":1509},{"id":1570,"depth":89,"text":1571},{"id":1609,"depth":89,"text":1610},{"id":1648,"depth":89,"text":1649},{"id":1688,"depth":89,"text":1689},"tips","2026-08-04","Past the beginner MPD explainer — SegmentTemplate vs SegmentTimeline, $Number$ vs $Time$, ContentProtection signaling, and the structural reasons DASH is harder to parse but easier to download than HLS.","md","\u002Fscreenshots\u002Fdash-downloader.png",{},"\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",{"title":5,"description":1742},"blog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",[1750,1751,1752,1753,1754,1755],"dash","mpd","deep-dive","segmenttemplate","drm","streaming","iU7CqkdriqVpRfkjLaIi5_t-1OnKBKZsG7-r9R5AG8E",[1758,1763],{"title":1759,"path":1760,"stem":1761,"description":1762,"date":1741,"category":1740,"children":-1},"Browser Video Processing Performance Benchmarks: FFmpeg WASM vs WebCodecs vs Native","\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","blog\u002Fbrowser-video-processing-performance-benchmarks","Real numbers from real hardware — 1080p\u002F4K\u002F8K remux and decode benchmarks across FFmpeg WASM, WebCodecs, and native FFmpeg. With methodology, raw data, and what the numbers actually mean.",{"title":1764,"path":1765,"stem":1766,"description":1767,"date":1768,"category":1769,"children":-1},"FlowPick v1.0.0 Release Notes: The First Public Drop","\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","blog\u002Fflowpick-v1-0-0-first-public-release","FlowPick v1.0.0 is live — a browser extension that auto-detects HLS\u002FDASH streams, audio, and images, merges fragments in-browser via WebAssembly FFmpeg, and keeps everything local. Here's what shipped on day one.","2025-04-20","updates",1787670588608]