[{"data":1,"prerenderedAt":1749},["ShallowReactive",2],{"blog-ko-dash-mpd-deep-dive-segmenttemplate-contentprotection":3,"blog-ko-dash-mpd-deep-dive-segmenttemplate-contentprotection-surround":1736},{"id":4,"title":5,"author":6,"body":7,"category":1719,"date":1720,"description":1721,"extension":1722,"image":1723,"lastModified":1720,"meta":1724,"navigation":228,"path":1725,"readingTime":701,"seo":1726,"stem":1727,"tags":1728,"__hash__":1735},"blog_ko\u002Fko\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection.md","DASH 딥: SegmentTemplate, ContentProtection, 시점 전환 MPD 구조","FlowPick 팀",{"type":8,"value":9,"toc":1702},"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,927,930,1485,1492,1500,1504,1525,1549,1559,1565,1569,1604,1607,1610,1627,1638,1641,1645,1698],[11,12,13,18,19,23,24,23,27,23,30,33],"p",{},[14,15,17],"a",{"href":16},"\u002Fko\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개 이상. 멀티 카메라 스포츠 라이브는 각 카메라가 AdaptationSet 하나씩.",[11,59,60,61,65],{},"핵심: ",[62,63,64],"strong",{},"DASH는 모든 세그먼트 URL을 나열하지 않도록 설계되어 있다."," HLS는 1개씩 나열. DASH는 템플릿 사용.",[38,67,69],{"id":68},"segmenttemplate-90의-케이스","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},"contentprotection-drm-시그널링","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\"","은 같은 콘텐츠의 다른 시점을 의미. 플레이어는 사용자에게 카메라 전환을 제공. 다운로더는 하나를 선택——보통 첫 번째지만, 이상은 사용자에게 선택시킨다.",[11,921,922],{},"Trick-mode AdaptationSet(여기서 group=2)은 시크바에서 드래그 시 미리보기용 저비트레이트 저프레임레이트 버전. 이걸 다운로드해봤자 소용없다——파서가 이걸 \"비디오\"로 잘못 인식하지 않게.",[38,924,926],{"id":925},"프리페치와-병렬화","프리페치와 병렬화",[11,928,929],{},"DASH 다운로드에서 가장 실용적인 최적화: 세그먼트를 병렬 페치.",[46,931,933],{"className":155,"code":932,"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,934,935,970,989,1038,1042,1069,1082,1086,1101,1124,1139,1165,1197,1202,1207,1211,1261,1266,1272,1333,1354,1366,1411,1433,1466,1471,1480],{"__ignoreMap":54},[78,936,937,940,943,946,948,952,955,958,961,964,967],{"class":80,"line":81},[78,938,939],{"class":164},"async",[78,941,942],{"class":164}," function",[78,944,945],{"class":247}," downloadRepresentation",[78,947,357],{"class":172},[78,949,951],{"class":950},"sHdIc","rep",[78,953,954],{"class":172},",",[78,956,957],{"class":950}," concurrency",[78,959,960],{"class":172}," =",[78,962,963],{"class":176}," 6",[78,965,966],{"class":172},")",[78,968,969],{"class":172}," {\n",[78,971,972,975,978,980,983,985,987],{"class":80,"line":87},[78,973,974],{"class":164},"  const",[78,976,977],{"class":168}," segmentUrls",[78,979,960],{"class":172},[78,981,982],{"class":247}," computeSegmentUrls",[78,984,357],{"class":356},[78,986,951],{"class":168},[78,988,381],{"class":356},[78,990,991,993,996,998,1001,1004,1006,1008,1010,1013,1015,1017,1020,1022,1024,1027,1030,1032,1035],{"class":80,"line":93},[78,992,974],{"class":164},[78,994,995],{"class":168}," initBytes",[78,997,960],{"class":172},[78,999,1000],{"class":297}," await",[78,1002,1003],{"class":247}," fetch",[78,1005,357],{"class":356},[78,1007,951],{"class":168},[78,1009,244],{"class":172},[78,1011,1012],{"class":168},"initUrl",[78,1014,966],{"class":356},[78,1016,244],{"class":172},[78,1018,1019],{"class":247},"then",[78,1021,357],{"class":356},[78,1023,577],{"class":950},[78,1025,1026],{"class":164}," =>",[78,1028,1029],{"class":168}," r",[78,1031,244],{"class":172},[78,1033,1034],{"class":247},"arrayBuffer",[78,1036,1037],{"class":356},"())\n",[78,1039,1040],{"class":80,"line":99},[78,1041,229],{"emptyLinePlaceholder":228},[78,1043,1044,1046,1049,1051,1054,1057,1059,1062,1064,1067],{"class":80,"line":105},[78,1045,974],{"class":164},[78,1047,1048],{"class":168}," results",[78,1050,960],{"class":172},[78,1052,1053],{"class":172}," new",[78,1055,1056],{"class":247}," Array",[78,1058,357],{"class":356},[78,1060,1061],{"class":168},"segmentUrls",[78,1063,244],{"class":172},[78,1065,1066],{"class":168},"length",[78,1068,381],{"class":356},[78,1070,1071,1074,1077,1079],{"class":80,"line":111},[78,1072,1073],{"class":164},"  let",[78,1075,1076],{"class":168}," nextIndex",[78,1078,960],{"class":172},[78,1080,1081],{"class":176}," 0\n",[78,1083,1084],{"class":80,"line":270},[78,1085,229],{"emptyLinePlaceholder":228},[78,1087,1088,1091,1093,1096,1099],{"class":80,"line":276},[78,1089,1090],{"class":164},"  async",[78,1092,942],{"class":164},[78,1094,1095],{"class":247}," worker",[78,1097,1098],{"class":172},"()",[78,1100,969],{"class":172},[78,1102,1103,1106,1108,1111,1114,1116,1118,1120,1122],{"class":80,"line":281},[78,1104,1105],{"class":297},"    while",[78,1107,301],{"class":356},[78,1109,1110],{"class":168},"nextIndex",[78,1112,1113],{"class":172}," \u003C",[78,1115,977],{"class":168},[78,1117,244],{"class":172},[78,1119,1066],{"class":168},[78,1121,339],{"class":356},[78,1123,342],{"class":172},[78,1125,1126,1129,1132,1134,1136],{"class":80,"line":294},[78,1127,1128],{"class":164},"      const",[78,1130,1131],{"class":168}," i",[78,1133,960],{"class":172},[78,1135,1076],{"class":168},[78,1137,1138],{"class":172},"++\n",[78,1140,1141,1143,1146,1148,1150,1152,1154,1156,1159,1162],{"class":80,"line":345},[78,1142,1128],{"class":164},[78,1144,1145],{"class":168}," resp",[78,1147,960],{"class":172},[78,1149,1000],{"class":297},[78,1151,1003],{"class":247},[78,1153,357],{"class":356},[78,1155,1061],{"class":168},[78,1157,1158],{"class":356},"[",[78,1160,1161],{"class":168},"i",[78,1163,1164],{"class":356},"])\n",[78,1166,1167,1170,1172,1174,1177,1179,1181,1184,1186,1189,1191,1193,1195],{"class":80,"line":384},[78,1168,1169],{"class":168},"      results",[78,1171,1158],{"class":356},[78,1173,1161],{"class":168},[78,1175,1176],{"class":356},"] ",[78,1178,173],{"class":172},[78,1180,1053],{"class":172},[78,1182,1183],{"class":247}," Uint8Array",[78,1185,357],{"class":356},[78,1187,1188],{"class":297},"await",[78,1190,1145],{"class":168},[78,1192,244],{"class":172},[78,1194,1034],{"class":247},[78,1196,1037],{"class":356},[78,1198,1199],{"class":80,"line":689},[78,1200,1201],{"class":172},"    }\n",[78,1203,1204],{"class":80,"line":695},[78,1205,1206],{"class":172},"  }\n",[78,1208,1209],{"class":80,"line":701},[78,1210,229],{"emptyLinePlaceholder":228},[78,1212,1213,1216,1220,1222,1225,1227,1230,1232,1235,1237,1240,1243,1246,1248,1251,1254,1256,1258],{"class":80,"line":707},[78,1214,1215],{"class":297},"  await",[78,1217,1219],{"class":1218},"sBMFI"," Promise",[78,1221,244],{"class":172},[78,1223,1224],{"class":247},"all",[78,1226,357],{"class":356},[78,1228,1229],{"class":168},"Array",[78,1231,244],{"class":172},[78,1233,1234],{"class":247},"from",[78,1236,357],{"class":356},[78,1238,1239],{"class":172},"{",[78,1241,1242],{"class":356}," length",[78,1244,1245],{"class":172},":",[78,1247,957],{"class":168},[78,1249,1250],{"class":172}," },",[78,1252,1253],{"class":172}," ()",[78,1255,1026],{"class":164},[78,1257,1095],{"class":247},[78,1259,1260],{"class":356},"()))\n",[78,1262,1264],{"class":80,"line":1263},17,[78,1265,229],{"emptyLinePlaceholder":228},[78,1267,1269],{"class":80,"line":1268},18,[78,1270,1271],{"class":192},"  \u002F\u002F init + 세그먼트를 순서대로 결합\n",[78,1273,1275,1277,1280,1282,1284,1286,1289,1292,1294,1296,1299,1301,1303,1306,1308,1311,1313,1315,1318,1320,1322,1324,1326,1328,1331],{"class":80,"line":1274},19,[78,1276,974],{"class":164},[78,1278,1279],{"class":168}," total",[78,1281,960],{"class":172},[78,1283,995],{"class":168},[78,1285,244],{"class":172},[78,1287,1288],{"class":168},"byteLength",[78,1290,1291],{"class":172}," +",[78,1293,1048],{"class":168},[78,1295,244],{"class":172},[78,1297,1298],{"class":247},"reduce",[78,1300,357],{"class":356},[78,1302,357],{"class":172},[78,1304,1305],{"class":950},"s",[78,1307,954],{"class":172},[78,1309,1310],{"class":950}," b",[78,1312,966],{"class":172},[78,1314,1026],{"class":164},[78,1316,1317],{"class":168}," s",[78,1319,1291],{"class":172},[78,1321,1310],{"class":168},[78,1323,244],{"class":172},[78,1325,1288],{"class":168},[78,1327,954],{"class":172},[78,1329,1330],{"class":176}," 0",[78,1332,381],{"class":356},[78,1334,1336,1338,1341,1343,1345,1347,1349,1352],{"class":80,"line":1335},20,[78,1337,974],{"class":164},[78,1339,1340],{"class":168}," out",[78,1342,960],{"class":172},[78,1344,1053],{"class":172},[78,1346,1183],{"class":247},[78,1348,357],{"class":356},[78,1350,1351],{"class":168},"total",[78,1353,381],{"class":356},[78,1355,1357,1359,1362,1364],{"class":80,"line":1356},21,[78,1358,1073],{"class":164},[78,1360,1361],{"class":168}," offset",[78,1363,960],{"class":172},[78,1365,1081],{"class":176},[78,1367,1369,1372,1374,1377,1379,1382,1384,1386,1389,1391,1393,1395,1397,1399,1401,1404,1406,1408],{"class":80,"line":1368},22,[78,1370,1371],{"class":168},"  out",[78,1373,244],{"class":172},[78,1375,1376],{"class":247},"set",[78,1378,357],{"class":356},[78,1380,1381],{"class":172},"new",[78,1383,1183],{"class":247},[78,1385,357],{"class":356},[78,1387,1388],{"class":168},"initBytes",[78,1390,966],{"class":356},[78,1392,954],{"class":172},[78,1394,1361],{"class":168},[78,1396,966],{"class":356},[78,1398,315],{"class":172},[78,1400,1361],{"class":168},[78,1402,1403],{"class":172}," +=",[78,1405,995],{"class":168},[78,1407,244],{"class":172},[78,1409,1410],{"class":168},"byteLength\n",[78,1412,1414,1417,1419,1421,1424,1427,1429,1431],{"class":80,"line":1413},23,[78,1415,1416],{"class":297},"  for",[78,1418,301],{"class":356},[78,1420,165],{"class":164},[78,1422,1423],{"class":168}," buf",[78,1425,1426],{"class":172}," of",[78,1428,1048],{"class":168},[78,1430,339],{"class":356},[78,1432,342],{"class":172},[78,1434,1436,1439,1441,1443,1445,1448,1450,1452,1454,1456,1458,1460,1462,1464],{"class":80,"line":1435},24,[78,1437,1438],{"class":168},"    out",[78,1440,244],{"class":172},[78,1442,1376],{"class":247},[78,1444,357],{"class":356},[78,1446,1447],{"class":168},"buf",[78,1449,954],{"class":172},[78,1451,1361],{"class":168},[78,1453,966],{"class":356},[78,1455,315],{"class":172},[78,1457,1361],{"class":168},[78,1459,1403],{"class":172},[78,1461,1423],{"class":168},[78,1463,244],{"class":172},[78,1465,1410],{"class":168},[78,1467,1469],{"class":80,"line":1468},25,[78,1470,1206],{"class":172},[78,1472,1474,1477],{"class":80,"line":1473},26,[78,1475,1476],{"class":297},"  return",[78,1478,1479],{"class":168}," out\n",[78,1481,1483],{"class":80,"line":1482},27,[78,1484,387],{"class":172},[11,1486,1487,1488,1491],{},"6개 병렬 연결이 스위트스팟——그 이상은 CDN 레이트 리미트를 트리거, 그 이하는 대역 낭비. ",[20,1489,1490],{},"Promise.all","의 worker 풀 패턴으로 세그먼트가 순서 없이 완료되어도 올바른 배열 위치에 떨어진다.",[11,1493,1494,1495,1499],{},"이게 FlowPick 내부에서 쓰는 패턴. ",[14,1496,1498],{"href":1497},"\u002Fko\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","브라우저 내 병합 글","에서 최종 버퍼를 어떻게 멀티플렉스하는지 설명.",[38,1501,1503],{"id":1502},"밟은-함정","밟은 함정",[11,1505,1506,1512,1513,1516,1517,1520,1521,1524],{},[62,1507,1508,1509,1511],{},"함정 1: ",[20,1510,794],{}," 무시."," MPD가 ",[20,1514,1515],{},"https:\u002F\u002Fcdn.example.com\u002Fv\u002Fmanifest.mpd","에 있고, 안에 ",[20,1518,1519],{},"\u003CBaseURL>https:\u002F\u002Fother-cdn.example.com\u002F\u003C\u002FBaseURL>","이면, 이 섹션의 상대 URL은 ",[20,1522,1523],{},"other-cdn.example.com","으로 해석. MPD의 origin이 아니다. 이걸 잊으면 \"전부 404\" 버그의 1등 원인.",[11,1526,1527,1533,1534,1537,1538,1541,1542,1545,1546,1548],{},[62,1528,1529,1530,1532],{},"함정 2: ",[20,1531,497],{}," 파싱 실수."," ",[20,1535,1536],{},"duration=\"6000\"","이고 ",[20,1539,1540],{},"timescale=\"1000\"","이면 6초. ",[20,1543,1544],{},"timescale=\"90000\"","(비디오에서 흔함)이면 0.067초. ",[20,1547,497],{},"을 반드시 확인——같은 MPD 내에서 AdaptationSet마다 다를 수 있다.",[11,1550,1551,1554,1555,1558],{},[62,1552,1553],{},"함정 3: init 세그먼트 잊기."," HLS(TS의 경우)는 각 세그먼트가 자기 기술적이지만, DASH의 fMP4 세그먼트는 init 세그먼트 없이는 쓸모없다. \"다운로드 성공한 것처럼 보이는데\" 재생 안 되는 파일은 ",[20,1556,1557],{},"initialization=\"init.mp4\"","를 잊은 경우가 많다.",[11,1560,1561,1564],{},[62,1562,1563],{},"함정 4: 라이브 MPD는 갱신된다."," 한 번 파싱하고 스냅샷으로 다운로드하면, 이후 추가된 세그먼트를 놓친다. VOD는 문제없음. 라이브는 주기적으로 재획득하거나 스트림 종료를 기다린다.",[38,1566,1568],{"id":1567},"참고-자료","참고 자료",[119,1570,1571,1580,1588,1596],{},[122,1572,1573,1579],{},[14,1574,1578],{"href":1575,"rel":1576},"https:\u002F\u002Fwww.iso.org\u002Fstandard\u002F79329.html",[1577],"nofollow","ISO\u002FIEC 23009-1 — DASH 사양","——공식 사양, 유료지만 권위",[122,1581,1582,1587],{},[14,1583,1586],{"href":1584,"rel":1585},"https:\u002F\u002Fdashif.org\u002Fguidelines\u002F",[1577],"DASH-IF 구현 가이드","——DASH Industry Forum의 가이드, 무료, ISO 문서보다 실용적",[122,1589,1590,1595],{},[14,1591,1594],{"href":1592,"rel":1593},"https:\u002F\u002Fgpac.wp.imt.fr\u002Fmp4box\u002F",[1577],"GPAC MP4Box 문서","——레퍼런스급 MP4\u002FDASH 도구. 파서 검증용",[122,1597,1598,1603],{},[14,1599,1602],{"href":1600,"rel":1601},"https:\u002F\u002Fgithub.com\u002Faxiomatic-systems\u002FBento4",[1577],"Bento4 MP4 문서","——오픈소스 MP4 툴킷, 컨테이너 포맷 이해에 도움",[38,1605,1606],{"id":1606},"정리",[11,1608,1609],{},"DASH와 HLS는 같은 문제——HTTP 기반의 적응형 스트리밍——를 반대 접근으로 푼다. HLS는 각 세그먼트를 나열. DASH는 템플릿을 쓴다. HLS는 기본이 MPEG-TS. DASH는 fMP4. HLS의 DRM은 Apple 독점(FairPlay). DASH의 DRM은 멀티 벤더(Widevine, PlayReady, FairPlay)로 CENC를 통과.",[11,1611,1612,1613,1615,1616,23,1619,23,1622,23,1624,1626],{},"다운로더 관점에서 DASH는 구조적으로 더 다루기 쉽다: fMP4 세그먼트는 직접 결합 가능, init 세그먼트가 codec 정보를 명시, ",[20,1614,138],{}," 치환은 청단을 추가 페치 없이 URL을 계산. 복잡도는 XML 파싱에 있다——하지만 ",[20,1617,1618],{},"SegmentTemplate",[20,1620,1621],{},"SegmentTimeline",[20,1623,794],{},[20,1625,756],{},"을 다룰 수 있으면, 현실의 MPD의 95%를 커버.",[11,1628,1629,1630,244],{},"다음은 ",[14,1631,1633,1634,1637],{"href":1632},"\u002Fko\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff","fMP4의 결합이 왜 되는지, ",[20,1635,1636],{},"moof"," 박스에 뭐가 들었는지, 왜 \"리먹스하고 트랜스코딩하지 않는\"게 올바른 철학인지",[1639,1640],"hr",{},[38,1642,1644],{"id":1643},"추천-글","추천 글",[119,1646,1647,1655,1664,1672,1681,1689],{},[122,1648,1649,1654],{},[62,1650,1651],{},[14,1652,1653],{"href":16},"DASH 스트리밍이란?MPD 파일 해설","——이 글의 입문판",[122,1656,1657,1663],{},[62,1658,1659],{},[14,1660,1662],{"href":1661},"\u002Fko\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack","HLS 딥: 암호화, 멀티트랙, 기억해야 할 EXT-X 태그","——HLS 쪽의 대응물",[122,1665,1666,1671],{},[62,1667,1668],{},[14,1669,1670],{"href":1632},"브라우저에서 비디오 리먹스: fMP4, ISOBMFF, 트랜스코딩하지 않는 이유","——DASH 세그먼트를 다운로드한 후 무슨 일이 일어나는가",[122,1673,1674,1680],{},[62,1675,1676],{},[14,1677,1679],{"href":1678},"\u002Fko\u002Fblog\u002Fis-it-legal-to-download-streaming-video","스트리밍 다운로드는 합법인가?기술과 법적 체크리스트","——ContentProtection 태그가 왜 하드한 선인가",[122,1682,1683,1688],{},[62,1684,1685],{},[14,1686,1687],{"href":1497},"FlowPick은 어떻게 브라우저에서 수백 개의 비디오 세그먼트를 하나의 MP4로 합치는가","——여기서의 패턴이 FlowPick에서 어떻게 구현되는가",[122,1690,1691,1697],{},[62,1692,1693],{},[14,1694,1696],{"href":1695},"\u002Fko\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs + Web Workers + OPFS: 브라우저 비디오 처리 실전","——FFmpeg WASM을 보완하는 더 저수준 브라우저 API",[1699,1700,1701],"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":1703},[1704,1705,1708,1709,1712,1713,1714,1715,1716,1717,1718],{"id":40,"depth":87,"text":41},{"id":68,"depth":87,"text":69,"children":1706},[1707],{"id":401,"depth":93,"text":402},{"id":501,"depth":87,"text":502},{"id":624,"depth":87,"text":625,"children":1710},[1711],{"id":760,"depth":93,"text":761},{"id":801,"depth":87,"text":802},{"id":864,"depth":87,"text":865},{"id":925,"depth":87,"text":926},{"id":1502,"depth":87,"text":1503},{"id":1567,"depth":87,"text":1568},{"id":1606,"depth":87,"text":1606},{"id":1643,"depth":87,"text":1644},"tips","2026-08-04","MPD 입문의 그 다음——SegmentTemplate vs SegmentTimeline, $Number$ vs $Time$, ContentProtection 시그널링, 그리고 DASH가 HLS보다 파싱은 어려운데 다운로드는 쉬운 구조적 이유.","md","\u002Fscreenshots\u002Fdash-downloader.png",{},"\u002Fko\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",{"title":5,"description":1721},"ko\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection",[1729,1730,1731,1732,1733,1734],"dash","mpd","딥","segmenttemplate","drm","스트리밍","xlZ-T7V0-m9SeYvsSKrzcAdVhzRmR5MaokuHHsVoNe4",[1737,1742],{"title":1738,"path":1739,"stem":1740,"description":1741,"date":1720,"category":1719,"children":-1},"브라우저 비디오 처리 성능 벤치마크: FFmpeg WASM vs WebCodecs vs 네이티브","\u002Fko\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","ko\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","실제 하드웨어에서의 실제 데이터——1080p\u002F4K\u002F8K 리먹스와 디코딩 벤치마크. FFmpeg WASM, WebCodecs, 네이티브 FFmpeg을 횡단. 방법론, 생 데이터, 숫자가 의미하는 바 포함.",{"title":1743,"path":1744,"stem":1745,"description":1746,"date":1747,"category":1748,"children":-1},"FlowPick v1.0.0 릴리즈 노트: 첫 공개 드롭","\u002Fko\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","ko\u002Fblog\u002Fflowpick-v1-0-0-first-public-release","FlowPick v1.0.0 출시——HLS\u002FDASH 스트림, 오디오, 이미지를 자동 감지하고 WebAssembly FFmpeg로 브라우저 내에서 프래그먼트를 병합하며, 모든 것을 로컬에서 처리하는 브라우저 확장. 첫날 출시된 내용은 이것이다.","2025-04-20","updates",1787670593042]