[{"data":1,"prerenderedAt":1752},["ShallowReactive",2],{"blog-zh-Hans-dash-mpd-deep-dive-segmenttemplate-contentprotection":3,"blog-zh-Hans-dash-mpd-deep-dive-segmenttemplate-contentprotection-surround":1739},{"id":4,"title":5,"author":6,"body":7,"category":1722,"date":1723,"description":1724,"extension":1725,"image":1726,"lastModified":1723,"meta":1727,"navigation":228,"path":1728,"readingTime":708,"seo":1729,"stem":1730,"tags":1731,"__hash__":1738},"blog_zh_Hans\u002Fzh-Hans\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection.md","DASH 深度：SegmentTemplate、ContentProtection 与多视角 MPD 结构","FlowPick 团队",{"type":8,"value":9,"toc":1705},"minimark",[10,34,37,42,45,55,58,66,70,115,118,147,153,388,398,403,412,434,440,494,501,505,515,565,589,592,615,618,626,629,633,717,720,746,752,758,765,769,772,806,810,835,838,853,856,859,870,874,922,928,931,934,937,1492,1499,1507,1510,1531,1555,1564,1570,1573,1608,1611,1614,1631,1642,1645,1648,1701],[11,12,13,18,19,23,24,23,27,23,30,33],"p",{},[14,15,17],"a",{"href":16},"\u002Fzh-Hans\u002Fblog\u002Fwhat-is-dash-mpd-streaming","MPD 入门指南"," 讲了什么是 MPD、为什么跳出来的是 12KB XML 而不是 MP4。这篇是续集——给那些打开过 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                  # 一段内容（如正片、广告）\n│   ├── AdaptationSet       # 一组可互换的版本（视频、音频等）\n│   │   ├── Representation  # 一个具体版本（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 有 1 个 Period、2 个 AdaptationSet（视频+音频）、每 set 5-10 个 Representation。带广告的直播可能有 4+ 个 Period。多机位体育直播每个机位一个 AdaptationSet。",[11,59,60,61,65],{},"核心思想：",[62,63,64],"strong",{},"DASH 一切设计都为了不必列出每个分片的 URL。"," HLS 一个一个列，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 的初始化 box）",[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 毫秒（6 秒，因为 ",[20,144,145],{},"timescale=1000","）",[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 被一个 ",[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,437,439],{},"解析 ",[20,438,410],{},"，用分片索引乘时长：",[46,441,443],{"className":155,"code":442,"language":157,"meta":54,"style":54},"const segmentTime = (n - startNumber) * duration\nconst url = `video\u002F${segmentTime}.m4s`\n",[20,444,445,468],{"__ignoreMap":54},[78,446,447,449,452,454,457,460,463,465],{"class":80,"line":81},[78,448,165],{"class":164},[78,450,451],{"class":168}," segmentTime ",[78,453,173],{"class":172},[78,455,456],{"class":168}," (n ",[78,458,459],{"class":172},"-",[78,461,462],{"class":168}," startNumber) ",[78,464,254],{"class":172},[78,466,467],{"class":168}," duration\n",[78,469,470,472,475,477,480,482,484,487,489,491],{"class":80,"line":87},[78,471,165],{"class":164},[78,473,474],{"class":168}," url ",[78,476,173],{"class":172},[78,478,479],{"class":172}," `",[78,481,364],{"class":363},[78,483,367],{"class":172},[78,485,486],{"class":168},"segmentTime",[78,488,373],{"class":172},[78,490,376],{"class":363},[78,492,493],{"class":172},"`\n",[11,495,496,497,500],{},"忘了乘 ",[20,498,499],{},"timescale","，URL 差 1000 倍，全部 404。这是 DASH 解析最常见的 bug。",[38,502,504],{"id":503},"segmenttimeline时长不固定时","SegmentTimeline：时长不固定时",[11,506,507,508,510,511,514],{},"直播和动态内容用 ",[20,509,26],{}," 而不是固定的 ",[20,512,513],{},"duration","：",[46,516,518],{"className":72,"code":517,"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,519,520,525,530,535,540,545,550,555,560],{"__ignoreMap":54},[78,521,522],{"class":80,"line":81},[78,523,524],{},"\u003CSegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" media=\"$Number$.m4s\">\n",[78,526,527],{"class":80,"line":87},[78,528,529],{},"  \u003CSegmentTimeline>\n",[78,531,532],{"class":80,"line":93},[78,533,534],{},"    \u003CS t=\"0\" d=\"6000\" \u002F>\n",[78,536,537],{"class":80,"line":99},[78,538,539],{},"    \u003CS t=\"6000\" d=\"6000\" \u002F>\n",[78,541,542],{"class":80,"line":105},[78,543,544],{},"    \u003CS t=\"12000\" d=\"4000\" \u002F>\n",[78,546,547],{"class":80,"line":111},[78,548,549],{},"    \u003CS t=\"16000\" d=\"6000\" r=\"3\" \u002F>  \u003C!-- r=3 表示重复 3 次：共 4 个分片 -->\n",[78,551,552],{"class":80,"line":270},[78,553,554],{},"    \u003CS t=\"40000\" d=\"6000\" \u002F>\n",[78,556,557],{"class":80,"line":276},[78,558,559],{},"  \u003C\u002FSegmentTimeline>\n",[78,561,562],{"class":80,"line":281},[78,563,564],{},"\u003C\u002FSegmentTemplate>\n",[119,566,567,573,579],{},[122,568,569,572],{},[20,570,571],{},"t"," — 起始时间（timescale 单位）",[122,574,575,578],{},[20,576,577],{},"d"," — 时长（timescale 单位）",[122,580,581,584,585,588],{},[20,582,583],{},"r"," — 重复次数（这条表示 ",[20,586,587],{},"r+1"," 个分片）",[11,590,591],{},"直播流的 timeline 会随时间增长。典型流程：",[593,594,595,602,605,608],"ol",{},[122,596,597,598,601],{},"客户端拉 MPD，看到分片 1-50，",[20,599,600],{},"minimumUpdatePeriod"," 是 2 秒",[122,603,604],{},"客户端播放 1-50",[122,606,607],{},"客户端重拉 MPD，现在显示 1-52（timeline 长了）",[122,609,610,611,614],{},"重复，直到 ",[20,612,613],{},"MPD@endOfAvailability"," 或流结束",[11,616,617],{},"下载器一般两条路：",[119,619,620,623],{},[122,621,622],{},"等流彻底结束（转 VOD）—— 不总可行",[122,624,625],{},"把当前 timeline 快照下来，接受会错过后续追加的内容",[11,627,628],{},"FlowPick 走快照路线：抓当前 timeline、下这些分片、合并。流还活着，用户能再触发一次抓更新的分片。",[38,630,632],{"id":631},"contentprotectiondrm-信令","ContentProtection：DRM 信令",[46,634,636],{"className":72,"code":635,"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,637,638,643,648,653,658,662,666,671,676,681,686,690,694,700,706,712],{"__ignoreMap":54},[78,639,640],{"class":80,"line":81},[78,641,642],{},"\u003CContentProtection\n",[78,644,645],{"class":80,"line":87},[78,646,647],{},"  schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\"\n",[78,649,650],{"class":80,"line":93},[78,651,652],{},"  value=\"cenc\"\n",[78,654,655],{"class":80,"line":99},[78,656,657],{},"  cenc:default_KID=\"12345678-1234-1234-1234-123456789012\" \u002F>\n",[78,659,660],{"class":80,"line":105},[78,661,229],{"emptyLinePlaceholder":228},[78,663,664],{"class":80,"line":111},[78,665,642],{},[78,667,668],{"class":80,"line":270},[78,669,670],{},"  schemeIdUri=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n",[78,672,673],{"class":80,"line":276},[78,674,675],{},"  value=\"Widevine\">\n",[78,677,678],{"class":80,"line":281},[78,679,680],{},"  \u003Ccenc:pssh>AAAA...\u003C\u002Fcenc:pssh>\n",[78,682,683],{"class":80,"line":294},[78,684,685],{},"\u003C\u002FContentProtection>\n",[78,687,688],{"class":80,"line":345},[78,689,229],{"emptyLinePlaceholder":228},[78,691,692],{"class":80,"line":384},[78,693,642],{},[78,695,697],{"class":80,"line":696},13,[78,698,699],{},"  schemeIdUri=\"urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95\"\n",[78,701,703],{"class":80,"line":702},14,[78,704,705],{},"  value=\"PlayReady\">\n",[78,707,709],{"class":80,"line":708},15,[78,710,711],{},"  \u003Ccenc:pssh>BBBB...\u003C\u002Fcenc:pssh>\n",[78,713,715],{"class":80,"line":714},16,[78,716,685],{},[11,718,719],{},"UUID 标识 DRM 系统：",[119,721,722,728,734,740],{},[122,723,724,727],{},[20,725,726],{},"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"," — Widevine（Google）",[122,729,730,733],{},[20,731,732],{},"9a04f079-9840-4286-ab92-e65be0885f95"," — PlayReady（Microsoft）",[122,735,736,739],{},[20,737,738],{},"f239e769-efa3-4850-9c16-a9036d3fe1c1"," — Adobe Primetime",[122,741,742,745],{},[20,743,744],{},"94ce86fb-07ff-4f43-adb8-93d2fa968ca2"," — FairPlay（Apple，在 DASH 语境下）",[11,747,748,751],{},[20,749,750],{},"\u003Ccenc:pssh>"," 是 Protection System Specific Header——一个 base64 的 blob，含 key ID 和系统特定数据。浏览器把它喂给 EME（Encrypted Media Extensions），EME 再跟系统的 CDM（Content Decryption Module）对话。",[11,753,754,757],{},[62,755,756],{},"这里要划线："," FlowPick 不做、不会做、也做不到解密 DRM 保护的 DASH 内容。任何合法的浏览器扩展或开源工具都做不到。CDM 是黑盒——你给它加密 buffer，它还你解密后的帧，仅用于显示，永远不给导出。绕过这个在反规避法规下违法（美国 DMCA §1201，其他地区类似），还违反 CDM 的合同。",[11,759,760,761,764],{},"DASH 规范里有\"未受保护\"路径：某个 AdaptationSet 没有 ",[20,762,763],{},"ContentProtection","，分片就是未加密的，下载器能随便抓和合并。这就是为什么 FlowPick 能搞定 B �ilibili、Udemy 免费课和大多数企业 LMS，但搞不了 Netflix\u002FDisney+\u002FHBO Max。",[399,766,768],{"id":767},"感觉像保护但不是-drm-的常见模式","\"感觉像保护\"但不是 DRM 的常见模式",[11,770,771],{},"不是所有\"下不了\"都是 DRM。有些模式让下载很难，但没真加密：",[593,773,774,784,790],{},[122,775,776,779,780,783],{},[62,777,778],{},"短 TTL 的 token URL"," — ",[20,781,782],{},"?token=abc&expires=1234567890","。token 几秒就过期，解析完清单必须立刻抓分片。FlowPick 这么干；那种\"提前几小时解析 URL 再下\"的批量下载器会扑空。",[122,785,786,789],{},[62,787,788],{},"CDN 的 CORS 限制"," — 但扩展从页面 origin 发请求不受 CORS 约束，所以基本拦不住嗅探器。",[122,791,792,798,799,802,803,805],{},[62,793,794,797],{},[20,795,796],{},"\u003CBaseURL>"," 重写"," — MPD 的 ",[20,800,801],{},"BaseURL"," 元素可以在文档中间变。每个 ",[20,804,801],{}," 相对其父级解析，所以得跟踪 URL 链。这是 DASH 版的 HLS 相对 URI，但更容易出错。",[38,807,809],{"id":808},"多-period-mpd广告和直播事件","多 Period MPD：广告和直播事件",[46,811,813],{"className":72,"code":812,"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,814,815,820,825,830],{"__ignoreMap":54},[78,816,817],{"class":80,"line":81},[78,818,819],{},"\u003CPeriod id=\"ad1\" start=\"PT0S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[78,821,822],{"class":80,"line":87},[78,823,824],{},"\u003CPeriod id=\"main\" start=\"PT30S\">...\u003C\u002FPeriod>\n",[78,826,827],{"class":80,"line":93},[78,828,829],{},"\u003CPeriod id=\"ad2\" start=\"PT1800S\" duration=\"PT30S\">...\u003C\u002FPeriod>\n",[78,831,832],{"class":80,"line":99},[78,833,834],{},"\u003CPeriod id=\"main2\" start=\"PT1830S\">...\u003C\u002FPeriod>\n",[11,836,837],{},"每个 Period 有自己的 AdaptationSet。下载整个内容：",[593,839,840,847,850],{},[122,841,842,843,846],{},"按 ",[20,844,845],{},"@start"," 排序 Period",[122,848,849],{},"每个 Period 下所有视频+音频分片",[122,851,852],{},"Period 之间拼接——但要留意 codec 在 Period 之间变化",[11,854,855],{},"广告时段常用不同 codec（广告可能是 H.264 baseline，正片是 H.264 high）。无脑拼接出来的 MP4 正片正常播，到广告段就花屏。",[11,857,858],{},"修复办法：",[593,860,861,864,867],{},[122,862,863],{},"直接跳过广告 Period（FlowPick 的做法——反正用户也不想要广告）",[122,865,866],{},"把广告 Period 重编码到跟正片一致（慢，要转码）",[122,868,869],{},"用更聪明的封装器处理 codec 切换（FFmpeg 能，但复杂）",[38,871,873],{"id":872},"多视角和-trick-mode-adaptationset","多视角和 trick-mode AdaptationSet",[46,875,877],{"className":72,"code":876,"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,878,879,884,889,894,899,904,908,913,918],{"__ignoreMap":54},[78,880,881],{"class":80,"line":81},[78,882,883],{},"\u003CAdaptationSet id=\"1\" contentType=\"video\" group=\"1\">\n",[78,885,886],{"class":80,"line":87},[78,887,888],{},"  \u003C!-- 主机位 -->\n",[78,890,891],{"class":80,"line":93},[78,892,893],{},"\u003C\u002FAdaptationSet>\n",[78,895,896],{"class":80,"line":99},[78,897,898],{},"\u003CAdaptationSet id=\"2\" contentType=\"video\" group=\"1\">\n",[78,900,901],{"class":80,"line":105},[78,902,903],{},"  \u003C!-- 备用机位，同 group -->\n",[78,905,906],{"class":80,"line":111},[78,907,893],{},[78,909,910],{"class":80,"line":270},[78,911,912],{},"\u003CAdaptationSet id=\"3\" contentType=\"video\" group=\"2\">\n",[78,914,915],{"class":80,"line":276},[78,916,917],{},"  \u003C!-- Trick mode（进度条缩略图） -->\n",[78,919,920],{"class":80,"line":281},[78,921,893],{},[11,923,924,927],{},[20,925,926],{},"group=\"1\""," 表示这些 AdaptationSet 是同一内容的不同视角。播放器让用户切机位。下载器得选一个——通常是第一个，但理想是让用户选。",[11,929,930],{},"Trick-mode AdaptationSet（这里的 group=2）是进度条上拖动预览用的小体积低帧率版本。下载它没用——确保你的解析器不会把它误认为\"视频\"抓走。",[38,932,933],{"id":933},"预取与并发",[11,935,936],{},"DASH 下载速度上最实在的优化：分片并发抓。",[46,938,940],{"className":155,"code":939,"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,941,942,977,996,1045,1049,1076,1089,1093,1108,1131,1146,1172,1204,1209,1214,1218,1268,1273,1279,1340,1361,1373,1418,1440,1473,1478,1487],{"__ignoreMap":54},[78,943,944,947,950,953,955,959,962,965,968,971,974],{"class":80,"line":81},[78,945,946],{"class":164},"async",[78,948,949],{"class":164}," function",[78,951,952],{"class":247}," downloadRepresentation",[78,954,357],{"class":172},[78,956,958],{"class":957},"sHdIc","rep",[78,960,961],{"class":172},",",[78,963,964],{"class":957}," concurrency",[78,966,967],{"class":172}," =",[78,969,970],{"class":176}," 6",[78,972,973],{"class":172},")",[78,975,976],{"class":172}," {\n",[78,978,979,982,985,987,990,992,994],{"class":80,"line":87},[78,980,981],{"class":164},"  const",[78,983,984],{"class":168}," segmentUrls",[78,986,967],{"class":172},[78,988,989],{"class":247}," computeSegmentUrls",[78,991,357],{"class":356},[78,993,958],{"class":168},[78,995,381],{"class":356},[78,997,998,1000,1003,1005,1008,1011,1013,1015,1017,1020,1022,1024,1027,1029,1031,1034,1037,1039,1042],{"class":80,"line":93},[78,999,981],{"class":164},[78,1001,1002],{"class":168}," initBytes",[78,1004,967],{"class":172},[78,1006,1007],{"class":297}," await",[78,1009,1010],{"class":247}," fetch",[78,1012,357],{"class":356},[78,1014,958],{"class":168},[78,1016,244],{"class":172},[78,1018,1019],{"class":168},"initUrl",[78,1021,973],{"class":356},[78,1023,244],{"class":172},[78,1025,1026],{"class":247},"then",[78,1028,357],{"class":356},[78,1030,583],{"class":957},[78,1032,1033],{"class":164}," =>",[78,1035,1036],{"class":168}," r",[78,1038,244],{"class":172},[78,1040,1041],{"class":247},"arrayBuffer",[78,1043,1044],{"class":356},"())\n",[78,1046,1047],{"class":80,"line":99},[78,1048,229],{"emptyLinePlaceholder":228},[78,1050,1051,1053,1056,1058,1061,1064,1066,1069,1071,1074],{"class":80,"line":105},[78,1052,981],{"class":164},[78,1054,1055],{"class":168}," results",[78,1057,967],{"class":172},[78,1059,1060],{"class":172}," new",[78,1062,1063],{"class":247}," Array",[78,1065,357],{"class":356},[78,1067,1068],{"class":168},"segmentUrls",[78,1070,244],{"class":172},[78,1072,1073],{"class":168},"length",[78,1075,381],{"class":356},[78,1077,1078,1081,1084,1086],{"class":80,"line":111},[78,1079,1080],{"class":164},"  let",[78,1082,1083],{"class":168}," nextIndex",[78,1085,967],{"class":172},[78,1087,1088],{"class":176}," 0\n",[78,1090,1091],{"class":80,"line":270},[78,1092,229],{"emptyLinePlaceholder":228},[78,1094,1095,1098,1100,1103,1106],{"class":80,"line":276},[78,1096,1097],{"class":164},"  async",[78,1099,949],{"class":164},[78,1101,1102],{"class":247}," worker",[78,1104,1105],{"class":172},"()",[78,1107,976],{"class":172},[78,1109,1110,1113,1115,1118,1121,1123,1125,1127,1129],{"class":80,"line":281},[78,1111,1112],{"class":297},"    while",[78,1114,301],{"class":356},[78,1116,1117],{"class":168},"nextIndex",[78,1119,1120],{"class":172}," \u003C",[78,1122,984],{"class":168},[78,1124,244],{"class":172},[78,1126,1073],{"class":168},[78,1128,339],{"class":356},[78,1130,342],{"class":172},[78,1132,1133,1136,1139,1141,1143],{"class":80,"line":294},[78,1134,1135],{"class":164},"      const",[78,1137,1138],{"class":168}," i",[78,1140,967],{"class":172},[78,1142,1083],{"class":168},[78,1144,1145],{"class":172},"++\n",[78,1147,1148,1150,1153,1155,1157,1159,1161,1163,1166,1169],{"class":80,"line":345},[78,1149,1135],{"class":164},[78,1151,1152],{"class":168}," resp",[78,1154,967],{"class":172},[78,1156,1007],{"class":297},[78,1158,1010],{"class":247},[78,1160,357],{"class":356},[78,1162,1068],{"class":168},[78,1164,1165],{"class":356},"[",[78,1167,1168],{"class":168},"i",[78,1170,1171],{"class":356},"])\n",[78,1173,1174,1177,1179,1181,1184,1186,1188,1191,1193,1196,1198,1200,1202],{"class":80,"line":384},[78,1175,1176],{"class":168},"      results",[78,1178,1165],{"class":356},[78,1180,1168],{"class":168},[78,1182,1183],{"class":356},"] ",[78,1185,173],{"class":172},[78,1187,1060],{"class":172},[78,1189,1190],{"class":247}," Uint8Array",[78,1192,357],{"class":356},[78,1194,1195],{"class":297},"await",[78,1197,1152],{"class":168},[78,1199,244],{"class":172},[78,1201,1041],{"class":247},[78,1203,1044],{"class":356},[78,1205,1206],{"class":80,"line":696},[78,1207,1208],{"class":172},"    }\n",[78,1210,1211],{"class":80,"line":702},[78,1212,1213],{"class":172},"  }\n",[78,1215,1216],{"class":80,"line":708},[78,1217,229],{"emptyLinePlaceholder":228},[78,1219,1220,1223,1227,1229,1232,1234,1237,1239,1242,1244,1247,1250,1253,1255,1258,1261,1263,1265],{"class":80,"line":714},[78,1221,1222],{"class":297},"  await",[78,1224,1226],{"class":1225},"sBMFI"," Promise",[78,1228,244],{"class":172},[78,1230,1231],{"class":247},"all",[78,1233,357],{"class":356},[78,1235,1236],{"class":168},"Array",[78,1238,244],{"class":172},[78,1240,1241],{"class":247},"from",[78,1243,357],{"class":356},[78,1245,1246],{"class":172},"{",[78,1248,1249],{"class":356}," length",[78,1251,1252],{"class":172},":",[78,1254,964],{"class":168},[78,1256,1257],{"class":172}," },",[78,1259,1260],{"class":172}," ()",[78,1262,1033],{"class":164},[78,1264,1102],{"class":247},[78,1266,1267],{"class":356},"()))\n",[78,1269,1271],{"class":80,"line":1270},17,[78,1272,229],{"emptyLinePlaceholder":228},[78,1274,1276],{"class":80,"line":1275},18,[78,1277,1278],{"class":192},"  \u002F\u002F 拼接：init + 按序分片\n",[78,1280,1282,1284,1287,1289,1291,1293,1296,1299,1301,1303,1306,1308,1310,1313,1315,1318,1320,1322,1325,1327,1329,1331,1333,1335,1338],{"class":80,"line":1281},19,[78,1283,981],{"class":164},[78,1285,1286],{"class":168}," total",[78,1288,967],{"class":172},[78,1290,1002],{"class":168},[78,1292,244],{"class":172},[78,1294,1295],{"class":168},"byteLength",[78,1297,1298],{"class":172}," +",[78,1300,1055],{"class":168},[78,1302,244],{"class":172},[78,1304,1305],{"class":247},"reduce",[78,1307,357],{"class":356},[78,1309,357],{"class":172},[78,1311,1312],{"class":957},"s",[78,1314,961],{"class":172},[78,1316,1317],{"class":957}," b",[78,1319,973],{"class":172},[78,1321,1033],{"class":164},[78,1323,1324],{"class":168}," s",[78,1326,1298],{"class":172},[78,1328,1317],{"class":168},[78,1330,244],{"class":172},[78,1332,1295],{"class":168},[78,1334,961],{"class":172},[78,1336,1337],{"class":176}," 0",[78,1339,381],{"class":356},[78,1341,1343,1345,1348,1350,1352,1354,1356,1359],{"class":80,"line":1342},20,[78,1344,981],{"class":164},[78,1346,1347],{"class":168}," out",[78,1349,967],{"class":172},[78,1351,1060],{"class":172},[78,1353,1190],{"class":247},[78,1355,357],{"class":356},[78,1357,1358],{"class":168},"total",[78,1360,381],{"class":356},[78,1362,1364,1366,1369,1371],{"class":80,"line":1363},21,[78,1365,1080],{"class":164},[78,1367,1368],{"class":168}," offset",[78,1370,967],{"class":172},[78,1372,1088],{"class":176},[78,1374,1376,1379,1381,1384,1386,1389,1391,1393,1396,1398,1400,1402,1404,1406,1408,1411,1413,1415],{"class":80,"line":1375},22,[78,1377,1378],{"class":168},"  out",[78,1380,244],{"class":172},[78,1382,1383],{"class":247},"set",[78,1385,357],{"class":356},[78,1387,1388],{"class":172},"new",[78,1390,1190],{"class":247},[78,1392,357],{"class":356},[78,1394,1395],{"class":168},"initBytes",[78,1397,973],{"class":356},[78,1399,961],{"class":172},[78,1401,1368],{"class":168},[78,1403,973],{"class":356},[78,1405,315],{"class":172},[78,1407,1368],{"class":168},[78,1409,1410],{"class":172}," +=",[78,1412,1002],{"class":168},[78,1414,244],{"class":172},[78,1416,1417],{"class":168},"byteLength\n",[78,1419,1421,1424,1426,1428,1431,1434,1436,1438],{"class":80,"line":1420},23,[78,1422,1423],{"class":297},"  for",[78,1425,301],{"class":356},[78,1427,165],{"class":164},[78,1429,1430],{"class":168}," buf",[78,1432,1433],{"class":172}," of",[78,1435,1055],{"class":168},[78,1437,339],{"class":356},[78,1439,342],{"class":172},[78,1441,1443,1446,1448,1450,1452,1455,1457,1459,1461,1463,1465,1467,1469,1471],{"class":80,"line":1442},24,[78,1444,1445],{"class":168},"    out",[78,1447,244],{"class":172},[78,1449,1383],{"class":247},[78,1451,357],{"class":356},[78,1453,1454],{"class":168},"buf",[78,1456,961],{"class":172},[78,1458,1368],{"class":168},[78,1460,973],{"class":356},[78,1462,315],{"class":172},[78,1464,1368],{"class":168},[78,1466,1410],{"class":172},[78,1468,1430],{"class":168},[78,1470,244],{"class":172},[78,1472,1417],{"class":168},[78,1474,1476],{"class":80,"line":1475},25,[78,1477,1213],{"class":172},[78,1479,1481,1484],{"class":80,"line":1480},26,[78,1482,1483],{"class":297},"  return",[78,1485,1486],{"class":168}," out\n",[78,1488,1490],{"class":80,"line":1489},27,[78,1491,387],{"class":172},[11,1493,1494,1495,1498],{},"6 个并发连接是甜点——更多会触发 CDN 限速，更少浪费带宽。",[20,1496,1497],{},"Promise.all"," worker 池模式保证分片乱序完成时也能落到正确的数组位置。",[11,1500,1501,1502,1506],{},"这就是 FlowPick 内部用的模式；",[14,1503,1505],{"href":1504},"\u002Fzh-Hans\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","浏览器内合并那篇"," 讲最终 buffer 怎么封装。",[38,1508,1509],{"id":1509},"踩过的坑",[11,1511,1512,1518,1519,1522,1523,1526,1527,1530],{},[62,1513,1514,1515,1517],{},"坑 1：忽略 ",[20,1516,801],{},"。"," MPD 在 ",[20,1520,1521],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmanifest.mpd","，里面是 ",[20,1524,1525],{},"\u003CBaseURL>https:\u002F\u002Fother-cdn.example.com\u002F\u003C\u002FBaseURL>","，那么这一段里所有相对 URL 都按 ",[20,1528,1529],{},"other-cdn.example.com"," 解析，不是按 MPD 的 origin。忘了这个，是\"所有分片都 404\"bug 的头号原因。",[11,1532,1533,1539,1540,1543,1544,1547,1548,1551,1552,1554],{},[62,1534,1535,1536,1538],{},"坑 2：",[20,1537,499],{}," 解析错。"," ",[20,1541,1542],{},"duration=\"6000\""," 配 ",[20,1545,1546],{},"timescale=\"1000\""," 是 6 秒。配 ",[20,1549,1550],{},"timescale=\"90000\"","（视频常见）是 0.067 秒。一定查 ",[20,1553,499],{},"——同一个 MPD 里不同 AdaptationSet 可能不一样。",[11,1556,1557,1560,1561,1517],{},[62,1558,1559],{},"坑 3：忘了 init 分片。"," HLS（TS 情况下）每个分片自描述，DASH 的 fMP4 分片没 init 分片就是废物。下载\"看起来成功了\"但出来的文件不能播，多半是漏了 ",[20,1562,1563],{},"initialization=\"init.mp4\"",[11,1565,1566,1569],{},[62,1567,1568],{},"坑 4：直播 MPD 会更新。"," 解析一次就按快照下，后续追加的分片会漏。VOD 没问题。直播要么定期重拉，要么等流结束。",[38,1571,1572],{"id":1572},"参考资料",[119,1574,1575,1584,1592,1600],{},[122,1576,1577,1583],{},[14,1578,1582],{"href":1579,"rel":1580},"https:\u002F\u002Fwww.iso.org\u002Fstandard\u002F79329.html",[1581],"nofollow","ISO\u002FIEC 23009-1 — DASH 规范"," — 正式规范，付费但权威",[122,1585,1586,1591],{},[14,1587,1590],{"href":1588,"rel":1589},"https:\u002F\u002Fdashif.org\u002Fguidelines\u002F",[1581],"DASH-IF 实施指南"," — DASH Industry Forum 出的指南，免费，比 ISO 文档实用",[122,1593,1594,1599],{},[14,1595,1598],{"href":1596,"rel":1597},"https:\u002F\u002Fgpac.wp.imt.fr\u002Fmp4box\u002F",[1581],"GPAC MP4Box 文档"," — 参考级 MP4\u002FDASH 工具，拿来验证你的解析器",[122,1601,1602,1607],{},[14,1603,1606],{"href":1604,"rel":1605},"https:\u002F\u002Fgithub.com\u002Faxiomatic-systems\u002FBento4",[1581],"Bento4 MP4 文档"," — 开源 MP4 工具包，理解容器格式有用",[38,1609,1610],{"id":1610},"小结",[11,1612,1613],{},"DASH 和 HLS 解决的是同一个问题——基于 HTTP 的自适应流——但思路相反。HLS 把每个分片列出来；DASH 用模板。HLS 默认用 MPEG-TS；DASH 用 fMP4。HLS 的 DRM 是 Apple 私有的（FairPlay）；DASH 的 DRM 多厂商（Widevine、PlayReady、FairPlay）走 CENC。",[11,1615,1616,1617,1619,1620,23,1623,23,1626,23,1628,1630],{},"下载器角度，DASH 结构上更好搞：fMP4 分片能直接拼、init 分片把 codec 信息显式化、",[20,1618,138],{}," 替换意味着不用拉额外清单就能算出 URL。复杂度在 XML 解析——但只要搞定 ",[20,1621,1622],{},"SegmentTemplate",[20,1624,1625],{},"SegmentTimeline",[20,1627,801],{},[20,1629,763],{},"，就覆盖了 95% 的真实 MPD。",[11,1632,1633,1634,1517],{},"下一篇进容器格式本身——",[14,1635,1637,1638,1641],{"href":1636},"\u002Fzh-Hans\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","fMP4 拼接为什么能行、",[20,1639,1640],{},"moof"," box 里有什么、为什么\"重封装不转码\"是对的哲学",[1643,1644],"hr",{},[38,1646,1647],{"id":1647},"推荐阅读",[119,1649,1650,1658,1667,1675,1684,1692],{},[122,1651,1652,1657],{},[62,1653,1654],{},[14,1655,1656],{"href":16},"什么是 DASH 流媒体？MPD 文件解析"," — 这篇的入门版",[122,1659,1660,1666],{},[62,1661,1662],{},[14,1663,1665],{"href":1664},"\u002Fzh-Hans\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack","HLS 深度：加密、多音轨、值得记住的 EXT-X 标签"," — HLS 那边的对应物",[122,1668,1669,1674],{},[62,1670,1671],{},[14,1672,1673],{"href":1636},"浏览器端视频重封装：fMP4、ISOBMFF 和不转码的理由"," — DASH 分片下完之后会发生什么",[122,1676,1677,1683],{},[62,1678,1679],{},[14,1680,1682],{"href":1681},"\u002Fzh-Hans\u002Fblog\u002Fis-it-legal-to-download-streaming-video","流媒体下载到底合不合法？一份技术和法律清单"," — 为什么 ContentProtection 标签是硬底线",[122,1685,1686,1691],{},[62,1687,1688],{},[14,1689,1690],{"href":1504},"FlowPick 是怎么在浏览器里把几百个视频切片合成 MP4 的"," — 这里讲的模式在 FlowPick 里怎么落地",[122,1693,1694,1700],{},[62,1695,1696],{},[14,1697,1699],{"href":1698},"\u002Fzh-Hans\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS：浏览器视频处理实战"," — 跟 FFmpeg WASM 互补的更底层浏览器 API",[1702,1703,1704],"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":1706},[1707,1708,1711,1712,1715,1716,1717,1718,1719,1720,1721],{"id":40,"depth":87,"text":41},{"id":68,"depth":87,"text":69,"children":1709},[1710],{"id":401,"depth":93,"text":402},{"id":503,"depth":87,"text":504},{"id":631,"depth":87,"text":632,"children":1713},[1714],{"id":767,"depth":93,"text":768},{"id":808,"depth":87,"text":809},{"id":872,"depth":87,"text":873},{"id":933,"depth":87,"text":933},{"id":1509,"depth":87,"text":1509},{"id":1572,"depth":87,"text":1572},{"id":1610,"depth":87,"text":1610},{"id":1647,"depth":87,"text":1647},"tips","2026-08-04","MPD 入门之后的进阶——SegmentTemplate 对比 SegmentTimeline、$Number$ 对比 $Time$、ContentProtection 信令，以及 DASH 比 HLS 难解析却好下载的结构性原因。","md","\u002Fscreenshots\u002Fdash-downloader.png",{},"\u002Fzh-hans\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",{"title":5,"description":1724},"zh-Hans\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",[1732,1733,1734,1735,1736,1737],"dash","mpd","深度","segmenttemplate","drm","串流","VfatDa2HEh_SVValH4UbKVvHKaKfXVvlNumD0rnLj6M",[1740,1745],{"title":1741,"path":1742,"stem":1743,"description":1744,"date":1723,"category":1722,"children":-1},"浏览器视频处理性能基准：FFmpeg WASM vs WebCodecs vs 原生","\u002Fzh-hans\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","zh-Hans\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","真实硬件上的真实数据——1080p\u002F4K\u002F8K 重封装和解码基准，横跨 FFmpeg WASM、WebCodecs、原生 FFmpeg。附方法论、原始数据、数字到底意味着什么。",{"title":1746,"path":1747,"stem":1748,"description":1749,"date":1750,"category":1751,"children":-1},"FlowPick v1.0.0 发布说明：第一次公开亮相","\u002Fzh-hans\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","zh-Hans\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","FlowPick v1.0.0 上线——浏览器扩展，自动嗅探 HLS\u002FDASH 流、音频和图片，通过 WebAssembly FFmpeg 在浏览器内合并分片，全程本地处理。这是首日发布的内容。","2025-04-20","updates",1787670586975]