[{"data":1,"prerenderedAt":6518},["ShallowReactive",2],{"navigation":3,"blog-posts-en":713},[4,170,307,444,580],{"title":5,"path":6,"stem":7,"children":8,"page":32},"Docs","\u002Fdocs","1.docs",[9,33,74,95,134,154],{"title":10,"path":11,"stem":12,"children":13,"icon":32},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started\u002F1.index",[14,17,22,27],{"title":15,"path":11,"stem":12,"icon":16},"Introduction","i-lucide-house",{"title":18,"path":19,"stem":20,"icon":21},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","1.docs\u002F1.getting-started\u002F2.installation","i-lucide-download",{"title":23,"path":24,"stem":25,"icon":26},"Usage Guide","\u002Fdocs\u002Fgetting-started\u002Fusage","1.docs\u002F1.getting-started\u002F3.usage","i-lucide-sliders",{"title":28,"path":29,"stem":30,"icon":31},"Configuration Reference","\u002Fdocs\u002Fgetting-started\u002Fconfiguration","1.docs\u002F1.getting-started\u002F4.configuration","i-lucide-settings",false,{"title":34,"icon":35,"path":36,"stem":37,"children":38,"page":32},"Features","i-lucide-star","\u002Fdocs\u002Ffeatures","1.docs\u002F2.features",[39,44,49,54,59,64,69],{"title":40,"path":41,"stem":42,"icon":43},"Video Sniffing","\u002Fdocs\u002Ffeatures\u002Fvideo-sniffing","1.docs\u002F2.features\u002F1.video-sniffing","i-lucide-video",{"title":45,"path":46,"stem":47,"icon":48},"Audio Capture","\u002Fdocs\u002Ffeatures\u002Faudio-capture","1.docs\u002F2.features\u002F2.audio-capture","i-lucide-music",{"title":50,"path":51,"stem":52,"icon":53},"Image Download","\u002Fdocs\u002Ffeatures\u002Fimage-download","1.docs\u002F2.features\u002F3.image-download","i-lucide-image",{"title":55,"path":56,"stem":57,"icon":58},"Privacy & Security","\u002Fdocs\u002Ffeatures\u002Fprivacy-security","1.docs\u002F2.features\u002F4.privacy-security","i-lucide-shield-check",{"title":60,"path":61,"stem":62,"icon":63},"Format Conversion","\u002Fdocs\u002Ffeatures\u002Fformat-conversion","1.docs\u002F2.features\u002F5.format-conversion","i-lucide-arrow-left-right",{"title":65,"path":66,"stem":67,"icon":68},"Batch Download","\u002Fdocs\u002Ffeatures\u002Fbatch-download","1.docs\u002F2.features\u002F6.batch-download","i-lucide-layers",{"title":70,"path":71,"stem":72,"icon":73},"Keyboard Shortcuts","\u002Fdocs\u002Ffeatures\u002Fkeyboard-shortcuts","1.docs\u002F2.features\u002F7.keyboard-shortcuts","i-lucide-keyboard",{"title":75,"icon":76,"path":77,"stem":78,"children":79,"page":32},"Advanced","i-lucide-book-open","\u002Fdocs\u002Fadvanced","1.docs\u002F3.advanced",[80,85,90],{"title":81,"path":82,"stem":83,"icon":84},"Online Tools","\u002Fdocs\u002Fadvanced\u002Fonline-tools","1.docs\u002F3.advanced\u002F1.online-tools","i-lucide-globe",{"title":86,"path":87,"stem":88,"icon":89},"Browser Compatibility","\u002Fdocs\u002Fadvanced\u002Fbrowser-compatibility","1.docs\u002F3.advanced\u002F2.browser-compatibility","i-lucide-monitor-check",{"title":91,"path":92,"stem":93,"icon":94},"Download Engine Architecture","\u002Fdocs\u002Fadvanced\u002Fdownload-engine","1.docs\u002F3.advanced\u002F3.download-engine","i-lucide-cpu",{"title":96,"icon":97,"path":98,"stem":99,"children":100,"page":32},"Use Cases","i-lucide-lightbulb","\u002Fdocs\u002Fusecases","1.docs\u002F4.usecases",[101,106,110,115,120,124,129],{"title":102,"path":103,"stem":104,"icon":105},"Online Courses & Educational Platforms","\u002Fdocs\u002Fusecases\u002Fonline-courses","1.docs\u002F4.usecases\u002F1.online-courses","i-lucide-graduation-cap",{"title":107,"path":108,"stem":109,"icon":43},"Video Platforms & UGC Content","\u002Fdocs\u002Fusecases\u002Fvideo-platforms","1.docs\u002F4.usecases\u002F2.video-platforms",{"title":111,"path":112,"stem":113,"icon":114},"Live Replays & Event Recordings","\u002Fdocs\u002Fusecases\u002Flive-replays","1.docs\u002F4.usecases\u002F3.live-replays","i-lucide-radio",{"title":116,"path":117,"stem":118,"icon":119},"Podcasts & Audio Content","\u002Fdocs\u002Fusecases\u002Fpodcasts-audio","1.docs\u002F4.usecases\u002F4.podcasts-audio","i-lucide-headphones",{"title":121,"path":122,"stem":123,"icon":53},"Image Galleries & Wallpaper Batch Download","\u002Fdocs\u002Fusecases\u002Fimage-galleries","1.docs\u002F4.usecases\u002F5.image-galleries",{"title":125,"path":126,"stem":127,"icon":128},"Corporate Training & Internal Videos","\u002Fdocs\u002Fusecases\u002Fcorporate-training","1.docs\u002F4.usecases\u002F6.corporate-training","i-lucide-building-2",{"title":130,"path":131,"stem":132,"icon":133},"Personal Media Archiving & Collection","\u002Fdocs\u002Fusecases\u002Fpersonal-archiving","1.docs\u002F4.usecases\u002F7.personal-archiving","i-lucide-archive",{"title":135,"icon":136,"path":137,"stem":138,"children":139,"page":32},"Troubleshooting","i-lucide-wrench","\u002Fdocs\u002Ftroubleshooting","1.docs\u002F5.troubleshooting",[140,144,149],{"title":141,"path":142,"stem":143,"icon":136},"Common Issues Troubleshooting","\u002Fdocs\u002Ftroubleshooting\u002Fcommon-issues","1.docs\u002F5.troubleshooting\u002F1.common-issues",{"title":145,"path":146,"stem":147,"icon":148},"Known Limitations & Caveats","\u002Fdocs\u002Ftroubleshooting\u002Fknown-issues","1.docs\u002F5.troubleshooting\u002F2.known-issues","i-lucide-alert-triangle",{"title":150,"path":151,"stem":152,"icon":153},"FAQ","\u002Fdocs\u002Ftroubleshooting\u002Ffaq","1.docs\u002F5.troubleshooting\u002F3.faq","i-lucide-message-circle-question",{"title":155,"icon":156,"path":157,"stem":158,"children":159,"page":32},"Developer Docs","i-lucide-code","\u002Fdocs\u002Fdeveloper","1.docs\u002F6.developer",[160,165],{"title":161,"path":162,"stem":163,"icon":164},"Contributing Guide","\u002Fdocs\u002Fdeveloper\u002Fcontributing","1.docs\u002F6.developer\u002F1.contributing","i-lucide-git-pull-request",{"title":166,"path":167,"stem":168,"icon":169},"Project Architecture","\u002Fdocs\u002Fdeveloper\u002Farchitecture","1.docs\u002F6.developer\u002F2.architecture","i-lucide-box",{"title":171,"path":172,"stem":173,"children":174,"page":32},"Ja","\u002Fja","ja",[175,194,227,244,277,294],{"title":176,"path":177,"stem":178,"children":179,"icon":32},"クイックスタート","\u002Fja\u002Fgetting-started","ja\u002F1.getting-started\u002F1.index",[180,182,186,190],{"title":181,"path":177,"stem":178,"icon":16},"概要",{"title":183,"path":184,"stem":185,"icon":21},"インストール","\u002Fja\u002Fgetting-started\u002Finstallation","ja\u002F1.getting-started\u002F2.installation",{"title":187,"path":188,"stem":189,"icon":26},"使用方法","\u002Fja\u002Fgetting-started\u002Fusage","ja\u002F1.getting-started\u002F3.usage",{"title":191,"path":192,"stem":193,"icon":31},"設定リファレンス","\u002Fja\u002Fgetting-started\u002Fconfiguration","ja\u002F1.getting-started\u002F4.configuration",{"title":195,"icon":35,"path":196,"stem":197,"children":198,"page":32},"機能","\u002Fja\u002Ffeatures","ja\u002F2.features",[199,203,207,211,215,219,223],{"title":200,"path":201,"stem":202,"icon":43},"動画検知","\u002Fja\u002Ffeatures\u002Fvideo-sniffing","ja\u002F2.features\u002F1.video-sniffing",{"title":204,"path":205,"stem":206,"icon":48},"音声キャプチャ","\u002Fja\u002Ffeatures\u002Faudio-capture","ja\u002F2.features\u002F2.audio-capture",{"title":208,"path":209,"stem":210,"icon":53},"画像ダウンロード","\u002Fja\u002Ffeatures\u002Fimage-download","ja\u002F2.features\u002F3.image-download",{"title":212,"path":213,"stem":214,"icon":58},"プライバシーとセキュリティ","\u002Fja\u002Ffeatures\u002Fprivacy-security","ja\u002F2.features\u002F4.privacy-security",{"title":216,"path":217,"stem":218,"icon":63},"フォーマット変換","\u002Fja\u002Ffeatures\u002Fformat-conversion","ja\u002F2.features\u002F5.format-conversion",{"title":220,"path":221,"stem":222,"icon":68},"一括ダウンロード","\u002Fja\u002Ffeatures\u002Fbatch-download","ja\u002F2.features\u002F6.batch-download",{"title":224,"path":225,"stem":226,"icon":73},"キーボードショートカット","\u002Fja\u002Ffeatures\u002Fkeyboard-shortcuts","ja\u002F2.features\u002F7.keyboard-shortcuts",{"title":228,"icon":76,"path":229,"stem":230,"children":231,"page":32},"詳細情報","\u002Fja\u002Fadvanced","ja\u002F3.advanced",[232,236,240],{"title":233,"path":234,"stem":235,"icon":84},"オンラインツール","\u002Fja\u002Fadvanced\u002Fonline-tools","ja\u002F3.advanced\u002F1.online-tools",{"title":237,"path":238,"stem":239,"icon":89},"ブラウザ互換性","\u002Fja\u002Fadvanced\u002Fbrowser-compatibility","ja\u002F3.advanced\u002F2.browser-compatibility",{"title":241,"path":242,"stem":243,"icon":94},"ダウンロードエンジンアーキテクチャ","\u002Fja\u002Fadvanced\u002Fdownload-engine","ja\u002F3.advanced\u002F3.download-engine",{"title":245,"icon":97,"path":246,"stem":247,"children":248,"page":32},"ユースケース","\u002Fja\u002Fusecases","ja\u002F4.usecases",[249,253,257,261,265,269,273],{"title":250,"path":251,"stem":252,"icon":105},"オンラインコースと教育プラットフォーム","\u002Fja\u002Fusecases\u002Fonline-courses","ja\u002F4.usecases\u002F1.online-courses",{"title":254,"path":255,"stem":256,"icon":43},"動画プラットフォームと UGC コンテンツ","\u002Fja\u002Fusecases\u002Fvideo-platforms","ja\u002F4.usecases\u002F2.video-platforms",{"title":258,"path":259,"stem":260,"icon":114},"ライブ配信アーカイブと競技録画","\u002Fja\u002Fusecases\u002Flive-replays","ja\u002F4.usecases\u002F3.live-replays",{"title":262,"path":263,"stem":264,"icon":119},"ポッドキャストとオーディオコンテンツ","\u002Fja\u002Fusecases\u002Fpodcasts-audio","ja\u002F4.usecases\u002F4.podcasts-audio",{"title":266,"path":267,"stem":268,"icon":53},"ギャラリーと壁紙の一括ダウンロード","\u002Fja\u002Fusecases\u002Fimage-galleries","ja\u002F4.usecases\u002F5.image-galleries",{"title":270,"path":271,"stem":272,"icon":128},"企業研修と内部動画","\u002Fja\u002Fusecases\u002Fcorporate-training","ja\u002F4.usecases\u002F6.corporate-training",{"title":274,"path":275,"stem":276,"icon":133},"個人メディアコレクションとアーカイブ","\u002Fja\u002Fusecases\u002Fpersonal-archiving","ja\u002F4.usecases\u002F7.personal-archiving",{"title":278,"icon":136,"path":279,"stem":280,"children":281,"page":32},"トラブルシューティング","\u002Fja\u002Ftroubleshooting","ja\u002F5.troubleshooting",[282,286,290],{"title":283,"path":284,"stem":285,"icon":136},"一般的な問題のトラブルシューティング","\u002Fja\u002Ftroubleshooting\u002Fcommon-issues","ja\u002F5.troubleshooting\u002F1.common-issues",{"title":287,"path":288,"stem":289,"icon":148},"既知の制限事項と注意点","\u002Fja\u002Ftroubleshooting\u002Fknown-issues","ja\u002F5.troubleshooting\u002F2.known-issues",{"title":291,"path":292,"stem":293,"icon":153},"よくある質問","\u002Fja\u002Ftroubleshooting\u002Ffaq","ja\u002F5.troubleshooting\u002F3.faq",{"title":295,"icon":156,"path":296,"stem":297,"children":298,"page":32},"開発者ガイド","\u002Fja\u002Fdeveloper","ja\u002F6.developer",[299,303],{"title":300,"path":301,"stem":302,"icon":164},"貢献ガイド","\u002Fja\u002Fdeveloper\u002Fcontributing","ja\u002F6.developer\u002F1.contributing",{"title":304,"path":305,"stem":306,"icon":169},"プロジェクトアーキテクチャ","\u002Fja\u002Fdeveloper\u002Farchitecture","ja\u002F6.developer\u002F2.architecture",{"title":308,"path":309,"stem":310,"children":311,"page":32},"Ko","\u002Fko","ko",[312,331,364,381,414,431],{"title":313,"path":314,"stem":315,"children":316,"icon":32},"빠른 시작","\u002Fko\u002Fgetting-started","ko\u002F1.getting-started\u002F1.index",[317,319,323,327],{"title":318,"path":314,"stem":315,"icon":16},"소개",{"title":320,"path":321,"stem":322,"icon":21},"설치","\u002Fko\u002Fgetting-started\u002Finstallation","ko\u002F1.getting-started\u002F2.installation",{"title":324,"path":325,"stem":326,"icon":26},"사용 방법","\u002Fko\u002Fgetting-started\u002Fusage","ko\u002F1.getting-started\u002F3.usage",{"title":328,"path":329,"stem":330,"icon":31},"설정 참조","\u002Fko\u002Fgetting-started\u002Fconfiguration","ko\u002F1.getting-started\u002F4.configuration",{"title":332,"icon":35,"path":333,"stem":334,"children":335,"page":32},"기능","\u002Fko\u002Ffeatures","ko\u002F2.features",[336,340,344,348,352,356,360],{"title":337,"path":338,"stem":339,"icon":43},"비디오 스니핑","\u002Fko\u002Ffeatures\u002Fvideo-sniffing","ko\u002F2.features\u002F1.video-sniffing",{"title":341,"path":342,"stem":343,"icon":48},"오디오 캡처","\u002Fko\u002Ffeatures\u002Faudio-capture","ko\u002F2.features\u002F2.audio-capture",{"title":345,"path":346,"stem":347,"icon":53},"이미지 다운로드","\u002Fko\u002Ffeatures\u002Fimage-download","ko\u002F2.features\u002F3.image-download",{"title":349,"path":350,"stem":351,"icon":58},"개인정보 보호와 보안","\u002Fko\u002Ffeatures\u002Fprivacy-security","ko\u002F2.features\u002F4.privacy-security",{"title":353,"path":354,"stem":355,"icon":63},"포맷 변환","\u002Fko\u002Ffeatures\u002Fformat-conversion","ko\u002F2.features\u002F5.format-conversion",{"title":357,"path":358,"stem":359,"icon":68},"일괄 다운로드","\u002Fko\u002Ffeatures\u002Fbatch-download","ko\u002F2.features\u002F6.batch-download",{"title":361,"path":362,"stem":363,"icon":73},"키보드 단축키","\u002Fko\u002Ffeatures\u002Fkeyboard-shortcuts","ko\u002F2.features\u002F7.keyboard-shortcuts",{"title":365,"icon":76,"path":366,"stem":367,"children":368,"page":32},"고급 가이드","\u002Fko\u002Fadvanced","ko\u002F3.advanced",[369,373,377],{"title":370,"path":371,"stem":372,"icon":84},"온라인 도구","\u002Fko\u002Fadvanced\u002Fonline-tools","ko\u002F3.advanced\u002F1.online-tools",{"title":374,"path":375,"stem":376,"icon":89},"브라우저 호환성","\u002Fko\u002Fadvanced\u002Fbrowser-compatibility","ko\u002F3.advanced\u002F2.browser-compatibility",{"title":378,"path":379,"stem":380,"icon":94},"다운로드 엔진 아키텍처","\u002Fko\u002Fadvanced\u002Fdownload-engine","ko\u002F3.advanced\u002F3.download-engine",{"title":382,"icon":97,"path":383,"stem":384,"children":385,"page":32},"사용 사례","\u002Fko\u002Fusecases","ko\u002F4.usecases",[386,390,394,398,402,406,410],{"title":387,"path":388,"stem":389,"icon":105},"온라인 강좌 및 교육 플랫폼","\u002Fko\u002Fusecases\u002Fonline-courses","ko\u002F4.usecases\u002F1.online-courses",{"title":391,"path":392,"stem":393,"icon":43},"비디오 플랫폼 및 UGC 콘텐츠","\u002Fko\u002Fusecases\u002Fvideo-platforms","ko\u002F4.usecases\u002F2.video-platforms",{"title":395,"path":396,"stem":397,"icon":114},"라이브 방송 재생 및 경기 녹화","\u002Fko\u002Fusecases\u002Flive-replays","ko\u002F4.usecases\u002F3.live-replays",{"title":399,"path":400,"stem":401,"icon":119},"팟캐스트 및 오디오 콘텐츠","\u002Fko\u002Fusecases\u002Fpodcasts-audio","ko\u002F4.usecases\u002F4.podcasts-audio",{"title":403,"path":404,"stem":405,"icon":53},"이미지 갤러리 및 배경화면 일괄 다운로드","\u002Fko\u002Fusecases\u002Fimage-galleries","ko\u002F4.usecases\u002F5.image-galleries",{"title":407,"path":408,"stem":409,"icon":128},"기업 교육 및 내부 비디오","\u002Fko\u002Fusecases\u002Fcorporate-training","ko\u002F4.usecases\u002F6.corporate-training",{"title":411,"path":412,"stem":413,"icon":133},"개인 미디어 수집 및 보관","\u002Fko\u002Fusecases\u002Fpersonal-archiving","ko\u002F4.usecases\u002F7.personal-archiving",{"title":415,"icon":136,"path":416,"stem":417,"children":418,"page":32},"문제 해결","\u002Fko\u002Ftroubleshooting","ko\u002F5.troubleshooting",[419,423,427],{"title":420,"path":421,"stem":422,"icon":136},"일반적인 문제 해결","\u002Fko\u002Ftroubleshooting\u002Fcommon-issues","ko\u002F5.troubleshooting\u002F1.common-issues",{"title":424,"path":425,"stem":426,"icon":148},"알려진 제한 사항 및 주의사항","\u002Fko\u002Ftroubleshooting\u002Fknown-issues","ko\u002F5.troubleshooting\u002F2.known-issues",{"title":428,"path":429,"stem":430,"icon":153},"자주 묻는 질문","\u002Fko\u002Ftroubleshooting\u002Ffaq","ko\u002F5.troubleshooting\u002F3.faq",{"title":432,"icon":156,"path":433,"stem":434,"children":435,"page":32},"개발자 문서","\u002Fko\u002Fdeveloper","ko\u002F6.developer",[436,440],{"title":437,"path":438,"stem":439,"icon":164},"기여 가이드라인","\u002Fko\u002Fdeveloper\u002Fcontributing","ko\u002F6.developer\u002F1.contributing",{"title":441,"path":442,"stem":443,"icon":169},"프로젝트 아키텍처","\u002Fko\u002Fdeveloper\u002Farchitecture","ko\u002F6.developer\u002F2.architecture",{"title":445,"path":446,"stem":447,"children":448,"page":32},"Zh Hans","\u002Fzh-hans","zh-Hans",[449,467,500,517,550,567],{"title":450,"path":451,"stem":452,"children":453,"icon":32},"快速开始","\u002Fzh-hans\u002Fgetting-started","zh-Hans\u002F1.getting-started\u002F1.index",[454,456,460,463],{"title":455,"path":451,"stem":452,"icon":16},"简介",{"title":457,"path":458,"stem":459,"icon":21},"安装","\u002Fzh-hans\u002Fgetting-started\u002Finstallation","zh-Hans\u002F1.getting-started\u002F2.installation",{"title":187,"path":461,"stem":462,"icon":26},"\u002Fzh-hans\u002Fgetting-started\u002Fusage","zh-Hans\u002F1.getting-started\u002F3.usage",{"title":464,"path":465,"stem":466,"icon":31},"配置参考","\u002Fzh-hans\u002Fgetting-started\u002Fconfiguration","zh-Hans\u002F1.getting-started\u002F4.configuration",{"title":468,"icon":35,"path":469,"stem":470,"children":471,"page":32},"功能特性","\u002Fzh-hans\u002Ffeatures","zh-Hans\u002F2.features",[472,476,480,484,488,492,496],{"title":473,"path":474,"stem":475,"icon":43},"视频嗅探","\u002Fzh-hans\u002Ffeatures\u002Fvideo-sniffing","zh-Hans\u002F2.features\u002F1.video-sniffing",{"title":477,"path":478,"stem":479,"icon":48},"音频捕获","\u002Fzh-hans\u002Ffeatures\u002Faudio-capture","zh-Hans\u002F2.features\u002F2.audio-capture",{"title":481,"path":482,"stem":483,"icon":53},"图片下载","\u002Fzh-hans\u002Ffeatures\u002Fimage-download","zh-Hans\u002F2.features\u002F3.image-download",{"title":485,"path":486,"stem":487,"icon":58},"隐私与安全","\u002Fzh-hans\u002Ffeatures\u002Fprivacy-security","zh-Hans\u002F2.features\u002F4.privacy-security",{"title":489,"path":490,"stem":491,"icon":63},"格式转换","\u002Fzh-hans\u002Ffeatures\u002Fformat-conversion","zh-Hans\u002F2.features\u002F5.format-conversion",{"title":493,"path":494,"stem":495,"icon":68},"批量下载","\u002Fzh-hans\u002Ffeatures\u002Fbatch-download","zh-Hans\u002F2.features\u002F6.batch-download",{"title":497,"path":498,"stem":499,"icon":73},"键盘快捷键","\u002Fzh-hans\u002Ffeatures\u002Fkeyboard-shortcuts","zh-Hans\u002F2.features\u002F7.keyboard-shortcuts",{"title":501,"icon":76,"path":502,"stem":503,"children":504,"page":32},"高级指南","\u002Fzh-hans\u002Fadvanced","zh-Hans\u002F3.advanced",[505,509,513],{"title":506,"path":507,"stem":508,"icon":84},"在线工具","\u002Fzh-hans\u002Fadvanced\u002Fonline-tools","zh-Hans\u002F3.advanced\u002F1.online-tools",{"title":510,"path":511,"stem":512,"icon":89},"浏览器兼容性","\u002Fzh-hans\u002Fadvanced\u002Fbrowser-compatibility","zh-Hans\u002F3.advanced\u002F2.browser-compatibility",{"title":514,"path":515,"stem":516,"icon":94},"下载引擎架构","\u002Fzh-hans\u002Fadvanced\u002Fdownload-engine","zh-Hans\u002F3.advanced\u002F3.download-engine",{"title":518,"icon":97,"path":519,"stem":520,"children":521,"page":32},"使用场景","\u002Fzh-hans\u002Fusecases","zh-Hans\u002F4.usecases",[522,526,530,534,538,542,546],{"title":523,"path":524,"stem":525,"icon":105},"在线课程与教育平台","\u002Fzh-hans\u002Fusecases\u002Fonline-courses","zh-Hans\u002F4.usecases\u002F1.online-courses",{"title":527,"path":528,"stem":529,"icon":43},"视频平台与 UGC 内容","\u002Fzh-hans\u002Fusecases\u002Fvideo-platforms","zh-Hans\u002F4.usecases\u002F2.video-platforms",{"title":531,"path":532,"stem":533,"icon":114},"直播回放与赛事录像","\u002Fzh-hans\u002Fusecases\u002Flive-replays","zh-Hans\u002F4.usecases\u002F3.live-replays",{"title":535,"path":536,"stem":537,"icon":119},"播客与音频内容","\u002Fzh-hans\u002Fusecases\u002Fpodcasts-audio","zh-Hans\u002F4.usecases\u002F4.podcasts-audio",{"title":539,"path":540,"stem":541,"icon":53},"图库与壁纸批量下载","\u002Fzh-hans\u002Fusecases\u002Fimage-galleries","zh-Hans\u002F4.usecases\u002F5.image-galleries",{"title":543,"path":544,"stem":545,"icon":128},"企业培训与内部视频","\u002Fzh-hans\u002Fusecases\u002Fcorporate-training","zh-Hans\u002F4.usecases\u002F6.corporate-training",{"title":547,"path":548,"stem":549,"icon":133},"个人媒体收藏与归档","\u002Fzh-hans\u002Fusecases\u002Fpersonal-archiving","zh-Hans\u002F4.usecases\u002F7.personal-archiving",{"title":551,"icon":136,"path":552,"stem":553,"children":554,"page":32},"故障排除","\u002Fzh-hans\u002Ftroubleshooting","zh-Hans\u002F5.troubleshooting",[555,559,563],{"title":556,"path":557,"stem":558,"icon":136},"常见问题排查","\u002Fzh-hans\u002Ftroubleshooting\u002Fcommon-issues","zh-Hans\u002F5.troubleshooting\u002F1.common-issues",{"title":560,"path":561,"stem":562,"icon":148},"已知限制与注意事项","\u002Fzh-hans\u002Ftroubleshooting\u002Fknown-issues","zh-Hans\u002F5.troubleshooting\u002F2.known-issues",{"title":564,"path":565,"stem":566,"icon":153},"常见问题解答","\u002Fzh-hans\u002Ftroubleshooting\u002Ffaq","zh-Hans\u002F5.troubleshooting\u002F3.faq",{"title":568,"icon":156,"path":569,"stem":570,"children":571,"page":32},"开发者文档","\u002Fzh-hans\u002Fdeveloper","zh-Hans\u002F6.developer",[572,576],{"title":573,"path":574,"stem":575,"icon":164},"贡献指南","\u002Fzh-hans\u002Fdeveloper\u002Fcontributing","zh-Hans\u002F6.developer\u002F1.contributing",{"title":577,"path":578,"stem":579,"icon":169},"项目架构","\u002Fzh-hans\u002Fdeveloper\u002Farchitecture","zh-Hans\u002F6.developer\u002F2.architecture",{"title":581,"path":582,"stem":583,"children":584,"page":32},"Zh Hant","\u002Fzh-hant","zh-Hant",[585,603,636,652,684,701],{"title":586,"path":587,"stem":588,"children":589,"icon":32},"快速入門","\u002Fzh-hant\u002Fgetting-started","zh-Hant\u002F1.getting-started\u002F1.index",[590,592,596,599],{"title":591,"path":587,"stem":588,"icon":16},"簡介",{"title":593,"path":594,"stem":595,"icon":21},"安裝","\u002Fzh-hant\u002Fgetting-started\u002Finstallation","zh-Hant\u002F1.getting-started\u002F2.installation",{"title":187,"path":597,"stem":598,"icon":26},"\u002Fzh-hant\u002Fgetting-started\u002Fusage","zh-Hant\u002F1.getting-started\u002F3.usage",{"title":600,"path":601,"stem":602,"icon":31},"配置參考","\u002Fzh-hant\u002Fgetting-started\u002Fconfiguration","zh-Hant\u002F1.getting-started\u002F4.configuration",{"title":604,"icon":35,"path":605,"stem":606,"children":607,"page":32},"功能特色","\u002Fzh-hant\u002Ffeatures","zh-Hant\u002F2.features",[608,612,616,620,624,628,632],{"title":609,"path":610,"stem":611,"icon":43},"影片嗅探","\u002Fzh-hant\u002Ffeatures\u002Fvideo-sniffing","zh-Hant\u002F2.features\u002F1.video-sniffing",{"title":613,"path":614,"stem":615,"icon":48},"音訊擷取","\u002Fzh-hant\u002Ffeatures\u002Faudio-capture","zh-Hant\u002F2.features\u002F2.audio-capture",{"title":617,"path":618,"stem":619,"icon":53},"圖片下載","\u002Fzh-hant\u002Ffeatures\u002Fimage-download","zh-Hant\u002F2.features\u002F3.image-download",{"title":621,"path":622,"stem":623,"icon":58},"隱私與安全","\u002Fzh-hant\u002Ffeatures\u002Fprivacy-security","zh-Hant\u002F2.features\u002F4.privacy-security",{"title":625,"path":626,"stem":627,"icon":63},"格式轉換","\u002Fzh-hant\u002Ffeatures\u002Fformat-conversion","zh-Hant\u002F2.features\u002F5.format-conversion",{"title":629,"path":630,"stem":631,"icon":68},"批次下載","\u002Fzh-hant\u002Ffeatures\u002Fbatch-download","zh-Hant\u002F2.features\u002F6.batch-download",{"title":633,"path":634,"stem":635,"icon":73},"鍵盤快速鍵","\u002Fzh-hant\u002Ffeatures\u002Fkeyboard-shortcuts","zh-Hant\u002F2.features\u002F7.keyboard-shortcuts",{"title":501,"icon":76,"path":637,"stem":638,"children":639,"page":32},"\u002Fzh-hant\u002Fadvanced","zh-Hant\u002F3.advanced",[640,644,648],{"title":641,"path":642,"stem":643,"icon":84},"線上工具","\u002Fzh-hant\u002Fadvanced\u002Fonline-tools","zh-Hant\u002F3.advanced\u002F1.online-tools",{"title":645,"path":646,"stem":647,"icon":89},"瀏覽器相容性","\u002Fzh-hant\u002Fadvanced\u002Fbrowser-compatibility","zh-Hant\u002F3.advanced\u002F2.browser-compatibility",{"title":649,"path":650,"stem":651,"icon":94},"下載引擎架構","\u002Fzh-hant\u002Fadvanced\u002Fdownload-engine","zh-Hant\u002F3.advanced\u002F3.download-engine",{"title":518,"icon":97,"path":653,"stem":654,"children":655,"page":32},"\u002Fzh-hant\u002Fusecases","zh-Hant\u002F4.usecases",[656,660,664,668,672,676,680],{"title":657,"path":658,"stem":659,"icon":105},"線上課程與教育平台","\u002Fzh-hant\u002Fusecases\u002Fonline-courses","zh-Hant\u002F4.usecases\u002F1.online-courses",{"title":661,"path":662,"stem":663,"icon":43},"影片平台與 UGC 內容","\u002Fzh-hant\u002Fusecases\u002Fvideo-platforms","zh-Hant\u002F4.usecases\u002F2.video-platforms",{"title":665,"path":666,"stem":667,"icon":114},"直播回放與賽事錄影","\u002Fzh-hant\u002Fusecases\u002Flive-replays","zh-Hant\u002F4.usecases\u002F3.live-replays",{"title":669,"path":670,"stem":671,"icon":119},"播客與音訊內容","\u002Fzh-hant\u002Fusecases\u002Fpodcasts-audio","zh-Hant\u002F4.usecases\u002F4.podcasts-audio",{"title":673,"path":674,"stem":675,"icon":53},"圖庫與桌布批次下載","\u002Fzh-hant\u002Fusecases\u002Fimage-galleries","zh-Hant\u002F4.usecases\u002F5.image-galleries",{"title":677,"path":678,"stem":679,"icon":128},"企業培訓與內部影片","\u002Fzh-hant\u002Fusecases\u002Fcorporate-training","zh-Hant\u002F4.usecases\u002F6.corporate-training",{"title":681,"path":682,"stem":683,"icon":133},"個人媒體收藏與歸檔","\u002Fzh-hant\u002Fusecases\u002Fpersonal-archiving","zh-Hant\u002F4.usecases\u002F7.personal-archiving",{"title":685,"icon":136,"path":686,"stem":687,"children":688,"page":32},"疑難排解","\u002Fzh-hant\u002Ftroubleshooting","zh-Hant\u002F5.troubleshooting",[689,693,697],{"title":690,"path":691,"stem":692,"icon":136},"常見問題排查","\u002Fzh-hant\u002Ftroubleshooting\u002Fcommon-issues","zh-Hant\u002F5.troubleshooting\u002F1.common-issues",{"title":694,"path":695,"stem":696,"icon":148},"已知限制與注意事項","\u002Fzh-hant\u002Ftroubleshooting\u002Fknown-issues","zh-Hant\u002F5.troubleshooting\u002F2.known-issues",{"title":698,"path":699,"stem":700,"icon":153},"常見問題解答","\u002Fzh-hant\u002Ftroubleshooting\u002Ffaq","zh-Hant\u002F5.troubleshooting\u002F3.faq",{"title":568,"icon":156,"path":702,"stem":703,"children":704,"page":32},"\u002Fzh-hant\u002Fdeveloper","zh-Hant\u002F6.developer",[705,709],{"title":706,"path":707,"stem":708,"icon":164},"貢獻指南","\u002Fzh-hant\u002Fdeveloper\u002Fcontributing","zh-Hant\u002F6.developer\u002F1.contributing",{"title":710,"path":711,"stem":712,"icon":169},"專案架構","\u002Fzh-hant\u002Fdeveloper\u002Farchitecture","zh-Hant\u002F6.developer\u002F2.architecture",[714,1453,2591,3212,3755,4383,5002,5639],{"id":715,"title":716,"author":717,"body":718,"category":1434,"date":1435,"description":1436,"extension":1437,"image":1438,"meta":1439,"navigation":1440,"path":1441,"readingTime":1442,"seo":1443,"stem":1444,"tags":1445,"__hash__":1452},"blog\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser.md","How FlowPick Merges Hundreds of Video Segments into MP4 in the Browser","FlowPick Team",{"type":719,"value":720,"toc":1416},"minimark",[721,725,728,731,736,753,760,763,785,788,792,795,806,813,820,823,827,830,842,849,915,926,929,933,936,941,955,958,968,971,1027,1032,1035,1111,1114,1124,1128,1131,1142,1145,1148,1169,1172,1175,1179,1182,1189,1192,1195,1199,1202,1208,1215,1218,1224,1228,1235,1238,1252,1255,1261,1267,1271,1274,1277,1280,1284,1287,1296,1302,1305,1309,1312,1318,1324,1330,1339,1345,1348,1354,1356,1360],[722,723,724],"p",{},"You click FlowPick's download button, wait a few minutes, and a complete MP4 shows up in your downloads folder.",[722,726,727],{},"This is a bit counterintuitive. Browsers aren't download tools, and they're certainly not video processing tools. Traditional streaming download solutions invariably rely on locally installed programs, or they send data to a server for processing and send it back.",[722,729,730],{},"FlowPick does everything inside a browser tab — no server, no app, no command line. This article explains why that's technically feasible and how it actually works.",[732,733,735],"h2",{"id":734},"first-understand-the-problem","First, Understand the Problem",[722,737,738,739,743,744,747,748,752],{},"An HLS video in transit is not a single complete file. It's a ",[740,741,742],"strong",{},"playlist file"," (M3U8) plus ",[740,745,746],{},"hundreds of 2-10 second segments"," (",[749,750,751],"code",{},".ts"," files). The player reads the playlist, pulls segments in order, and stitches them together in real time for playback.",[722,754,755,756,759],{},"A 30-minute 1080P video with 10-second segments means 180 files. DASH is similar, except the segments are ",[749,757,758],{},".m4s"," and audio and video travel in two separate streams.",[722,761,762],{},"What does \"downloading\" this video actually mean?",[764,765,766,770,773,776,779,782],"ol",{},[767,768,769],"li",{},"Parse the M3U8\u002FMPD to get all segment URLs",[767,771,772],{},"Download hundreds of segments one by one",[767,774,775],{},"If AES-128 encrypted, decrypt each segment",[767,777,778],{},"Stitch hundreds of fragments into one continuous file",[767,780,781],{},"For HLS TS segments, remux into MP4 (a player-friendly format)",[767,783,784],{},"For DASH, also merge the video and audio tracks",[722,786,787],{},"This isn't a simple file download — it's a processing pipeline. Traditional tools hand this off to FFmpeg command line or a server. FlowPick moves the entire pipeline into the browser.",[732,789,791],{"id":790},"detection-comes-first-the-webrequest-api","Detection Comes First: The webRequest API",[722,793,794],{},"Before processing segments, FlowPick needs to know what videos are on the page.",[722,796,797,798,801,802,805],{},"Browser extensions can use the ",[749,799,800],{},"webRequest"," API to listen to all requests at the ",[740,803,804],{},"network layer"," — not the DOM layer, but the actual HTTP request layer. Every network request made from the current tab, FlowPick can see the request URL and response headers.",[722,807,808,809,812],{},"FlowPick scans ",[749,810,811],{},"Content-Type"," and URL patterns, recording any requests that match HLS\u002FDASH\u002Fdirect video\u002Faudio\u002Fimage signatures. When you open the FlowPick popup, the list you see is every media resource it has intercepted up to that moment.",[722,814,815],{},[816,817],"img",{"alt":818,"src":819},"FlowPick popup showing detected video and audio resources","\u002Fscreenshots\u002Fhls-detection.png",[722,821,822],{},"This also explains why FlowPick can't see a stream before the video starts playing — the M3U8 request is only made when the player initializes. FlowPick can only see requests that have already happened; it can't actively probe.",[732,824,826],{"id":825},"the-core-challenge-turning-ts-segments-into-mp4","The Core Challenge: Turning TS Segments into MP4",[722,828,829],{},"Once the video segments are downloaded, the format problem hits.",[722,831,832,833,835,836,838,839,841],{},"HLS segments are in MPEG-TS format (",[749,834,751],{},"). If you just binary-concatenate 180 ",[749,837,751],{}," files end to end, you get a big ",[749,840,751],{}," file — some players can play it, but most devices, video editing software, and video platforms prefer MP4.",[722,843,844,845,848],{},"The core difference between TS and MP4 is the ",[740,846,847],{},"container format",":",[850,851,852,867],"table",{},[853,854,855],"thead",{},[856,857,858,861,864],"tr",{},[859,860],"th",{},[859,862,863],{},"MPEG-TS",[859,865,866],{},"MP4",[868,869,870,882,893,904],"tbody",{},[856,871,872,876,879],{},[873,874,875],"td",{},"Designed for",[873,877,878],{},"Broadcast transmission, loss-tolerant",[873,880,881],{},"File storage, random access",[856,883,884,887,890],{},[873,885,886],{},"Each packet",[873,888,889],{},"Fixed 188 bytes",[873,891,892],{},"Variable-length Box structure",[856,894,895,898,901],{},[873,896,897],{},"Index location",[873,899,900],{},"No standalone index, sequential read only",[873,902,903],{},"moov atom, can be placed at file start",[856,905,906,909,912],{},[873,907,908],{},"Random seeking",[873,910,911],{},"Painful",[873,913,914],{},"Direct jump via moov index",[722,916,917,918,921,922,925],{},"Turning TS into MP4 is essentially ",[740,919,920],{},"extracting the raw video and audio data inside and repackaging it into MP4's Box structure",". This operation is called ",[740,923,924],{},"remuxing"," — it doesn't touch the codec. Every bit of video and audio stays exactly as-is; only the \"shell\" changes.",[722,927,928],{},"Remuxing doesn't need a GPU, and CPU load is minimal. Most of the time is spent on file I\u002FO. This is the technical premise that makes in-browser remuxing viable — if it required re-encoding (transcoding), browser CPU power simply couldn't handle it.",[732,930,932],{"id":931},"the-two-engine-selection-logic","The Two-Engine Selection Logic",[722,934,935],{},"FlowPick has two remuxing engines internally, switching automatically based on the scenario:",[937,938,940],"h3",{"id":939},"engine-1-ffmpeg-wasm","Engine 1: FFmpeg WASM",[722,942,943,950,951,954],{},[944,945,949],"a",{"href":946,"rel":947},"https:\u002F\u002Fffmpeg.org\u002F",[948],"nofollow","FFmpeg"," is the industry-standard command-line video processing tool, supporting virtually every format. ",[740,952,953],{},"WebAssembly (WASM)"," is a binary format that runs at near-native speed inside the browser. Compile FFmpeg to WASM, and you can invoke FFmpeg's full capabilities inside a browser tab.",[722,956,957],{},"When FlowPick calls FFmpeg WASM, the actual command executed looks roughly like this:",[959,960,965],"pre",{"className":961,"code":963,"language":964},[962],"language-text","ffmpeg -f concat -safe 0 -i filelist.txt \\\n  -c copy \\\n  -movflags +faststart \\\n  -bsf:a aac_adtstoasc \\\n  output.mp4\n","text",[749,966,963],{"__ignoreMap":967},"",[722,969,970],{},"Parameter breakdown:",[850,972,973,983],{},[853,974,975],{},[856,976,977,980],{},[859,978,979],{},"Parameter",[859,981,982],{},"What it does",[868,984,985,995,1007,1017],{},[856,986,987,992],{},[873,988,989],{},[749,990,991],{},"-f concat",[873,993,994],{},"Use the concat demuxer to handle multiple TS segments",[856,996,997,1002],{},[873,998,999],{},[749,1000,1001],{},"-c copy",[873,1003,1004],{},[740,1005,1006],{},"Stream copy, zero re-encoding",[856,1008,1009,1014],{},[873,1010,1011],{},[749,1012,1013],{},"-movflags +faststart",[873,1015,1016],{},"Move the index to the file start for streaming playback",[856,1018,1019,1024],{},[873,1020,1021],{},[749,1022,1023],{},"-bsf:a aac_adtstoasc",[873,1025,1026],{},"Convert AAC packaging format (TS uses ADTS, MP4 uses ASC)",[722,1028,1029,1031],{},[749,1030,1001],{}," is the key — it ensures FlowPick does pure remuxing with zero quality loss and no heavy CPU requirements.",[722,1033,1034],{},"WASM FFmpeg runs inside the browser's JavaScript engine, so there's a performance penalty. But remuxing is I\u002FO-bound, not CPU-bound, so real-world performance is decent. Based on measured data (Chrome + Intel i7-13700 + 16GB RAM):",[850,1036,1037,1053],{},[853,1038,1039],{},[856,1040,1041,1044,1047,1050],{},[859,1042,1043],{},"Video",[859,1045,1046],{},"Segments",[859,1048,1049],{},"File Size",[859,1051,1052],{},"FFmpeg WASM Remux Time",[868,1054,1055,1069,1083,1097],{},[856,1056,1057,1060,1063,1066],{},[873,1058,1059],{},"10 min 1080P",[873,1061,1062],{},"~60",[873,1064,1065],{},"~200 MB",[873,1067,1068],{},"~3 sec",[856,1070,1071,1074,1077,1080],{},[873,1072,1073],{},"30 min 1080P",[873,1075,1076],{},"~180",[873,1078,1079],{},"~600 MB",[873,1081,1082],{},"~8 sec",[856,1084,1085,1088,1091,1094],{},[873,1086,1087],{},"60 min 4K",[873,1089,1090],{},"~360",[873,1092,1093],{},"~2.5 GB",[873,1095,1096],{},"~30 sec",[856,1098,1099,1102,1105,1108],{},[873,1100,1101],{},"120 min 4K",[873,1103,1104],{},"~720",[873,1106,1107],{},"~5 GB",[873,1109,1110],{},"~60 sec",[722,1112,1113],{},"This speed is perfectly usable for everyday needs.",[722,1115,1116,1119,1120,1123],{},[740,1117,1118],{},"FFmpeg WASM limitations:"," WASM runs in a 32-bit address space with a theoretical 4GB memory ceiling. In practice, FFmpeg WASM needs to write all segments into its ",[740,1121,1122],{},"virtual filesystem"," (WASM memory). Large files eat up a lot of memory, and exceeding the limit causes a crash.",[937,1125,1127],{"id":1126},"engine-2-tstomp4muxer","Engine 2: TSToMP4Muxer",[722,1129,1130],{},"This is FlowPick's custom pure-JavaScript streaming remuxer, purpose-built for the single TS → MP4 scenario, but with a completely different approach.",[722,1132,1133,1134,1137,1138,1141],{},"FFmpeg WASM's model is: ",[740,1135,1136],{},"gather all segments first, then process them together",". TSToMP4Muxer's model is: ",[740,1139,1140],{},"download, process, and write to disk simultaneously",".",[722,1143,1144],{},"Specifically, TSToMP4Muxer parses TS data packets (each fixed at 188 bytes) in real time as segments come in, extracts PES (Packetized Elementary Stream) data, converts it to MP4 Box format on the fly, and continuously writes to disk via the browser's Streams API.",[722,1146,1147],{},"The benefits are clear:",[1149,1150,1151,1157,1163],"ul",{},[767,1152,1153,1156],{},[740,1154,1155],{},"Memory usage is independent of file size"," — processing a 5GB video uses roughly the same memory as a 200MB video",[767,1158,1159,1162],{},[740,1160,1161],{},"No need to load FFmpeg WASM"," (a 30MB+ WASM file), so startup is faster",[767,1164,1165,1168],{},[740,1166,1167],{},"Naturally handles large files"," — only a small chunk of data is in memory at any given time",[722,1170,1171],{},"The trade-off is narrower coverage: it only does single TS → MP4 and can't handle tasks like DASH audio\u002Fvideo merging that require two input streams.",[722,1173,1174],{},"FlowPick's switching logic: regular HLS prefers TSToMP4Muxer; DASH or dual-stream merging scenarios use FFmpeg WASM. Files over 2GB also prefer TSToMP4Muxer or direct TS output (skipping remux) to avoid WASM memory overflow.",[732,1176,1178],{"id":1177},"where-the-file-goes-file-system-access-api","Where the File Goes: File System Access API",[722,1180,1181],{},"Where does the processed video data go? This isn't a problem for traditional download tools, but browser JavaScript has no default permission to write directly to the filesystem.",[722,1183,1184,1185,1188],{},"FlowPick uses the ",[740,1186,1187],{},"File System Access API (FSA)"," provided by Chrome and Edge. After the user picks a save location, FSA provides a writable file handle that lets JavaScript write continuously in streaming mode — rather than accumulating all data in memory first and writing it out in one shot.",[722,1190,1191],{},"This means: even a 5GB 4K video only keeps the currently-processing chunk in memory at any time. FlowPick theoretically has no file size ceiling (limited only by disk space and WASM memory limits, the latter bypassed by TSToMP4Muxer).",[722,1193,1194],{},"Firefox also supports it, but uses a Blob-based approach with slightly different memory behavior and less stability for very large files.",[732,1196,1198],{"id":1197},"aes-128-decryption-web-crypto-api","AES-128 Decryption: Web Crypto API",[722,1200,1201],{},"Some HLS streams encrypt each segment. The M3U8 will have a line like:",[959,1203,1206],{"className":1204,"code":1205,"language":964},[962],"#EXT-X-KEY:METHOD=AES-128,URI=\"https:\u002F\u002Fapi.example.com\u002Fkey?token=...\"\n",[749,1207,1205],{"__ignoreMap":967},[722,1209,1210,1211,1214],{},"When FlowPick detects this tag, it first requests the key from the Key URI (carrying your current cookies and session — since the request comes from the browser extension, it's your browser's real session). Once it has the key, for each downloaded segment, it uses the browser's built-in ",[740,1212,1213],{},"Web Crypto API"," to perform AES-128-CBC decryption, then feeds the decrypted data into the remux pipeline.",[722,1216,1217],{},"The entire key retrieval and decryption process happens locally in the browser. The key never passes through any FlowPick server.",[722,1219,1220,1223],{},[740,1221,1222],{},"What FlowPick can't handle:"," Widevine, PlayReady, FairPlay, and other DRM systems. These bind decryption keys to dedicated hardware or OS modules that JavaScript can't access. This is a technical limitation, not a feature decision.",[732,1225,1227],{"id":1226},"dash-download-audio-and-video-separately-then-merge","DASH: Download Audio and Video Separately, Then Merge",[722,1229,1230,1231,1234],{},"DASH adds a layer of complexity beyond HLS — audio and video are ",[740,1232,1233],{},"two independent streams",", segmented and transmitted separately. What you download isn't a bunch of complete video segments, but a bunch of video-only segments (no sound) plus a bunch of audio-only segments (no picture).",[722,1236,1237],{},"FlowPick's DASH handling:",[764,1239,1240,1243,1246,1249],{},[767,1241,1242],{},"Parse the MPD XML, identify video AdaptationSet and audio AdaptationSet",[767,1244,1245],{},"Download both sets of segments simultaneously (in parallel)",[767,1247,1248],{},"Download init segments for each (contains codec parameters, required for merging)",[767,1250,1251],{},"Feed both streams into FFmpeg WASM for audio\u002Fvideo merging",[722,1253,1254],{},"The merge command is roughly:",[959,1256,1259],{"className":1257,"code":1258,"language":964},[962],"ffmpeg \\\n  -f concat -safe 0 -i video_list.txt \\\n  -f concat -safe 0 -i audio_list.txt \\\n  -c:v copy -c:a copy \\\n  -movflags +faststart \\\n  output.mp4\n",[749,1260,1258],{"__ignoreMap":967},[722,1262,1263,1264,1266],{},"Again, ",[749,1265,1001],{}," — zero re-encoding. Most of the time is spent on concurrent segment downloads and the final merge I\u002FO.",[732,1268,1270],{"id":1269},"concurrent-downloads-segments-dont-have-to-come-one-at-a-time","Concurrent Downloads: Segments Don't Have to Come One at a Time",[722,1272,1273],{},"If hundreds of segments were downloaded serially, speed would be entirely bottlenecked by individual HTTP request latency. FlowPick uses multi-threaded concurrent downloading, defaulting to 2 concurrent threads (a conservative value to avoid triggering server-side rate limiting), adjustable to 4-6 in settings.",[722,1275,1276],{},"Multiple segments download in parallel — whichever finishes first gets processed first, but writes to the file must be reordered by sequence number to ensure correct concatenation order. FlowPick maintains an internal download queue and ordered write buffer to handle this detail.",[722,1278,1279],{},"Failed segment downloads are automatically retried (default 3 attempts). Since segments are small, the cost of a single retry is negligible — no need to start over from scratch.",[732,1281,1283],{"id":1282},"practical-differences-in-output-format","Practical Differences in Output Format",[722,1285,1286],{},"FlowPick supports two output options:",[722,1288,1289,1291,1292,1295],{},[740,1290,866],{},": remuxed standard MP4 with the moov atom at the file start (",[749,1293,1294],{},"faststart","). Best compatibility, ideal for long-term storage, uploading, and video editing. The trade-off is the remux step, which adds a few dozen extra seconds for large videos.",[722,1297,1298,1301],{},[740,1299,1300],{},"TS",": direct binary concatenation of all segments, zero CPU overhead, nearly instant. But the file is 5-15% larger than MP4 (TS container overhead), and some devices and platforms don't support TS as well as MP4.",[722,1303,1304],{},"If you're downloading to upload to Bilibili or import into CapCut, pick MP4. If you're just staging it temporarily or plan to process further with FFmpeg command line, pick TS to save time.",[732,1306,1308],{"id":1307},"what-it-costs-to-do-this-in-the-browser","What It Costs to Do This in the Browser",[722,1310,1311],{},"Having covered what it can do, let's be clear about what it can't:",[722,1313,1314,1317],{},[740,1315,1316],{},"No re-encoding."," FlowPick only swaps containers, never touches codecs. An H.265 video comes out as H.265 — if an old player doesn't support H.265, it still won't. FlowPick can't help with that.",[722,1319,1320,1323],{},[740,1321,1322],{},"FFmpeg WASM has a memory ceiling."," 4GB of 32-bit WASM address space, minus browser overhead, means the usable amount is less than 4GB. The automatic strategy switch for files over 2GB (TSToMP4Muxer or direct TS output) is specifically to bypass this limit.",[722,1325,1326,1329],{},[740,1327,1328],{},"WASM is slower than native FFmpeg."," WASM isn't native code; there's a performance penalty. A 60-minute 4K video takes FFmpeg WASM about 30 seconds to remux; native FFmpeg might do it in 5 seconds. For everyday use, 30 seconds is acceptable. If you process large volumes daily, native FFmpeg command line is more appropriate.",[722,1331,1332,1335,1336,1338],{},[740,1333,1334],{},"No hardware acceleration."," Native FFmpeg can use NVENC\u002FQSV\u002FVideoToolbox for GPU-accelerated encoding\u002Fdecoding; the WASM version can't. However, since ",[749,1337,1001],{}," doesn't involve encoding or decoding, this limitation has minimal impact on FlowPick's use case.",[722,1340,1341,1344],{},[740,1342,1343],{},"DRM is a hard wall."," This is a hard limit, not a trade-off. Widevine is designed specifically to keep browser extensions from accessing keys.",[1346,1347],"hr",{},[722,1349,1350,1351,1353],{},"Overall, FlowPick's ability to fully handle HLS\u002FDASH downloads in the browser comes down to a combination of key technical pieces: WebAssembly lets FFmpeg run in the browser, ",[749,1352,1001],{}," keeps processing costs down to I\u002FO only, TSToMP4Muxer solves the large-file memory problem, the File System Access API solves large-file disk writes, and the Web Crypto API handles AES-128 decryption. None of it is \"black magic\" — it's all direct application of modern browser standard capabilities.",[1346,1355],{},[732,1357,1359],{"id":1358},"recommended-reading","Recommended Reading",[1149,1361,1362,1371,1380,1389,1398,1407],{},[767,1363,1364,1370],{},[740,1365,1366],{},[944,1367,1369],{"href":1368},"\u002Fblog\u002Fgetting-started-with-hls-m3u8-download","How to Download M3U8\u002FHLS Streams: A Complete Beginner's Guide"," — now that you understand the principles, here's how to actually do it",[767,1372,1373,1379],{},[740,1374,1375],{},[944,1376,1378],{"href":1377},"\u002Fblog\u002Fhow-i-built-flowpick-in-three-months","The Code Is Open Source! How I Built FlowPick in Three Months"," — the story behind how these principles were forced into existence",[767,1381,1382,1388],{},[740,1383,1384],{},[944,1385,1387],{"href":1386},"\u002Fblog\u002Fwhat-is-dash-mpd-streaming","What Is DASH Streaming? MPD File Explained"," — why DASH's audio\u002Fvideo separation design exists",[767,1390,1391,1397],{},[740,1392,1393],{},[944,1394,1396],{"href":1395},"\u002Fblog\u002Fhow-to-download-bilibili-videos","How to Download Bilibili Videos to Your Computer"," — Bilibili's DASH is a classic real-world case of the techniques above",[767,1399,1400,1406],{},[740,1401,1402],{},[944,1403,1405],{"href":1404},"\u002Fblog\u002Fbatch-download-images-from-any-website","How to Batch Download Images from Any Website"," — same extension, images can be batch-handled too",[767,1408,1409,1415],{},[740,1410,1411],{},[944,1412,1414],{"href":1413},"\u002Fblog\u002Fflowpick-vs-video-downloadhelper","FlowPick vs. Video DownloadHelper"," — why \"no CoApp needed\" is a big deal",{"title":967,"searchDepth":1417,"depth":1417,"links":1418},2,[1419,1420,1421,1422,1427,1428,1429,1430,1431,1432,1433],{"id":734,"depth":1417,"text":735},{"id":790,"depth":1417,"text":791},{"id":825,"depth":1417,"text":826},{"id":931,"depth":1417,"text":932,"children":1423},[1424,1426],{"id":939,"depth":1425,"text":940},3,{"id":1126,"depth":1425,"text":1127},{"id":1177,"depth":1417,"text":1178},{"id":1197,"depth":1417,"text":1198},{"id":1226,"depth":1417,"text":1227},{"id":1269,"depth":1417,"text":1270},{"id":1282,"depth":1417,"text":1283},{"id":1307,"depth":1417,"text":1308},{"id":1358,"depth":1417,"text":1359},"tips","2026-07-02","No server, no FFmpeg install, no transcoding — a full technical breakdown of how FlowPick handles HLS\u002FDASH segment merging entirely within a browser tab.","md","\u002Fscreenshots\u002Fformat-conversion.png",{},true,"\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser",13,{"title":716,"description":1436},"blog\u002Fhow-flowpick-merges-video-segments-in-browser",[1446,1447,1448,1449,1450,1451],"technical","ffmpeg","wasm","hls","dash","architecture","Z21DDGZa2VWrqgjPtRtZ7rzmXXpXQ7qns4bAPyUqcAE",{"id":1454,"title":1378,"author":717,"body":1455,"category":2580,"date":1435,"description":2581,"extension":1437,"image":2423,"meta":2582,"navigation":1440,"path":1377,"readingTime":2583,"seo":2584,"stem":2585,"tags":2586,"__hash__":2590},"blog\u002Fblog\u002Fhow-i-built-flowpick-in-three-months.md",{"type":719,"value":1456,"toc":2566},[1457,1466,1469,1471,1475,1478,1481,1484,1487,1490,1494,1497,1503,1509,1512,1515,1519,1522,1533,1536,1539,1549,1719,1722,1725,1728,1739,1742,1746,1749,1757,1763,1766,1777,1780,1791,1796,1807,1810,1815,1818,1821,1826,1829,1832,1836,1842,1849,1855,1858,1861,1864,1890,1904,1908,1911,1914,1920,1923,1941,2082,2088,2091,2097,2101,2107,2110,2113,2119,2132,2135,2138,2142,2145,2152,2155,2323,2326,2329,2332,2336,2339,2342,2345,2412,2415,2418,2424,2428,2431,2437,2443,2449,2453,2456,2461,2481,2486,2497,2520,2522,2524,2562],[722,1458,1459,1460,1465],{},"Today I pushed the code to ",[944,1461,1464],{"href":1462,"rel":1463},"https:\u002F\u002Fgithub.com\u002FFlowPick\u002Fflowpick",[948],"GitHub",". The repo is public now.",[722,1467,1468],{},"I'm not writing this to celebrate. I mainly want to document the pitfalls I fell into over those three months — so I can look them up later when I forget, and as a reference for anyone with the same needs. This will get into some technical details, but I'll keep it conversational. This is a story, not a spec document.",[1346,1470],{},[732,1472,1474],{"id":1473},"the-trigger-an-experience-that-bugged-me-for-months","The Trigger: An Experience That Bugged Me for Months",[722,1476,1477],{},"Late last year, I was following a series of technical courses on an online learning platform. The platform didn't allow downloads, but I had a habit of watching offline — no signal on the subway commute, unreliable coffee shop WiFi, and on the way home I'd want to replay a section without scrubbing through a progress bar.",[722,1479,1480],{},"I tried Video DownloadHelper. For HLS streams, it required installing CoApp. Fine, I installed CoApp. Then it threw an error on an encrypted course. I searched for a few online tools — the ones that worked were either speed-capped, crashed on large files, or routed through a server proxy, which I wasn't comfortable with.",[722,1482,1483],{},"youtube-dl could handle some of it, but yt-dlp didn't recognize that platform at all — either there was no extractor for it, or the cookies wouldn't pass through.",[722,1485,1486],{},"This dragged on for months. Then one weekend, with nothing better to do, a thought popped into my head: isn't this just \"parse M3U8, download segments, stitch files together\"? Could this be done inside a browser?",[722,1488,1489],{},"I opened MDN and started browsing. The Web Crypto API, File System Access API, and WebAssembly — all three had stabilized in recent years, and compatibility on Chrome was no longer an issue. So I started coding.",[732,1491,1493],{"id":1492},"week-1-figuring-out-can-we-even-detect-it","Week 1: Figuring Out \"Can We Even Detect It?\"",[722,1495,1496],{},"The first problem any tool has to solve: how do you know what videos are on the page?",[722,1498,1499,1500,1502],{},"Browser extensions have an API called ",[749,1501,800],{}," that can listen to all network requests made by a tab — and I mean all requests, at the network layer, not the DOM layer. The moment a player sends out an M3U8 or MPD request, the extension can intercept it.",[959,1504,1507],{"className":1505,"code":1506,"language":964},[962],"Extension → webRequest.onBeforeRequest\n         → Filter by Content-Type\n         → Hit application\u002Fx-mpegurl → Record as HLS\n         → Hit application\u002Fdash+xml  → Record as DASH\n         → Hit video\u002F*               → Record as direct video\n",[749,1508,1506],{"__ignoreMap":967},[722,1510,1511],{},"This part went faster than I expected. Two days later, I had a prototype that could list all video streams on the current page in the extension popup. I opened Bilibili to test — boom, it listed video streams, audio streams, ad streams, everything, complete with URLs and file sizes.",[722,1513,1514],{},"One detail tripped me up for a while: M3U8 comes in two flavors — Master Playlist and Media Playlist. The Master is a directory listing all available quality levels, each pointing to a Media Playlist; the Media Playlist is the actual segment manifest. I initially displayed both, and users saw a mess of entries with no idea which to pick. I later changed it: when a Master Playlist is detected, automatically parse the quality options inside and only show a human-readable resolution list, hiding the raw URL hierarchy.",[732,1516,1518],{"id":1517},"week-3-the-first-real-hard-problem-where-do-the-files-go","Week 3: The First Real Hard Problem — Where Do the Files Go?",[722,1520,1521],{},"Downloading hundreds of segments — that's an async concurrency problem, not a hard one. The hard part was where to put all that data.",[722,1523,1524,1525,1528,1529,1532],{},"Browser JavaScript has no default permission to access the filesystem. The simplest approach is to accumulate all the data into a Blob, then trigger a download — ",[749,1526,1527],{},"URL.createObjectURL(blob)"," paired with a hidden ",[749,1530,1531],{},"\u003Ca>"," tag.",[722,1534,1535],{},"I got the prototype running with this method first. 500MB video, no problem. 800MB, still fine. 1GB, Chrome started to stutter — the reason was obvious. Blob mode loads the entire file into memory. A 1GB video means the browser is holding 1GB of ArrayBuffer, plus the page and extension overhead. Memory pressure was severe. Above 1.5GB, it just froze.",[722,1537,1538],{},"I set a hard limit on Blob mode: refuse to process anything over 1.5GB, warn at 800MB. That wasn't solving the problem — it was just drawing a line around it.",[722,1540,1541,1542,1544,1545,1548],{},"The real solution was the ",[740,1543,1187],{},". Supported since Chrome 86, it lets JavaScript obtain a file handle and then continuously write through a ",[749,1546,1547],{},"WritableStream"," — just like a native program writing to a file. Only the current chunk being written is in memory at any time, completely independent of total file size.",[959,1550,1554],{"className":1551,"code":1552,"language":1553,"meta":967,"style":967},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const writable = await handle.createWritable()\n\u002F\u002F Configured with a 16MB buffer\nconst stream = new WritableStream({\n  async write(chunk) { await writable.write(chunk) },\n  async close() { await writable.close() }\n}, { highWaterMark: 16 * 1024 * 1024 })\n","typescript",[749,1555,1556,1589,1595,1616,1658,1686],{"__ignoreMap":967},[1557,1558,1561,1565,1569,1573,1577,1580,1582,1586],"span",{"class":1559,"line":1560},"line",1,[1557,1562,1564],{"class":1563},"spNyl","const",[1557,1566,1568],{"class":1567},"sTEyZ"," writable ",[1557,1570,1572],{"class":1571},"sMK4o","=",[1557,1574,1576],{"class":1575},"s7zQu"," await",[1557,1578,1579],{"class":1567}," handle",[1557,1581,1141],{"class":1571},[1557,1583,1585],{"class":1584},"s2Zo4","createWritable",[1557,1587,1588],{"class":1567},"()\n",[1557,1590,1591],{"class":1559,"line":1417},[1557,1592,1594],{"class":1593},"sHwdD","\u002F\u002F Configured with a 16MB buffer\n",[1557,1596,1597,1599,1602,1604,1607,1610,1613],{"class":1559,"line":1425},[1557,1598,1564],{"class":1563},[1557,1600,1601],{"class":1567}," stream ",[1557,1603,1572],{"class":1571},[1557,1605,1606],{"class":1571}," new",[1557,1608,1609],{"class":1584}," WritableStream",[1557,1611,1612],{"class":1567},"(",[1557,1614,1615],{"class":1571},"{\n",[1557,1617,1619,1622,1626,1628,1632,1635,1638,1640,1643,1645,1648,1650,1652,1655],{"class":1559,"line":1618},4,[1557,1620,1621],{"class":1563},"  async",[1557,1623,1625],{"class":1624},"swJcz"," write",[1557,1627,1612],{"class":1571},[1557,1629,1631],{"class":1630},"sHdIc","chunk",[1557,1633,1634],{"class":1571},")",[1557,1636,1637],{"class":1571}," {",[1557,1639,1576],{"class":1575},[1557,1641,1642],{"class":1567}," writable",[1557,1644,1141],{"class":1571},[1557,1646,1647],{"class":1584},"write",[1557,1649,1612],{"class":1624},[1557,1651,1631],{"class":1567},[1557,1653,1654],{"class":1624},") ",[1557,1656,1657],{"class":1571},"},\n",[1557,1659,1661,1663,1666,1669,1671,1673,1675,1677,1680,1683],{"class":1559,"line":1660},5,[1557,1662,1621],{"class":1563},[1557,1664,1665],{"class":1624}," close",[1557,1667,1668],{"class":1571},"()",[1557,1670,1637],{"class":1571},[1557,1672,1576],{"class":1575},[1557,1674,1642],{"class":1567},[1557,1676,1141],{"class":1571},[1557,1678,1679],{"class":1584},"close",[1557,1681,1682],{"class":1624},"() ",[1557,1684,1685],{"class":1571},"}\n",[1557,1687,1689,1692,1694,1697,1699,1703,1706,1709,1711,1713,1716],{"class":1559,"line":1688},6,[1557,1690,1691],{"class":1571},"},",[1557,1693,1637],{"class":1571},[1557,1695,1696],{"class":1624}," highWaterMark",[1557,1698,848],{"class":1571},[1557,1700,1702],{"class":1701},"sbssI"," 16",[1557,1704,1705],{"class":1571}," *",[1557,1707,1708],{"class":1701}," 1024",[1557,1710,1705],{"class":1571},[1557,1712,1708],{"class":1701},[1557,1714,1715],{"class":1571}," }",[1557,1717,1718],{"class":1567},")\n",[722,1720,1721],{},"With this, downloading a 5GB video and a 500MB video use essentially the same amount of memory.",[722,1723,1724],{},"But Firefox doesn't support the FSA API, and neither does Safari. I needed a fallback.",[722,1726,1727],{},"I ended up with a three-tier write strategy:",[764,1729,1730,1733,1736],{},[767,1731,1732],{},"Prefer FSA API (Chrome\u002FEdge, no size limit)",[767,1734,1735],{},"Fall back to StreamSaver.js (relies on Service Worker, better compatibility)",[767,1737,1738],{},"Last resort Blob mode (all browsers, but 1.5GB limit)",[722,1740,1741],{},"Which tier gets used is determined at runtime. The user never needs to know what's happening behind the scenes.",[732,1743,1745],{"id":1744},"week-5-the-real-boss-fight-merging-segments-into-mp4","Week 5: The Real Boss Fight — Merging Segments into MP4",[722,1747,1748],{},"I could download segments. I could write files. But HLS segments are in TS format (MPEG Transport Stream), and most people want MP4.",[722,1750,1751,1752,838,1754,1756],{},"If you just binary-concatenate 180 ",[749,1753,751],{},[749,1755,751],{}," file. VLC can play it, but Windows' native player won't recognize it, CapCut can't import it, and uploading to Bilibili throws a format error. It technically works, but the user experience is terrible.",[722,1758,1759,1760,1762],{},"TS to MP4 is fundamentally ",[740,1761,924],{},": extract the video and audio streams from TS and repackage them into MP4's container structure. No re-encoding involved, so it should be fast in theory, with zero quality loss.",[722,1764,1765],{},"The question was: in a browser, who's going to do this?",[722,1767,1768,1769,1772,1773,1776],{},"The answer had existed for about a year already — ",[740,1770,1771],{},"FFmpeg had been compiled to WebAssembly",". Several open-source projects had done this, the most well-known being ",[749,1774,1775],{},"@ffmpeg\u002Fffmpeg",". WASM can run near-native-speed binary code inside the browser's JavaScript environment, and FFmpeg natively supports remuxing.",[722,1778,1779],{},"In theory, all I needed to do was:",[764,1781,1782,1785,1788],{},[767,1783,1784],{},"Load FFmpeg WASM",[767,1786,1787],{},"Write segments into FFmpeg's virtual filesystem (it simulates a filesystem in memory)",[767,1789,1790],{},"Run this command:",[959,1792,1794],{"className":1793,"code":963,"language":964},[962],[749,1795,963],{"__ignoreMap":967},[722,1797,1798,1800,1801,1803,1804,1806],{},[749,1799,1001],{}," is stream copy — no re-encoding, just a container swap. ",[749,1802,1023],{}," converts AAC audio from the ADTS packaging used by TS to the ASC packaging used by MP4; without it, some players have audio issues. ",[749,1805,1013],{}," moves the MP4 index (moov atom) to the file start for streaming playback.",[722,1808,1809],{},"Sounded great. In practice, I hit several walls:",[722,1811,1812],{},[740,1813,1814],{},"Wall 1: FFmpeg WASM takes 10-30 seconds to load the first time.",[722,1816,1817],{},"The WASM core file is about 30MB, needing network download and compilation. The user clicks download, then waits 20 seconds with nothing happening — terrible UX.",[722,1819,1820],{},"Fix: trigger FFmpeg loading the moment the download starts, running it in parallel with segment downloads. By the time all segments are downloaded, FFmpeg is usually initialized, making the wait imperceptible. Plus, with browser caching, subsequent loads only take 2-5 seconds.",[722,1822,1823],{},[740,1824,1825],{},"Wall 2: The WASM virtual filesystem lives in memory.",[722,1827,1828],{},"FFmpeg processes files by first writing them into its virtual filesystem. A one-hour 1080P video — 600-800MB of segments — all written into WASM memory. Add FFmpeg's own runtime memory (200-400MB), and you easily exceed WASM's 4GB address space limit. Crash.",[722,1830,1831],{},"This wasn't an occasional issue with large files — it was inevitable. I needed a large-file solution that didn't depend on FFmpeg WASM.",[732,1833,1835],{"id":1834},"week-7-writing-tstomp4muxer","Week 7: Writing TSToMP4Muxer",[722,1837,1838,1839,1141],{},"I spent nearly two weeks writing a pure-JavaScript TS → MP4 streaming remuxer called ",[749,1840,1841],{},"TSToMP4Muxer",[722,1843,1844,1845,1848],{},"The principle: instead of waiting for all segments to download before processing, ",[740,1846,1847],{},"download, parse, and write to disk simultaneously",". Each TS data packet is fixed at 188 bytes. I read packet by packet, extract PES (Packetized Elementary Stream) data, convert it to MP4 Box format in real time, and continuously write to disk via the Streams API.",[959,1850,1853],{"className":1851,"code":1852,"language":964},[962],"Segment 0 downloaded\n  → Parse 188-byte TS packets\n  → Extract video\u002Faudio PES\n  → Write to MP4 mdat Box\nSegment 1 downloaded\n  → Keep appending\n...\nSegment N written\n  → Write moov Box (index)\n  → Close file\n",[749,1854,1852],{"__ignoreMap":967},[722,1856,1857],{},"This way, only the currently-processing segment is in memory at any time — completely decoupled from total file size. And since it's pure JS, there's no need to load the FFmpeg WASM file, saving that 30MB load overhead and initialization time.",[722,1859,1860],{},"The trade-off is narrower coverage — it only does single TS → MP4. It can't handle DASH audio\u002Fvideo merging (which requires synchronized dual-input processing) or various edge-case formats. But for the most common scenario — HLS downloads — TSToMP4Muxer is the better solution.",[722,1862,1863],{},"The current logic: regular HLS prefers TSToMP4Muxer for streaming processing — fast and memory-efficient. DASH or dual-stream merging scenarios use FFmpeg WASM. HLS files over 2GB also go through TSToMP4Muxer to avoid WASM memory overflow.",[722,1865,1866,1867,1870,1871,1870,1874,1870,1877,1870,1880,1870,1883,1870,1886,1889],{},"Writing this took a long time. The MP4 Box structure has a spec — a 600+ page spec. I only read the few dozen pages relevant to remuxing. ",[749,1868,1869],{},"ftyp",", ",[749,1872,1873],{},"moov",[749,1875,1876],{},"mvhd",[749,1878,1879],{},"trak",[749,1881,1882],{},"mdia",[749,1884,1885],{},"minf",[749,1887,1888],{},"stbl"," — if the nesting hierarchy of these Boxes is wrong, players can't read the file. The debugging loop was: write it, open in VLC, see what error it throws, check the spec, fix, repeat. Probably cycled through this twenty-plus times.",[722,1891,1892,1893,1896,1897,1900,1901,1903],{},"The most maddening moment: the video played, but the timeline was scrambled — dragging the seek bar jumped to random positions, not where you expected. The cause: the timestamp calculations in the ",[749,1894,1895],{},"stts"," (Sample to Time) and ",[749,1898,1899],{},"ctts"," (Composition Time Offset) Boxes weren't handling B-frame DTS\u002FPTS offsets. H.264 has B-frames, where decode order (DTS) and display order (PTS) differ. The ",[749,1902,1899],{}," Box has to separately record each frame's display time offset. Found the problem, fixed it — took two days.",[732,1905,1907],{"id":1906},"week-9-aes-128-encryption-easier-than-i-thought","Week 9: AES-128 Encryption — Easier Than I Thought",[722,1909,1910],{},"I'd always assumed encrypted video handling would be the hardest part. Turns out it was way easier than TSToMP4Muxer.",[722,1912,1913],{},"HLS encryption is clearly specified in the M3U8:",[959,1915,1918],{"className":1916,"code":1917,"language":964},[962],"#EXT-X-KEY:METHOD=AES-128,URI=\"https:\u002F\u002Fapi.example.com\u002Fkey?token=abc\",IV=0x00000000000000000000000000000001\n",[749,1919,1917],{"__ignoreMap":967},[722,1921,1922],{},"The logic is:",[764,1924,1925,1932,1935],{},[767,1926,1927,1928,1931],{},"Spot ",[749,1929,1930],{},"#EXT-X-KEY",", parse out the Key URI and IV",[767,1933,1934],{},"Request the Key URI to get the 16-byte key (the request comes from the browser extension, carrying the user's current cookies — so even keys that require login are accessible)",[767,1936,1937,1938,1940],{},"For each downloaded segment, use the ",[749,1939,1213],{}," for AES-128-CBC decryption",[959,1942,1944],{"className":1551,"code":1943,"language":1553,"meta":967,"style":967},"const keyBytes = await crypto.subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['decrypt'])\nconst decrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivBuffer }, keyBytes, encryptedData)\n",[749,1945,1946,2026],{"__ignoreMap":967},[1557,1947,1948,1950,1953,1955,1957,1960,1962,1965,1967,1970,1972,1975,1979,1981,1984,1987,1989,1991,1994,1996,1999,2002,2004,2007,2011,2013,2016,2018,2021,2023],{"class":1559,"line":1560},[1557,1949,1564],{"class":1563},[1557,1951,1952],{"class":1567}," keyBytes ",[1557,1954,1572],{"class":1571},[1557,1956,1576],{"class":1575},[1557,1958,1959],{"class":1567}," crypto",[1557,1961,1141],{"class":1571},[1557,1963,1964],{"class":1567},"subtle",[1557,1966,1141],{"class":1571},[1557,1968,1969],{"class":1584},"importKey",[1557,1971,1612],{"class":1567},[1557,1973,1974],{"class":1571},"'",[1557,1976,1978],{"class":1977},"sfazB","raw",[1557,1980,1974],{"class":1571},[1557,1982,1983],{"class":1571},",",[1557,1985,1986],{"class":1567}," key",[1557,1988,1983],{"class":1571},[1557,1990,1637],{"class":1571},[1557,1992,1993],{"class":1624}," name",[1557,1995,848],{"class":1571},[1557,1997,1998],{"class":1571}," '",[1557,2000,2001],{"class":1977},"AES-CBC",[1557,2003,1974],{"class":1571},[1557,2005,2006],{"class":1571}," },",[1557,2008,2010],{"class":2009},"sfNiH"," false",[1557,2012,1983],{"class":1571},[1557,2014,2015],{"class":1567}," [",[1557,2017,1974],{"class":1571},[1557,2019,2020],{"class":1977},"decrypt",[1557,2022,1974],{"class":1571},[1557,2024,2025],{"class":1567},"])\n",[1557,2027,2028,2030,2033,2035,2037,2039,2041,2043,2045,2047,2049,2052,2054,2056,2058,2060,2062,2064,2067,2069,2072,2074,2077,2079],{"class":1559,"line":1417},[1557,2029,1564],{"class":1563},[1557,2031,2032],{"class":1567}," decrypted ",[1557,2034,1572],{"class":1571},[1557,2036,1576],{"class":1575},[1557,2038,1959],{"class":1567},[1557,2040,1141],{"class":1571},[1557,2042,1964],{"class":1567},[1557,2044,1141],{"class":1571},[1557,2046,2020],{"class":1584},[1557,2048,1612],{"class":1567},[1557,2050,2051],{"class":1571},"{",[1557,2053,1993],{"class":1624},[1557,2055,848],{"class":1571},[1557,2057,1998],{"class":1571},[1557,2059,2001],{"class":1977},[1557,2061,1974],{"class":1571},[1557,2063,1983],{"class":1571},[1557,2065,2066],{"class":1624}," iv",[1557,2068,848],{"class":1571},[1557,2070,2071],{"class":1567}," ivBuffer ",[1557,2073,1691],{"class":1571},[1557,2075,2076],{"class":1567}," keyBytes",[1557,2078,1983],{"class":1571},[1557,2080,2081],{"class":1567}," encryptedData)\n",[722,2083,2084,2087],{},[749,2085,2086],{},"crypto.subtle"," is the browser's built-in Web Crypto API, with hardware acceleration under the hood. Decryption speed isn't a bottleneck — a few milliseconds per segment.",[722,2089,2090],{},"What actually took time was IV handling. The HLS spec says: if the M3U8 doesn't explicitly declare an IV, each segment's IV should use the segment's sequence number (MSB format, 16 bytes). I initially didn't handle this rule and used the first IV for all segments. Encrypted courses decrypted with correct video but garbled audio. Looked up RFC 8216, found the rule, added it — fixed.",[722,2092,2093,2096],{},[740,2094,2095],{},"What can't be handled:"," Widevine, PlayReady, FairPlay. These are DRM. The keys live in the OS or hardware; JavaScript simply can't access them. When FlowPick encounters this type of content, it can only honestly tell the user \"not supported\" — there's no way around it. Netflix and Disney+ are in this category and were never in scope.",[732,2098,2100],{"id":2099},"week-11-dash-audiovideo-merging","Week 11: DASH Audio\u002FVideo Merging",[722,2102,2103,2104,1141],{},"DASH is the other major streaming format, used by YouTube and regular Bilibili uploads. The core difference from HLS: ",[740,2105,2106],{},"audio and video are two completely independent streams",[722,2108,2109],{},"This isn't a design flaw — it's intentional. It lets platforms store one video track and pair it with multiple language audio tracks, slashing storage costs by several times. But for a download tool, it means you have to download two streams simultaneously and then merge them.",[722,2111,2112],{},"Merging two streams uses FFmpeg WASM:",[959,2114,2117],{"className":2115,"code":2116,"language":964},[962],"ffmpeg\n  -f concat -safe 0 -i video_list.txt\n  -f concat -safe 0 -i audio_list.txt\n  -c:v copy -c:a copy\n  -movflags +faststart\n  output.mp4\n",[749,2118,2116],{"__ignoreMap":967},[722,2120,2121,2122,2125,2126,2128,2129,2131],{},"One detail here: DASH segments are in FMP4 (Fragmented MP4) format, not HLS's TS. The special thing about FMP4 is that each segment file can play independently, but to parse them correctly, you must first have an ",[740,2123,2124],{},"init segment"," — an initialization fragment containing codec parameters (",[749,2127,1869],{}," + ",[749,2130,1873],{}," Box). Without the init segment, FFmpeg doesn't know the video's resolution, frame rate, or codec, and can't process the subsequent data segments.",[722,2133,2134],{},"So the DASH processing flow has one extra step compared to HLS: download the init segment first, then all data segments, and the init segment must come first during merging. I initially forgot to handle the init segment, and FFmpeg's merged MP4 opened with a \"file corrupted\" error. Took two days to realize the problem was here.",[722,2136,2137],{},"The DASH parallel download strategy also needed adjustment — video segments and audio segments should download simultaneously, not video first then audio. This nearly halved the total download time. The Worker Pool design supports this: two task streams in parallel, sharing one thread pool — whichever worker is free grabs the next segment from whichever stream.",[732,2139,2141],{"id":2140},"concurrent-downloads-worker-pool-not-pre-splitting","Concurrent Downloads: Worker Pool, Not Pre-Splitting",[722,2143,2144],{},"I want to call out the concurrent download design separately because one implementation detail made me rewrite it twice.",[722,2146,2147,2148,2151],{},"The most intuitive approach: split 200 segments into 4 equal batches, 4 Workers each handle one batch. The problem with this is ",[740,2149,2150],{},"load imbalance"," — HLS segments aren't equal-sized. Segments at the start and end of a video tend to be smaller, middle ones larger. Pre-splitting can leave one Worker stuck on a batch of large segments while the other three are already idle.",[722,2153,2154],{},"FlowPick uses a shared counter approach:",[959,2156,2158],{"className":1551,"code":2157,"language":1553,"meta":967,"style":967},"let nextIndex = 0\nconst worker = async () => {\n  while (nextIndex \u003C segments.length) {\n    const index = nextIndex++  \u002F\u002F Atomically grab the next task\n    await downloadAndProcess(segments[index])\n  }\n}\n\u002F\u002F Launch N workers, all sharing nextIndex\nawait Promise.all(Array.from({ length: concurrency }, () => worker()))\n",[749,2159,2160,2173,2194,2219,2239,2260,2265,2270,2276],{"__ignoreMap":967},[1557,2161,2162,2165,2168,2170],{"class":1559,"line":1560},[1557,2163,2164],{"class":1563},"let",[1557,2166,2167],{"class":1567}," nextIndex ",[1557,2169,1572],{"class":1571},[1557,2171,2172],{"class":1701}," 0\n",[1557,2174,2175,2177,2180,2182,2185,2188,2191],{"class":1559,"line":1417},[1557,2176,1564],{"class":1563},[1557,2178,2179],{"class":1567}," worker ",[1557,2181,1572],{"class":1571},[1557,2183,2184],{"class":1563}," async",[1557,2186,2187],{"class":1571}," ()",[1557,2189,2190],{"class":1563}," =>",[1557,2192,2193],{"class":1571}," {\n",[1557,2195,2196,2199,2201,2204,2207,2210,2212,2215,2217],{"class":1559,"line":1425},[1557,2197,2198],{"class":1575},"  while",[1557,2200,747],{"class":1624},[1557,2202,2203],{"class":1567},"nextIndex",[1557,2205,2206],{"class":1571}," \u003C",[1557,2208,2209],{"class":1567}," segments",[1557,2211,1141],{"class":1571},[1557,2213,2214],{"class":1567},"length",[1557,2216,1654],{"class":1624},[1557,2218,1615],{"class":1571},[1557,2220,2221,2224,2227,2230,2233,2236],{"class":1559,"line":1618},[1557,2222,2223],{"class":1563},"    const",[1557,2225,2226],{"class":1567}," index",[1557,2228,2229],{"class":1571}," =",[1557,2231,2232],{"class":1567}," nextIndex",[1557,2234,2235],{"class":1571},"++",[1557,2237,2238],{"class":1593},"  \u002F\u002F Atomically grab the next task\n",[1557,2240,2241,2244,2247,2249,2252,2255,2258],{"class":1559,"line":1660},[1557,2242,2243],{"class":1575},"    await",[1557,2245,2246],{"class":1584}," downloadAndProcess",[1557,2248,1612],{"class":1624},[1557,2250,2251],{"class":1567},"segments",[1557,2253,2254],{"class":1624},"[",[1557,2256,2257],{"class":1567},"index",[1557,2259,2025],{"class":1624},[1557,2261,2262],{"class":1559,"line":1688},[1557,2263,2264],{"class":1571},"  }\n",[1557,2266,2268],{"class":1559,"line":2267},7,[1557,2269,1685],{"class":1571},[1557,2271,2273],{"class":1559,"line":2272},8,[1557,2274,2275],{"class":1593},"\u002F\u002F Launch N workers, all sharing nextIndex\n",[1557,2277,2279,2282,2286,2288,2291,2294,2296,2299,2301,2303,2306,2308,2311,2313,2315,2317,2320],{"class":1559,"line":2278},9,[1557,2280,2281],{"class":1575},"await",[1557,2283,2285],{"class":2284},"sBMFI"," Promise",[1557,2287,1141],{"class":1571},[1557,2289,2290],{"class":1584},"all",[1557,2292,2293],{"class":1567},"(Array",[1557,2295,1141],{"class":1571},[1557,2297,2298],{"class":1584},"from",[1557,2300,1612],{"class":1567},[1557,2302,2051],{"class":1571},[1557,2304,2305],{"class":1624}," length",[1557,2307,848],{"class":1571},[1557,2309,2310],{"class":1567}," concurrency ",[1557,2312,1691],{"class":1571},[1557,2314,2187],{"class":1571},[1557,2316,2190],{"class":1563},[1557,2318,2319],{"class":1584}," worker",[1557,2321,2322],{"class":1567},"()))\n",[722,2324,2325],{},"Each Worker grabs the next segment the moment it finishes the current one — no waiting for other Workers, regardless of segment size. This naturally achieves dynamic load balancing, with total completion time approaching the theoretical optimum.",[722,2327,2328],{},"The default concurrency is 2, adjustable up to 8. Why not default to 8? Because for some CDNs, high concurrency triggers rate limiting or 429 errors, making things slower. 4-6 concurrent threads is usually the sweet spot; diminishing returns kick in around 8.",[722,2330,2331],{},"The retry mechanism uses exponential backoff: wait 400ms after a failure, then 800ms, then 1600ms. Give up after 3 total failures. Most network hiccups recover on the first or second retry. 4xx errors (403, 404) are not retried — the server explicitly refused, retrying is pointless.",[732,2333,2335],{"id":2334},"memory-management-preventing-browser-crashes","Memory Management: Preventing Browser Crashes",[722,2337,2338],{},"In the final stretch, memory management was what I obsessed over the most.",[722,2340,2341],{},"The problem: you don't know how big the downloaded file will be. A user picks 1080P for a livestream replay — could be 2GB, could be 20GB. You won't know until all segments are downloaded.",[722,2343,2344],{},"I built a sampling-based estimate before downloading:",[959,2346,2348],{"className":1551,"code":2347,"language":1553,"meta":967,"style":967},"\u002F\u002F Download max(1, total × 10%) segments first, take the average size, multiply by total\nconst sampleCount = Math.min(5, Math.max(1, Math.floor(totalCount * 0.1)))\n",[749,2349,2350,2355],{"__ignoreMap":967},[1557,2351,2352],{"class":1559,"line":1560},[1557,2353,2354],{"class":1593},"\u002F\u002F Download max(1, total × 10%) segments first, take the average size, multiply by total\n",[1557,2356,2357,2359,2362,2364,2367,2369,2372,2374,2377,2379,2381,2383,2386,2388,2391,2393,2395,2397,2400,2403,2406,2409],{"class":1559,"line":1417},[1557,2358,1564],{"class":1563},[1557,2360,2361],{"class":1567}," sampleCount ",[1557,2363,1572],{"class":1571},[1557,2365,2366],{"class":1567}," Math",[1557,2368,1141],{"class":1571},[1557,2370,2371],{"class":1584},"min",[1557,2373,1612],{"class":1567},[1557,2375,2376],{"class":1701},"5",[1557,2378,1983],{"class":1571},[1557,2380,2366],{"class":1567},[1557,2382,1141],{"class":1571},[1557,2384,2385],{"class":1584},"max",[1557,2387,1612],{"class":1567},[1557,2389,2390],{"class":1701},"1",[1557,2392,1983],{"class":1571},[1557,2394,2366],{"class":1567},[1557,2396,1141],{"class":1571},[1557,2398,2399],{"class":1584},"floor",[1557,2401,2402],{"class":1567},"(totalCount ",[1557,2404,2405],{"class":1571},"*",[1557,2407,2408],{"class":1701}," 0.1",[1557,2410,2411],{"class":1567},")))\n",[722,2413,2414],{},"Based on the estimate, pick a write strategy: under 800MB, anything goes. 800MB-1.5GB, must use streaming writes. Over 1.5GB, refuse Blob mode.",[722,2416,2417],{},"But estimates aren't enough — runtime monitoring is also needed. During download, continuously track total allocated ArrayBuffer size. Over 600MB triggers a warning. Over 1200MB pauses new segment downloads, prioritizes writing already-downloaded segments to disk and freeing memory, then resumes. This way, even if the estimate is way off, there's a safety net.",[722,2419,2420],{},[816,2421],{"alt":2422,"src":2423},"Download engine architecture: three core modules — segment download, segment processing, file writing","\u002Fscreenshots\u002Fdownload-engine-architecture.png",[732,2425,2427],{"id":2426},"features-i-built-and-then-deleted","Features I Built and Then Deleted",[722,2429,2430],{},"Over three months, a few features got halfway built and then scrapped:",[722,2432,2433,2436],{},[740,2434,2435],{},"Transcoding",": I briefly wanted to support \"convert to 720P on download\" to reduce file size. Then I realized transcoding in a browser is way too slow — FFmpeg WASM can handle remuxing fine, but re-encoding means decoding every video frame and re-encoding, CPU usage spikes to max, and a 1GB video could take 30 minutes. Unacceptable user experience. FlowPick ultimately only does remuxing (container swap, no codec change). If you want transcoding, use command-line FFmpeg.",[722,2438,2439,2442],{},[740,2440,2441],{},"Real-time ETA on the progress bar",": my first version used instantaneous speed, and the progress bar jittered wildly — CDN segment sizes aren't uniform, so instantaneous speed could bounce between 0.5MB\u002Fs and 50MB\u002Fs. Switched to a 5-second sliding window average, much smoother.",[722,2444,2445,2448],{},[740,2446,2447],{},"Multi-file batch downloading",": I originally wasn't going to do this, then saw people asking if they could download all episodes at once. Added a simple queue — each download task runs sequentially. No parallel support (two simultaneous video downloads would max out both bandwidth and FFmpeg memory).",[732,2450,2452],{"id":2451},"the-result-after-three-months","The Result After Three Months",[722,2454,2455],{},"About 12,000 lines of TypeScript — extension + online tool + landing page.",[722,2457,2458],{},[740,2459,2460],{},"What it can do:",[1149,2462,2463,2466,2469,2472,2475,2478],{},[767,2464,2465],{},"HLS\u002FM3U8 downloads with automatic AES-128 decryption",[767,2467,2468],{},"DASH\u002FMPD downloads with automatic audio\u002Fvideo merging",[767,2470,2471],{},"Batch direct video sniffing",[767,2473,2474],{},"Batch image detection and downloading",[767,2476,2477],{},"Audio resource detection",[767,2479,2480],{},"MP4 output, no file size limit (Chrome\u002FEdge)",[722,2482,2483],{},[740,2484,2485],{},"What it can't do (not laziness — genuinely impossible):",[1149,2487,2488,2491,2494],{},[767,2489,2490],{},"Widevine\u002FPlayReady\u002FFairPlay DRM — hardware-level protection, JS can't access keys",[767,2492,2493],{},"Video transcoding — browser compute power isn't enough, experience would be terrible",[767,2495,2496],{},"Format conversion to MKV\u002FAVI — FFmpeg WASM integration currently only covers the MP4\u002FTS path",[722,2498,2499,2500,2503,2504,1870,2509,1870,2514,2519],{},"The code is open source, repo at ",[944,2501,1464],{"href":1462,"rel":2502},[948],". The extension is published on three browsers: ",[944,2505,2508],{"href":2506,"rel":2507},"https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fflowpick-media-sniffer-do\u002Fmfinfkkabangbkanlfhhbokgfekjklea",[948],"Chrome Web Store",[944,2510,2513],{"href":2511,"rel":2512},"https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fflowpick-media-sniffer-\u002Fegbfhgcifljmeaomlbhggplohemjombb",[948],"Edge Add-ons",[944,2515,2518],{"href":2516,"rel":2517},"https:\u002F\u002Faddons.mozilla.org\u002Ffirefox\u002Faddon\u002Fflowpick\u002F",[948],"Firefox Add-ons",". If you find bugs or have ideas, feel free to open an Issue or send a PR.",[1346,2521],{},[732,2523,1359],{"id":1358},[1149,2525,2526,2534,2541,2548,2555],{},[767,2527,2528,2533],{},[740,2529,2530],{},[944,2531,2532],{"href":1441},"How FlowPick Merges Video Segments into MP4 in the Browser"," — this one tells the story, that one explains the principles. Read them together.",[767,2535,2536,2540],{},[740,2537,2538],{},[944,2539,1369],{"href":1368}," — after all those pitfalls, the user only sees five steps.",[767,2542,2543,2547],{},[740,2544,2545],{},[944,2546,1387],{"href":1386}," — the DASH format mentioned repeatedly throughout this article, explained in detail.",[767,2549,2550,2554],{},[740,2551,2552],{},[944,2553,1396],{"href":1395}," — DASH audio\u002Fvideo merging in a classic real-world case.",[767,2556,2557,2561],{},[740,2558,2559],{},[944,2560,1414],{"href":1413}," — a head-to-head comparison with the oldest extension in this space.",[2563,2564,2565],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":967,"searchDepth":1417,"depth":1417,"links":2567},[2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579],{"id":1473,"depth":1417,"text":1474},{"id":1492,"depth":1417,"text":1493},{"id":1517,"depth":1417,"text":1518},{"id":1744,"depth":1417,"text":1745},{"id":1834,"depth":1417,"text":1835},{"id":1906,"depth":1417,"text":1907},{"id":2099,"depth":1417,"text":2100},{"id":2140,"depth":1417,"text":2141},{"id":2334,"depth":1417,"text":2335},{"id":2426,"depth":1417,"text":2427},{"id":2451,"depth":1417,"text":2452},{"id":1358,"depth":1417,"text":1359},"stories","A solo developer who got fed up and built his own tool — the full journey from discovering the problem to open-source release: webRequest sniffing, FFmpeg WASM pitfalls, building TSToMP4Muxer from scratch, and the three-tier write strategy.",{},16,{"title":1378,"description":2581},"blog\u002Fhow-i-built-flowpick-in-three-months",[2587,2588,1448,1451,2589],"open source","solo dev","dev log","8U_ykhCHttGxgrWBQIlM84BHrYlrh75E078uBuZ_5Tg",{"id":2592,"title":2593,"author":717,"body":2594,"category":3197,"date":1435,"description":3198,"extension":1437,"image":2863,"meta":3199,"navigation":1440,"path":3200,"readingTime":3201,"seo":3202,"stem":3203,"tags":3204,"__hash__":3211},"blog\u002Fblog\u002Fhow-to-sniff-and-download-audio-from-any-website.md","How to Sniff and Download Audio from Any Website: Podcasts, BGM, Audiobooks",{"type":719,"value":2595,"toc":3170},[2596,2599,2602,2606,2613,2616,2627,2633,2639,2642,2646,2652,2677,2682,2693,2697,2821,2825,2829,2832,2851,2855,2858,2864,2868,2879,2882,2886,2892,2902,2910,2916,2920,2924,2927,2932,2949,2952,2958,2962,2965,2968,2972,2975,2978,2982,2993,3000,3004,3007,3011,3014,3020,3026,3030,3034,3037,3041,3044,3047,3051,3054,3078,3082,3085,3119,3123,3126,3128,3130,3167],[722,2597,2598],{},"There's a background track on some website that sounds great, but there's no download button anywhere. A podcast platform you want to save a few episodes from, but the official app wants you to pay. An audiobook site where every chapter is a separate audio file, and clicking through them one by one is driving you nuts.",[722,2600,2601],{},"This article is about one thing: how to pull all that hidden audio out.",[732,2603,2605],{"id":2604},"why-web-audio-is-hard-to-download-directly","Why Web Audio Is Hard to Download Directly",[722,2607,2608,2609,2612],{},"Browsers have no built-in \"batch save\" for audio files. Right-click an ",[749,2610,2611],{},"\u003Caudio>"," element and sometimes you'll see \"Save as,\" but most modern sites either disable the context menu entirely or the \"save\" option only grabs a temporary, token-laden URL that's already 403 by the time you open it.",[722,2614,2615],{},"Then there are the trickier cases:",[722,2617,2618,2621,2622,2626],{},[740,2619,2620],{},"HLS audio streams"," — podcast platforms and course sites often deliver audio via HLS, chopped into hundreds of segments just like HLS video. You get the M3U8 URL, it plays fine, but \"Save as\" only saves that few-KB playlist file. Same principle applies — see ",[740,2623,2624],{},[944,2625,1369],{"href":1368}," for details.",[722,2628,2629,2632],{},[740,2630,2631],{},"Signed direct links"," — some platforms generate time-limited URLs for each request. Copy the link into IDM or your browser's download bar, and you'll likely get a 403.",[722,2634,2635,2638],{},[740,2636,2637],{},"Login-gated content"," — member-exclusive content. A bare link gets you nowhere; you need the current session's cookies.",[722,2640,2641],{},"FlowPick, as a browser extension, intercepts at the network request layer — the URLs it sees are exactly what your browser sees, with cookies, with tokens, valid in real time.",[732,2643,2645],{"id":2644},"what-audio-flowpick-can-detect","What Audio FlowPick Can Detect",[722,2647,2648,2649,2651],{},"By monitoring all network requests via the ",[749,2650,800],{}," API, FlowPick can detect:",[1149,2653,2654,2660,2665,2671],{},[767,2655,2656,2659],{},[740,2657,2658],{},"Direct audio files",": MP3, M4A, AAC, WAV, FLAC, OGG, WEBA, etc. — visible as soon as the page loads",[767,2661,2662,2664],{},[740,2663,2620],{},": audio-only tracks in M3U8 playlists, requiring a play click to trigger the request",[767,2666,2667,2670],{},[740,2668,2669],{},"DASH audio tracks",": independent audio AdaptationSets in DASH streams, typically M4A\u002FAAC format",[767,2672,2673,2676],{},[740,2674,2675],{},"Page background music",": BGM that auto-requests on page load — a page refresh is enough to detect it",[722,2678,2679],{},[740,2680,2681],{},"What it can't detect:",[1149,2683,2684,2687,2690],{},[767,2685,2686],{},"Audio synthesized client-side via the Web Audio API (no network request)",[767,2688,2689],{},"Widevine\u002FPlayReady DRM-encrypted audio (Spotify and similar platforms)",[767,2691,2692],{},"Base64 audio data embedded inline in HTML",[732,2694,2696],{"id":2695},"supported-audio-formats","Supported Audio Formats",[850,2698,2699,2712],{},[853,2700,2701],{},[856,2702,2703,2706,2709],{},[859,2704,2705],{},"Format",[859,2707,2708],{},"Extension",[859,2710,2711],{},"Common Use",[868,2713,2714,2727,2743,2756,2769,2782,2795,2808],{},[856,2715,2716,2719,2724],{},[873,2717,2718],{},"MP3",[873,2720,2721],{},[749,2722,2723],{},".mp3",[873,2725,2726],{},"Podcasts, music, audiobooks",[856,2728,2729,2732,2740],{},[873,2730,2731],{},"M4A\u002FAAC",[873,2733,2734,1870,2737],{},[749,2735,2736],{},".m4a",[749,2738,2739],{},".aac",[873,2741,2742],{},"Apple devices, YouTube audio tracks",[856,2744,2745,2748,2753],{},[873,2746,2747],{},"WAV",[873,2749,2750],{},[749,2751,2752],{},".wav",[873,2754,2755],{},"Game sound effects, pro recordings",[856,2757,2758,2761,2766],{},[873,2759,2760],{},"FLAC",[873,2762,2763],{},[749,2764,2765],{},".flac",[873,2767,2768],{},"Lossless music",[856,2770,2771,2774,2779],{},[873,2772,2773],{},"OGG",[873,2775,2776],{},[749,2777,2778],{},".ogg",[873,2780,2781],{},"Game BGM, some podcasts",[856,2783,2784,2787,2792],{},[873,2785,2786],{},"WebM Audio",[873,2788,2789],{},[749,2790,2791],{},".weba",[873,2793,2794],{},"Chrome-based browsers",[856,2796,2797,2800,2805],{},[873,2798,2799],{},"HLS audio stream",[873,2801,2802],{},[749,2803,2804],{},".m3u8",[873,2806,2807],{},"Podcast platforms, course sites",[856,2809,2810,2813,2818],{},[873,2811,2812],{},"DASH audio track",[873,2814,2815],{},[749,2816,2817],{},".mpd",[873,2819,2820],{},"Bilibili audio-only, YouTube Music",[732,2822,2824],{"id":2823},"step-by-step-sniff-and-download-audio","Step-by-Step: Sniff and Download Audio",[937,2826,2828],{"id":2827},"step-1-refresh-the-page-or-trigger-playback","Step 1: Refresh the Page or Trigger Playback",[722,2830,2831],{},"The key action depends on the audio type:",[1149,2833,2834,2840,2846],{},[767,2835,2836,2839],{},[740,2837,2838],{},"Page BGM \u002F auto-play audio",": just refresh the page. The BGM load request is captured by FlowPick immediately — no manual action needed",[767,2841,2842,2845],{},[740,2843,2844],{},"Podcasts\u002Faudiobooks that require clicking play",": click the play button, wait for playback to start (usually 2-3 seconds), then open FlowPick",[767,2847,2848,2850],{},[740,2849,2620],{},": FlowPick only sees the M3U8 request after you hit play. No playback, no stream visible",[937,2852,2854],{"id":2853},"step-2-open-the-flowpick-popup","Step 2: Open the FlowPick Popup",[722,2856,2857],{},"Click the FlowPick icon in your browser toolbar. The badge number on the popup shows the total media resources detected on the current page.",[722,2859,2860],{},[816,2861],{"alt":2862,"src":2863},"FlowPick popup showing detected audio resources","\u002Fscreenshots\u002Faudio-detection.png",[937,2865,2867],{"id":2866},"step-3-switch-to-the-audio-tab","Step 3: Switch to the Audio Tab",[722,2869,2870,2871,2874,2875,2878],{},"The top of the popup has media type tabs. Click the ",[740,2872,2873],{},"Audio"," tab (🎵 icon). You'll see a list of all detected audio resources, each showing filename, format, and file size (if the server returned ",[749,2876,2877],{},"Content-Length",").",[722,2880,2881],{},"If the file size shows as \"Unknown\" — that's normal. The server didn't include the size in the response headers. Doesn't affect the download.",[937,2883,2885],{"id":2884},"step-4-select-and-download","Step 4: Select and Download",[722,2887,2888,2891],{},[740,2889,2890],{},"Single file",": click the download icon on the right to download directly.",[722,2893,2894,2897,2898,2901],{},[740,2895,2896],{},"Batch download",": check multiple entries → click ",[740,2899,2900],{},"Download Selected"," at the top. FlowPick queues them sequentially; the popup shows progress.",[722,2903,2904,2905,2909],{},"For HLS audio streams, FlowPick automatically downloads all segments and merges them into a complete M4A or MP3 file — no manual handling needed. The merge pipeline behind this is fully broken down in ",[740,2906,2907],{},[944,2908,2532],{"href":1441}," — audio streams go through the same pipeline.",[722,2911,2912],{},[816,2913],{"alt":2914,"src":2915},"FlowPick audio download progress","\u002Fscreenshots\u002Fdownload-progress.png",[732,2917,2919],{"id":2918},"scenario-specific-workflows","Scenario-Specific Workflows",[937,2921,2923],{"id":2922},"batch-saving-podcasts","Batch Saving Podcasts",[722,2925,2926],{},"Common podcast platform behavior: when the homepage list loads, each episode's audio URL may not have been requested yet — the audio request only fires when you enter the episode page or click play.",[722,2928,2929],{},[740,2930,2931],{},"Workflow rhythm:",[764,2933,2934,2937,2940,2943,2946],{},[767,2935,2936],{},"Open the podcast show page",[767,2938,2939],{},"Click play on each episode one by one (even just 2 seconds is enough)",[767,2941,2942],{},"Don't close the tab — keep clicking play on the next episode",[767,2944,2945],{},"After triggering multiple episodes in sequence, open FlowPick → Audio tab",[767,2947,2948],{},"Select All → Download Selected",[722,2950,2951],{},"This queues up all triggered episodes for download in one batch — no need to wait for each one individually.",[722,2953,2954,2957],{},[740,2955,2956],{},"Note:"," if the platform only loads 20 episodes at a time, scroll to the bottom to load more, then continue triggering playback to detect the later episodes.",[937,2959,2961],{"id":2960},"audiobooks-by-chapter","Audiobooks by Chapter",[722,2963,2964],{},"Audiobook platforms typically split each chapter into a separate audio file. Go to the listening page, play chapter one — FlowPick shows the audio entry. If the platform preloads multiple chapters at once, they'll all appear — select all and batch download.",[722,2966,2967],{},"If the platform only loads the next chapter after the current one finishes: play, download, switch to next chapter, play, download again, repeat. Compared to right-click-saving each one in the browser, FlowPick handles it faster — especially HLS streams where you don't have to manually deal with segments.",[937,2969,2971],{"id":2970},"website-background-music","Website Background Music",[722,2973,2974],{},"Lots of creative sites, game promo pages, and product landing pages have carefully crafted BGM. This type of audio usually auto-requests on page load — no action needed to trigger it.",[722,2976,2977],{},"Open the page (wait for BGM to start) → immediately open FlowPick → it's already in the Audio tab. Usually MP3 or M4A format, ready to download directly.",[937,2979,2981],{"id":2980},"youtube-music-bilibili-audio-only","YouTube Music \u002F Bilibili Audio-Only",[722,2983,2984,2985,2988,2989,1141],{},"YouTube and Bilibili audio-only content uses DASH, with audio and video tracks separated. The FlowPick popup shows both video entries and audio entries — if you only need the audio, switch to the ",[740,2986,2987],{},"Audio tab"," and download the audio track directly. No need to download the entire video and extract audio afterwards. For why DASH is designed this way, see ",[740,2990,2991],{},[944,2992,1387],{"href":1386},[722,2994,2995,2996,1141],{},"Bilibili's DASH audio tracks are typically M4A format, 128kbps or 320kbps (premium). The full Bilibili download workflow is covered in more detail in ",[740,2997,2998],{},[944,2999,1396],{"href":1395},[937,3001,3003],{"id":3002},"course-platform-audio-lectures","Course Platform Audio Lectures",[722,3005,3006],{},"Some course platforms offer standalone audio versions of courses, or the main video's HLS audio track can be downloaded separately. In the FlowPick popup: if you see a video stream (M3U8) and also a separate M3U8 entry in the Audio tab, the platform is transmitting audio and video separately — download the audio track directly. Smaller file, perfect for commute listening.",[732,3008,3010],{"id":3009},"filtering-out-unwanted-audio","Filtering Out Unwanted Audio",[722,3012,3013],{},"Not all audio on a page is what you want — ad audio, UI interaction sounds, and page notification chimes all get detected.",[722,3015,3016,3019],{},[740,3017,3018],{},"Filter by file size",": in FlowPick settings, set a minimum file size (e.g., 500KB) to eliminate the vast majority of short UI sounds and ad audio. Podcast episodes are typically tens of MB; audiobook chapters are at least several MB. This threshold works well.",[722,3021,3022,3025],{},[740,3023,3024],{},"Manually uncheck",": before batch downloading, scan the list. Manually uncheck entries with keywords like \"ad,\" \"jingle,\" or \"notify\" in the filename.",[732,3027,3029],{"id":3028},"common-issues","Common Issues",[937,3031,3033],{"id":3032},"detected-but-download-fails","Detected but download fails",[722,3035,3036],{},"Most likely the URL contains a time-limited token, and too much time passed between opening the popup and actually clicking download — the token expired. Refresh the page, re-trigger playback, re-detect, then download immediately.",[937,3038,3040],{"id":3039},"hls-audio-downloads-as-an-empty-or-few-kb-file","HLS audio downloads as an empty or few-KB file",[722,3042,3043],{},"The M3U8 file itself is only a few KB — it's a playlist, not audio content. When downloading HLS audio, FlowPick should automatically parse and download all segments and merge them. If you get a few-KB file, FlowPick only saved the playlist without triggering the segment download pipeline.",[722,3045,3046],{},"Switch to Chrome or Edge — merge functionality is most stable on these two browsers.",[937,3048,3050],{"id":3049},"file-downloaded-but-wont-play","File downloaded but won't play",[722,3052,3053],{},"A few possibilities:",[1149,3055,3056,3066,3072],{},[767,3057,3058,3061,3062,3065],{},[740,3059,3060],{},"Mislabeled format",": the server sent M4A but the Content-Type header says ",[749,3063,3064],{},"audio\u002Fmpeg",", so it saved as .mp3. Try forcing it open in VLC, or manually rename the extension to .m4a",[767,3067,3068,3071],{},[740,3069,3070],{},"Missing HLS segments",": playback buffering wasn't complete when stopped, some segments didn't finish downloading. Re-download and make sure the FlowPick progress bar reaches 100%",[767,3073,3074,3077],{},[740,3075,3076],{},"DRM encrypted",": Spotify, NetEase Cloud Music VIP tracks, and similar platforms use DRM. What you downloaded is encrypted data — no player can open it. FlowPick can't handle this type of content",[937,3079,3081],{"id":3080},"downloaded-podcast-has-music-but-no-voice","Downloaded podcast has music but no voice",[722,3083,3084],{},"Rare edge case: the platform split background music and vocals into two separate audio tracks (DASH multi-AdaptationSet), and you only downloaded one. Check if there's another entry in the FlowPick audio list. Select both and download, then mix with FFmpeg:",[959,3086,3090],{"className":3087,"code":3088,"language":3089,"meta":967,"style":967},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","ffmpeg -i voice.m4a -i bgm.m4a -filter_complex amix=inputs=2 output.mp3\n","bash",[749,3091,3092],{"__ignoreMap":967},[1557,3093,3094,3096,3099,3102,3104,3107,3110,3113,3116],{"class":1559,"line":1560},[1557,3095,1447],{"class":2284},[1557,3097,3098],{"class":1977}," -i",[1557,3100,3101],{"class":1977}," voice.m4a",[1557,3103,3098],{"class":1977},[1557,3105,3106],{"class":1977}," bgm.m4a",[1557,3108,3109],{"class":1977}," -filter_complex",[1557,3111,3112],{"class":1977}," amix=inputs=",[1557,3114,3115],{"class":1701},"2",[1557,3117,3118],{"class":1977}," output.mp3\n",[732,3120,3122],{"id":3121},"privacy-note","Privacy Note",[722,3124,3125],{},"FlowPick's audio detection and downloading happens entirely locally in the browser. Detected URLs and downloaded files never pass through FlowPick's servers. Your account cookies and session info are only used for your browser's direct requests to the target server — the exact same network path as you opening the page and listening to the audio directly.",[1346,3127],{},[732,3129,1359],{"id":1358},[1149,3131,3132,3139,3146,3153,3160],{},[767,3133,3134,3138],{},[740,3135,3136],{},[944,3137,1369],{"href":1368}," — audio streams and video streams work on the same principle; this one goes deeper",[767,3140,3141,3145],{},[740,3142,3143],{},[944,3144,1387],{"href":1386}," — DASH separates audio and video into separate tracks; standalone audio downloads rely on this mechanism",[767,3147,3148,3152],{},[740,3149,3150],{},[944,3151,1396],{"href":1395}," — Bilibili's DASH audio tracks can also be downloaded separately",[767,3154,3155,3159],{},[740,3156,3157],{},[944,3158,2532],{"href":1441}," — HLS audio stream segment merging uses the same pipeline",[767,3161,3162,3166],{},[740,3163,3164],{},[944,3165,1405],{"href":1404}," — images and audio can both be batch-grabbed; read this to connect the dots",[2563,3168,3169],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":967,"searchDepth":1417,"depth":1417,"links":3171},[3172,3173,3174,3175,3181,3188,3189,3195,3196],{"id":2604,"depth":1417,"text":2605},{"id":2644,"depth":1417,"text":2645},{"id":2695,"depth":1417,"text":2696},{"id":2823,"depth":1417,"text":2824,"children":3176},[3177,3178,3179,3180],{"id":2827,"depth":1425,"text":2828},{"id":2853,"depth":1425,"text":2854},{"id":2866,"depth":1425,"text":2867},{"id":2884,"depth":1425,"text":2885},{"id":2918,"depth":1417,"text":2919,"children":3182},[3183,3184,3185,3186,3187],{"id":2922,"depth":1425,"text":2923},{"id":2960,"depth":1425,"text":2961},{"id":2970,"depth":1425,"text":2971},{"id":2980,"depth":1425,"text":2981},{"id":3002,"depth":1425,"text":3003},{"id":3009,"depth":1417,"text":3010},{"id":3028,"depth":1417,"text":3029,"children":3190},[3191,3192,3193,3194],{"id":3032,"depth":1425,"text":3033},{"id":3039,"depth":1425,"text":3040},{"id":3049,"depth":1425,"text":3050},{"id":3080,"depth":1425,"text":3081},{"id":3121,"depth":1417,"text":3122},{"id":1358,"depth":1417,"text":1359},"tutorials","A complete guide to sniffing and detecting web audio resources with FlowPick, covering direct MP3\u002FM4A links, HLS audio streams, batch podcast downloads, and audiobook grabbing.",{},"\u002Fblog\u002Fhow-to-sniff-and-download-audio-from-any-website",10,{"title":2593,"description":3198},"blog\u002Fhow-to-sniff-and-download-audio-from-any-website",[3205,3206,3207,3208,3209,3210],"audio","podcast","mp3","sniffing","tutorial","audiobook","bix0sRYIdfSJfCavhTqMh-SLZwPO1WHGHG2pZi3UK-E",{"id":3213,"title":1405,"author":717,"body":3214,"category":3197,"date":3743,"description":3744,"extension":1437,"image":3453,"meta":3745,"navigation":1440,"path":1404,"readingTime":3201,"seo":3746,"stem":3747,"tags":3748,"__hash__":3754},"blog\u002Fblog\u002Fbatch-download-images-from-any-website.md",{"type":719,"value":3215,"toc":3720},[3216,3219,3223,3226,3233,3239,3243,3249,3287,3290,3294,3313,3317,3399,3408,3412,3416,3423,3426,3428,3431,3437,3441,3448,3454,3457,3461,3473,3478,3516,3519,3523,3537,3541,3547,3550,3554,3561,3572,3576,3580,3583,3586,3590,3593,3596,3600,3603,3606,3610,3619,3623,3628,3648,3653,3677,3679,3682,3684,3687,3689],[722,3217,3218],{},"When you're doing design research or collecting competitor assets, the worst part is saving images one by one. Eighty product shots on a supplier's site, high-res images in a gallery, or even a portfolio from some big-name studio — right-click, save as, rename, repeat. That's not work. That's manual labor.",[732,3220,3222],{"id":3221},"the-problem-with-right-click-save","The Problem with Right-Click Save",[722,3224,3225],{},"Most people only realize they need batch image downloading when they're already stuck in the loop — right-click, save as, rename, right-click, save as, rename. Browsers have no built-in \"select all images and download\" feature.",[722,3227,3228,3229,3232],{},"Third-party tools exist, but they fall into two camps: lightweight extensions that only scan ",[749,3230,3231],{},"\u003Cimg>"," tags (missing lazy-loaded images and background images), or desktop programs you have to install (which can't carry your browser login session, so images behind authentication return 403).",[722,3234,3235,3236,3238],{},"FlowPick's image downloader runs inside the browser, carrying your current login session. It captures images by monitoring network requests, not just scanning ",[749,3237,3231],{}," tags.",[732,3240,3242],{"id":3241},"what-flowpick-can-detect","What FlowPick Can Detect",[722,3244,3245,3246,3248],{},"Not all images live inside ",[749,3247,3231],{}," tags. FlowPick captures images by monitoring the actual network requests the browser makes, covering these sources:",[1149,3250,3251,3260,3272,3278],{},[767,3252,3253,3259],{},[740,3254,3255,3256,3258],{},"Standard ",[749,3257,3231],{}," elements"," — the most common type",[767,3261,3262,3271],{},[740,3263,3264,3267,3268,3258],{},[749,3265,3266],{},"\u003Cpicture>"," and ",[749,3269,3270],{},"\u003Csource>"," — responsive images with multiple resolutions; FlowPick picks the highest-resolution variant",[767,3273,3274,3277],{},[740,3275,3276],{},"Lazy-loaded images"," — images that load on scroll, captured once they enter the DOM",[767,3279,3280,3286],{},[740,3281,3282,3285],{},[749,3283,3284],{},"\u003Cvideo>"," poster frames"," — video thumbnail posters",[722,3288,3289],{},"The scanner runs against the current state of the DOM. Areas you haven't scrolled to, images that haven't loaded yet — they won't appear in the list. Keep this in mind while using it.",[722,3291,3292],{},[740,3293,2681],{},[1149,3295,3296,3299,3302],{},[767,3297,3298],{},"Canvas-rendered images — no network request, invisible to the extension",[767,3300,3301],{},"Base64 inline images — embedded directly in HTML, not standalone resources",[767,3303,3304,3305,3308,3309,3312],{},"CSS ",[749,3306,3307],{},"background-image"," — ",[740,3310,3311],{},"may"," or may not be detected, depending on how the site implements it",[732,3314,3316],{"id":3315},"supported-image-formats","Supported Image Formats",[850,3318,3319,3329],{},[853,3320,3321],{},[856,3322,3323,3325,3327],{},[859,3324,2705],{},[859,3326,2708],{},[859,3328,2711],{},[868,3330,3331,3347,3360,3373,3386],{},[856,3332,3333,3336,3344],{},[873,3334,3335],{},"JPEG",[873,3337,3338,1870,3341],{},[749,3339,3340],{},".jpg",[749,3342,3343],{},".jpeg",[873,3345,3346],{},"Photos, product images",[856,3348,3349,3352,3357],{},[873,3350,3351],{},"PNG",[873,3353,3354],{},[749,3355,3356],{},".png",[873,3358,3359],{},"Screenshots, graphics with transparency",[856,3361,3362,3365,3370],{},[873,3363,3364],{},"GIF",[873,3366,3367],{},[749,3368,3369],{},".gif",[873,3371,3372],{},"Animations, icons",[856,3374,3375,3378,3383],{},[873,3376,3377],{},"WebP",[873,3379,3380],{},[749,3381,3382],{},".webp",[873,3384,3385],{},"Modern web format, usually smaller than JPEG",[856,3387,3388,3391,3396],{},[873,3389,3390],{},"SVG",[873,3392,3393],{},[749,3394,3395],{},".svg",[873,3397,3398],{},"Icons, logos, vector illustrations",[722,3400,3401,3402,3404,3405,1141],{},"A note on ",[740,3403,3377],{},": all major browsers support it, but some older desktop software (pre-2021 Photoshop, certain Windows apps) can't open it directly. If you need JPEG compatibility, FlowPick lets you filter to only download ",[749,3406,3407],{},".jpg\u002F.jpeg",[732,3409,3411],{"id":3410},"step-by-step","Step-by-Step",[937,3413,3415],{"id":3414},"step-1-open-the-page-and-scroll-through-it-first","Step 1: Open the Page and Scroll Through It First",[722,3417,3418,3419,3422],{},"Open the page with your target images. If it's a lazy-loaded gallery (most modern image-heavy sites are), ",[740,3420,3421],{},"slowly scroll from top to bottom first"," to let images actually load into the DOM before opening FlowPick.",[722,3424,3425],{},"A lot of people skip this step, then wonder why the extension only shows a handful of images. The top half of the page loaded, but images below the fold aren't in the DOM yet — FlowPick can't capture what hasn't loaded.",[937,3427,2854],{"id":2853},[722,3429,3430],{},"Click the FlowPick icon in your browser toolbar. The badge number shows the total media resources detected on the current page.",[722,3432,3433],{},[816,3434],{"alt":3435,"src":3436},"FlowPick toolbar icon showing detected media count","\u002Fscreenshots\u002Fextension-popup-detection.png",[937,3438,3440],{"id":3439},"step-3-switch-to-the-images-tab","Step 3: Switch to the Images Tab",[722,3442,3443,3444,3447],{},"In the popup, click the ",[740,3445,3446],{},"Images"," tab (📷) to see a thumbnail grid of detected images.",[722,3449,3450],{},[816,3451],{"alt":3452,"src":3453},"FlowPick image downloader thumbnail grid","\u002Fscreenshots\u002Fimage-downloader.png",[722,3455,3456],{},"The count might be higher than you expected — most pages mix in UI elements, icons, and ad images alongside content images. That's where filtering comes in.",[937,3458,3460],{"id":3459},"step-4-filter-what-you-want","Step 4: Filter What You Want",[722,3462,3463,3466,3467,3469,3470,3472],{},[740,3464,3465],{},"Filter by format",": check only the formats you need. If you only want JPEG, just check ",[749,3468,3407],{},". Skip ",[749,3471,3369],{}," if you don't need animations.",[722,3474,3475,3477],{},[740,3476,3018],{},": setting a minimum KB threshold is the most effective filter. Anything under 5 KB is almost certainly an icon or decorative element, not the content image you're after.",[850,3479,3480,3490],{},[853,3481,3482],{},[856,3483,3484,3487],{},[859,3485,3486],{},"Scenario",[859,3488,3489],{},"Suggested Minimum File Size",[868,3491,3492,3500,3508],{},[856,3493,3494,3497],{},[873,3495,3496],{},"Filter out icons and decorations",[873,3498,3499],{},"10 KB",[856,3501,3502,3505],{},[873,3503,3504],{},"Content galleries",[873,3506,3507],{},"50 KB",[856,3509,3510,3513],{},[873,3511,3512],{},"High-res images only",[873,3514,3515],{},"100 KB+",[722,3517,3518],{},"The thumbnail grid updates after filtering. Give it a quick scan to make sure nothing slipped through — unchecking an image now is way faster than deleting it after download.",[937,3520,3522],{"id":3521},"step-5-select-target-images","Step 5: Select Target Images",[1149,3524,3525,3531,3534],{},[767,3526,3527,3530],{},[740,3528,3529],{},"Select All"," to grab everything after filtering",[767,3532,3533],{},"Click individual thumbnails to toggle selection",[767,3535,3536],{},"Shift + click to select a range",[937,3538,3540],{"id":3539},"step-6-download","Step 6: Download",[722,3542,3543,3544,3546],{},"Click ",[740,3545,2900],{},". FlowPick downloads the selected images concurrently, saving each one to your browser's default download folder with the original filename.",[722,3548,3549],{},"The popup shows progress. Concurrent download means 50 images don't take 50 times as long as one.",[732,3551,3553],{"id":3552},"where-downloaded-files-go","Where Downloaded Files Go",[722,3555,3556,3557,3560],{},"All images are saved to your browser's default download folder (usually your system ",[749,3558,3559],{},"Downloads"," directory), each keeping the filename from the original URL. No ZIP packaging.",[722,3562,3563,3564,3567,3568,3571],{},"If the downloaded images have generic names like ",[749,3565,3566],{},"image.jpg"," or ",[749,3569,3570],{},"photo001.jpg",", that's because the site's image URLs never had meaningful filenames — not a FlowPick issue.",[732,3573,3575],{"id":3574},"real-world-scenarios","Real-World Scenarios",[937,3577,3579],{"id":3578},"product-image-comparison-research","Product Image Comparison Research",[722,3581,3582],{},"You're comparing products across multiple suppliers. Each product page has 5-8 angle shots, lazy-loaded in a gallery.",[722,3584,3585],{},"How to do it: open the product page, click each thumbnail to load the full-size images, then use FlowPick with a minimum file size filter (say, 150 KB) to isolate the large product shots.",[937,3587,3589],{"id":3588},"photography-portfolio-archiving","Photography Portfolio Archiving",[722,3591,3592],{},"Portfolio sites often use full-screen scrolling layouts with heavy lazy-loading — images only load when that section scrolls into the viewport.",[722,3594,3595],{},"How to do it: browse through the entire page at a normal pace, then open FlowPick and set a 100 KB minimum. You'll get clean high-res photos without thumbnails mixed in.",[937,3597,3599],{"id":3598},"course-slides-and-technical-documentation","Course Slides and Technical Documentation",[722,3601,3602],{},"Technical docs and online courses often have tons of diagrams and process screenshots with no standalone download button.",[722,3604,3605],{},"How to do it: scroll through the entire doc page, use FlowPick to filter out UI elements by file size, and batch-grab all the diagrams.",[937,3607,3609],{"id":3608},"bilibili-posts-and-comments","Bilibili Posts and Comments",[722,3611,3612,3613,3618],{},"Images in creator posts and comment sections are all detectable by FlowPick. Scroll through the page first to let images load, then open FlowPick to scan. This pairs well with ",[740,3614,3615],{},[944,3616,3617],{"href":1395},"Bilibili video downloading"," — videos in the video tab, images in the image tab, all from the same popup.",[732,3620,3622],{"id":3621},"which-sites-work-well-and-which-dont","Which Sites Work Well (and Which Don't)",[722,3624,3625],{},[740,3626,3627],{},"Works well on:",[1149,3629,3630,3636,3642],{},[767,3631,3632,3635],{},[740,3633,3634],{},"Pixiv \u002F ArtStation",": gallery-style sites where images go through standard network requests. Scroll through, then scan — works great.",[767,3637,3638,3641],{},[740,3639,3640],{},"Notion public pages",": shared Notion pages with embedded images can be grabbed in one pass.",[767,3643,3644,3647],{},[740,3645,3646],{},"Bilibili posts",": creator posts and comment images, batch-captured after scrolling to load.",[722,3649,3650],{},[740,3651,3652],{},"Not great on:",[1149,3654,3655,3661,3667],{},[767,3656,3657,3660],{},[740,3658,3659],{},"Google Image search results",": images in search results are hosted on third-party domains. CORS restrictions may block downloads. Better to jump to the original page and download from there.",[767,3662,3663,3666],{},[740,3664,3665],{},"Canvas-rendered galleries",": a few sites render images via WebGL\u002FCanvas. In this case, images don't go through network requests, so FlowPick can't detect them.",[767,3668,3669,3672,3673,3676],{},[740,3670,3671],{},"Hotlink-protected images",": some sites embed IP-bound signature tokens or ",[749,3674,3675],{},"Referer"," checks in image URLs. FlowPick can detect the URL, but a direct request returns 403. In that case, right-click → open in new tab and save manually.",[732,3678,3122],{"id":3121},[722,3680,3681],{},"FlowPick's image scanning runs entirely within your browser tab. Detected image URLs and downloaded files never leave your device. Downloads are direct requests from your browser to the image hosting server — no FlowPick server is involved.",[1346,3683],{},[722,3685,3686],{},"The same FlowPick extension handles different media types just by switching tabs. Images in the image tab, videos in the video tab, audio in the audio tab. If you're interested in the other two, check out the guides below.",[732,3688,1359],{"id":1358},[1149,3690,3691,3698,3706,3713],{},[767,3692,3693,3697],{},[740,3694,3695],{},[944,3696,1369],{"href":1368}," — images, video, audio — FlowPick uses the same sniffing logic for all of them. This one covers HLS streaming fundamentals and step-by-step instructions.",[767,3699,3700,3705],{},[740,3701,3702],{},[944,3703,3704],{"href":3200},"How to Sniff and Download Audio from Any Website"," — same workflow as image downloading, just switch to the audio tab.",[767,3707,3708,3712],{},[740,3709,3710],{},[944,3711,1396],{"href":1395}," — Bilibili videos and images often need to be saved together. This covers the video side.",[767,3714,3715,3719],{},[740,3716,3717],{},[944,3718,1414],{"href":1413}," — batch image downloading is a feature FlowPick has that Video DownloadHelper doesn't. This article compares the two in detail.",{"title":967,"searchDepth":1417,"depth":1417,"links":3721},[3722,3723,3724,3725,3733,3734,3740,3741,3742],{"id":3221,"depth":1417,"text":3222},{"id":3241,"depth":1417,"text":3242},{"id":3315,"depth":1417,"text":3316},{"id":3410,"depth":1417,"text":3411,"children":3726},[3727,3728,3729,3730,3731,3732],{"id":3414,"depth":1425,"text":3415},{"id":2853,"depth":1425,"text":2854},{"id":3439,"depth":1425,"text":3440},{"id":3459,"depth":1425,"text":3460},{"id":3521,"depth":1425,"text":3522},{"id":3539,"depth":1425,"text":3540},{"id":3552,"depth":1417,"text":3553},{"id":3574,"depth":1417,"text":3575,"children":3735},[3736,3737,3738,3739],{"id":3578,"depth":1425,"text":3579},{"id":3588,"depth":1425,"text":3589},{"id":3598,"depth":1425,"text":3599},{"id":3608,"depth":1425,"text":3609},{"id":3621,"depth":1417,"text":3622},{"id":3121,"depth":1417,"text":3122},{"id":1358,"depth":1417,"text":1359},"2026-07-01","Use FlowPick's built-in image downloader to grab dozens of images from a webpage in one go, with filtering by file size and format — no desktop software required.",{},{"title":1405,"description":3744},"blog\u002Fbatch-download-images-from-any-website",[3749,3750,3751,3752,3753,3209],"images","batch download","jpeg","png","webp","BTwclNIaPPSsMvqhYWyeTfdMAgQjYR8U8_w3uai7fVM",{"id":3756,"title":3757,"author":717,"body":3758,"category":4373,"date":3743,"description":4374,"extension":1437,"image":3436,"meta":4375,"navigation":1440,"path":1413,"readingTime":1442,"seo":4376,"stem":4377,"tags":4378,"__hash__":4382},"blog\u002Fblog\u002Fflowpick-vs-video-downloadhelper.md","FlowPick vs. Video DownloadHelper: Which One Should You Use in 2026?",{"type":719,"value":3759,"toc":4360},[3760,3763,3766,3770,3776,3782,3786,3789,3798,3803,3809,3815,3819,3972,3976,3981,3992,3995,4000,4026,4029,4033,4036,4041,4063,4068,4079,4082,4085,4089,4095,4101,4107,4113,4117,4123,4129,4135,4141,4149,4155,4159,4163,4174,4178,4183,4187,4190,4195,4206,4211,4219,4224,4235,4240,4248,4253,4261,4265,4270,4287,4292,4306,4312,4314,4316],[722,3761,3762],{},"Let's get this out of the way: we built FlowPick. So read this comparison with whatever level of skepticism you think is appropriate. That said, we've done our best to be fair — Video DownloadHelper is a solid extension with 15 years of history, and there are genuinely scenarios where it's the better choice.",[722,3764,3765],{},"Here's the honest breakdown.",[732,3767,3769],{"id":3768},"background","Background",[722,3771,3772,3775],{},[740,3773,3774],{},"Video DownloadHelper"," launched in 2007 and was one of the first browser extensions capable of detecting and downloading streaming video. For years, it was the go-to recommendation on forums and tech blogs. It has over 3 million active users and hundreds of thousands of reviews across Chrome and Firefox combined.",[722,3777,3778,3781],{},[740,3779,3780],{},"FlowPick"," launched in 2025 with a narrower focus: making HLS and DASH downloads work without requiring any extra software beyond the browser extension itself.",[732,3783,3785],{"id":3784},"core-technical-difference","Core Technical Difference",[722,3787,3788],{},"This is what most people actually care about:",[722,3790,3791,3793,3794,3797],{},[740,3792,3774],{}," can detect M3U8 and MPD streams, but to download and merge them, you need to install a separate ",[740,3795,3796],{},"CoApp"," (companion application) — a standalone desktop program. Without CoApp, clicking \"download\" on an HLS stream gives you either a playlist file or a single segment, neither of which is playable.",[722,3799,3800,3802],{},[740,3801,3780],{}," uses WebAssembly-compiled FFmpeg to handle HLS and DASH entirely within the browser — no companion app, no desktop installer, no system-level access required.",[722,3804,3805,3806,3808],{},"This gap matters a lot in practice. If you've ever installed Video DownloadHelper, clicked download on a Twitch VOD or Bilibili video, and ended up with a 4KB ",[749,3807,2804],{}," file — that's the CoApp requirement at work.",[722,3810,3811],{},[816,3812],{"alt":3813,"src":3814},"FlowPick popup directly detecting and offering download options without extra software","\u002Fscreenshots\u002Fflowpic_vs_videodownloadhelper.png",[732,3816,3818],{"id":3817},"feature-comparison-side-by-side","Feature Comparison Side by Side",[850,3820,3821,3832],{},[853,3822,3823],{},[856,3824,3825,3828,3830],{},[859,3826,3827],{},"Feature",[859,3829,3780],{},[859,3831,3774],{},[868,3833,3834,3845,3856,3866,3877,3888,3899,3910,3921,3932,3942,3951,3961],{},[856,3835,3836,3839,3842],{},[873,3837,3838],{},"Price",[873,3840,3841],{},"Free, no tiers",[873,3843,3844],{},"Free (limited) + CoApp €5.99 one-time",[856,3846,3847,3850,3853],{},[873,3848,3849],{},"HLS\u002FM3U8 download",[873,3851,3852],{},"✅ In-browser, no extra tools",[873,3854,3855],{},"✅ Requires CoApp",[856,3857,3858,3861,3863],{},[873,3859,3860],{},"DASH\u002FMPD download",[873,3862,3852],{},[873,3864,3865],{},"⚠️ Requires CoApp",[856,3867,3868,3871,3874],{},[873,3869,3870],{},"AES-128 decryption",[873,3872,3873],{},"✅ Automatic",[873,3875,3876],{},"⚠️ Partial support (CoApp handles some)",[856,3878,3879,3882,3885],{},[873,3880,3881],{},"In-browser video merging",[873,3883,3884],{},"✅ WebAssembly FFmpeg",[873,3886,3887],{},"❌ Delegated to CoApp",[856,3889,3890,3893,3896],{},[873,3891,3892],{},"Batch image download",[873,3894,3895],{},"✅ Built-in",[873,3897,3898],{},"❌ Not available",[856,3900,3901,3904,3907],{},[873,3902,3903],{},"Analytics tracking",[873,3905,3906],{},"❌ None",[873,3908,3909],{},"✅ Free version has it",[856,3911,3912,3915,3918],{},[873,3913,3914],{},"Open source",[873,3916,3917],{},"✅ Full codebase",[873,3919,3920],{},"❌ Partially open source",[856,3922,3923,3926,3929],{},[873,3924,3925],{},"Requires companion app",[873,3927,3928],{},"❌ No",[873,3930,3931],{},"✅ Yes, for HLS\u002FDASH\u002Fmerging",[856,3933,3934,3937,3940],{},[873,3935,3936],{},"Chrome",[873,3938,3939],{},"✅",[873,3941,3939],{},[856,3943,3944,3947,3949],{},[873,3945,3946],{},"Firefox",[873,3948,3939],{},[873,3950,3939],{},[856,3952,3953,3956,3958],{},[873,3954,3955],{},"Edge",[873,3957,3939],{},[873,3959,3960],{},"⚠️ Limited support",[856,3962,3963,3966,3969],{},[873,3964,3965],{},"Format conversion",[873,3967,3968],{},"MP4 \u002F TS output",[873,3970,3971],{},"CoApp supports multiple formats",[732,3973,3975],{"id":3974},"installation-experience","Installation Experience",[722,3977,3978],{},[740,3979,3980],{},"FlowPick:",[764,3982,3983,3986,3989],{},[767,3984,3985],{},"Search \"FlowPick\" on the Chrome Web Store",[767,3987,3988],{},"Click \"Add to Chrome\"",[767,3990,3991],{},"Done",[722,3993,3994],{},"The whole thing takes 30 seconds. No account registration, no system permission prompts, no firewall dialogs.",[722,3996,3997],{},[740,3998,3999],{},"Video DownloadHelper:",[764,4001,4002,4005,4008,4011,4014,4017,4020,4023],{},[767,4003,4004],{},"Install the VDH extension from the store (10 seconds)",[767,4006,4007],{},"Visit paid content, click download — get a .m3u8 file",[767,4009,4010],{},"Search \"Video DownloadHelper CoApp\"",[767,4012,4013],{},"Download the CoApp installer (varies by platform)",[767,4015,4016],{},"Run the installer (may require admin privileges)",[767,4018,4019],{},"Configure the CoApp path in the extension",[767,4021,4022],{},"Restart the browser",[767,4024,4025],{},"Try again",[722,4027,4028],{},"If everything goes smoothly, about 5-10 minutes. But users have reported CoApp breaking after major browser updates, requiring reconfiguration and re-authorization.",[732,4030,4032],{"id":4031},"privacy-and-permissions","Privacy and Permissions",[722,4034,4035],{},"The two extensions request noticeably different permissions:",[722,4037,4038],{},[740,4039,4040],{},"FlowPick requests:",[1149,4042,4043,4048,4054,4060],{},[767,4044,4045,4047],{},[749,4046,800],{}," — monitor network requests (for media detection)",[767,4049,4050,4053],{},[749,4051,4052],{},"storage"," — save settings",[767,4055,4056,4059],{},[749,4057,4058],{},"downloads"," — write downloaded files",[767,4061,4062],{},"Page content access (current tab)",[722,4064,4065],{},[740,4066,4067],{},"Video DownloadHelper requests:",[1149,4069,4070,4073,4076],{},[767,4071,4072],{},"Similar base permissions as above",[767,4074,4075],{},"Plus: native messaging permission to communicate with the local CoApp",[767,4077,4078],{},"CoApp runs at the system level with full filesystem access",[722,4080,4081],{},"FlowPick is fully open source — the code is auditable. VDH's extension is partially open source; CoApp is not.",[722,4083,4084],{},"On data collection: VDH's free version has usage analytics (can be disabled in settings), and CoApp makes external connections for license verification. FlowPick has no telemetry, no license server.",[732,4086,4088],{"id":4087},"where-video-downloadhelper-wins","Where Video DownloadHelper Wins",[722,4090,4091,4094],{},[740,4092,4093],{},"Format conversion options",": with CoApp installed, VDH can convert to AVI, MKV, WebM, MP3, and more. FlowPick currently outputs MP4 and TS — pick MP4 for maximum compatibility, or TS for the fastest possible direct concatenation. If you need MKV or codec-level conversion, FlowPick doesn't do that.",[722,4096,4097,4100],{},[740,4098,4099],{},"Mature ecosystem",": 15 years of bug reports and edge cases means VDH handles a wider range of obscure sites and player implementations. FlowPick is newer.",[722,4102,4103,4106],{},[740,4104,4105],{},"Format negotiation",": VDH's CoApp talks directly to FFmpeg on your system, using whatever codecs you have installed. FlowPick's WebAssembly FFmpeg is more constrained.",[722,4108,4109,4112],{},[740,4110,4111],{},"Firefox power users",": VDH has always had a strong Firefox presence. FlowPick is available on Firefox, but in-browser merging is better optimized for Chrome's File System Access API.",[732,4114,4116],{"id":4115},"where-flowpick-wins","Where FlowPick Wins",[722,4118,4119,4122],{},[740,4120,4121],{},"Zero-friction installation",": one click on the Chrome Web Store. Done. No companion app, no system-level installer, no firewall permissions.",[722,4124,4125,4128],{},[740,4126,4127],{},"HLS\u002FDASH without extra tools",": this is the big one. With VDH's free version, downloading a Bilibili video or Udemy course gives you unplayable files. With FlowPick, you get a playable MP4 immediately — no extra steps.",[722,4130,4131,4134],{},[740,4132,4133],{},"Privacy",": zero analytics tracking, no license server heartbeats, fully auditable extension. VDH's free version has usage analytics, and CoApp makes external connections.",[722,4136,4137,4140],{},[740,4138,4139],{},"Cost",": completely free. VDH's free version limits streaming content; full HLS\u002FDASH functionality requires the CoApp purchase.",[722,4142,4143,4146,4147,1141],{},[740,4144,4145],{},"Batch image downloading",": FlowPick has a built-in image downloader that can detect and download images in bulk across any website. VDH has no equivalent feature. If you frequently need to batch-download images, this is a significant plus — see the full tutorial at ",[944,4148,1405],{"href":1404},[722,4150,4151,4154],{},[740,4152,4153],{},"Large file handling",": FlowPick uses the File System Access API for streaming writes, with theoretically no file size limit. Even 4K feature-length videos won't crash from memory exhaustion.",[732,4156,4158],{"id":4157},"pricing-breakdown","Pricing Breakdown",[722,4160,4161],{},[740,4162,3999],{},[1149,4164,4165,4168,4171],{},[767,4166,4167],{},"Free version: works for simple MP4\u002FWebM downloads. HLS\u002FDASH requires CoApp.",[767,4169,4170],{},"CoApp: €5.99 one-time (single browser), subscription options for multiple browsers\u002Fdevices",[767,4172,4173],{},"Some users report CoApp license breaking after major browser updates",[722,4175,4176],{},[740,4177,3980],{},[1149,4179,4180],{},[767,4181,4182],{},"Permanently free, no pro tier, no limits on download count, thread count, or file size.",[732,4184,4186],{"id":4185},"real-world-test-cases","Real-World Test Cases",[722,4188,4189],{},"We tested both extensions against a set of common use cases. Here's what actually happened (not what the marketing pages say):",[722,4191,4192],{},[740,4193,4194],{},"Bilibili 1080P DASH video",[1149,4196,4197,4200,4203],{},[767,4198,4199],{},"FlowPick: detected, merged, completed as MP4 in about 3 minutes",[767,4201,4202],{},"VDH (free, no CoApp): downloaded a playlist file, unplayable",[767,4204,4205],{},"VDH (with CoApp): succeeded, similar download time",[722,4207,4208],{},[740,4209,4210],{},"Twitch VOD (HLS\u002FM3U8)",[1149,4212,4213,4216],{},[767,4214,4215],{},"FlowPick: detected M3U8, merged, saved as MP4",[767,4217,4218],{},"VDH (with CoApp): also succeeded",[722,4220,4221],{},[740,4222,4223],{},"Udemy course (AES-128 encrypted HLS)",[1149,4225,4226,4229,4232],{},[767,4227,4228],{},"FlowPick: detected, decrypted segments, merged as MP4",[767,4230,4231],{},"VDH (free): decryption failed",[767,4233,4234],{},"VDH (with CoApp): results varied by CoApp version",[722,4236,4237],{},[740,4238,4239],{},"Pinterest image gallery (batch)",[1149,4241,4242,4245],{},[767,4243,4244],{},"FlowPick: detected 47 images, batch-downloaded in 8 seconds",[767,4246,4247],{},"VDH: no batch image feature",[722,4249,4250],{},[740,4251,4252],{},"4K large file video (~3.2 GB)",[1149,4254,4255,4258],{},[767,4256,4257],{},"FlowPick: streaming write via FSA, stable memory throughout, completed",[767,4259,4260],{},"VDH (with CoApp): relies on system FFmpeg, needs sufficient local disk temp space",[732,4262,4264],{"id":4263},"which-one-should-you-install","Which One Should You Install?",[722,4266,4267],{},[740,4268,4269],{},"Install FlowPick if:",[1149,4271,4272,4275,4278,4281,4284],{},[767,4273,4274],{},"You mainly encounter HLS or DASH streams (Bilibili, Twitch, Udemy, course platforms)",[767,4276,4277],{},"You don't want to install desktop software",[767,4279,4280],{},"You care about privacy and open source",[767,4282,4283],{},"You want batch image downloading",[767,4285,4286],{},"You don't want to pay",[722,4288,4289],{},[740,4290,4291],{},"Install Video DownloadHelper if:",[1149,4293,4294,4297,4300,4303],{},[767,4295,4296],{},"You need specific format conversions (MKV, AVI, MP3, etc. — not just MP4 or TS)",[767,4298,4299],{},"You're already using CoApp and it works for you",[767,4301,4302],{},"You primarily use Firefox",[767,4304,4305],{},"You need to handle obscure players that newer tools haven't caught up with yet",[722,4307,4308,4311],{},[740,4309,4310],{},"Install both",": they don't conflict. Some power users run both and use whichever detects better on a given site.",[1346,4313],{},[732,4315,1359],{"id":1358},[1149,4317,4318,4325,4332,4339,4346,4353],{},[767,4319,4320,4324],{},[740,4321,4322],{},[944,4323,2532],{"href":1441}," — the technical implementation behind \"no CoApp needed,\" breaking down the full in-browser merging pipeline",[767,4326,4327,4331],{},[740,4328,4329],{},[944,4330,1378],{"href":1377}," — the CoApp pain point was the direct reason FlowPick was born",[767,4333,4334,4338],{},[740,4335,4336],{},[944,4337,1369],{"href":1368}," — HLS download walkthrough; the CoApp gap is most obvious here",[767,4340,4341,4345],{},[740,4342,4343],{},[944,4344,1387],{"href":1386}," — understanding DASH audio\u002Fvideo separation explains why in-browser merging isn't trivial",[767,4347,4348,4352],{},[740,4349,4350],{},[944,4351,1396],{"href":1395}," — Bilibili is a DASH-heavy platform; the CoApp gap is most visible here",[767,4354,4355,4359],{},[740,4356,4357],{},[944,4358,1405],{"href":1404}," — a feature VDH doesn't have at all, exclusive to FlowPick",{"title":967,"searchDepth":1417,"depth":1417,"links":4361},[4362,4363,4364,4365,4366,4367,4368,4369,4370,4371,4372],{"id":3768,"depth":1417,"text":3769},{"id":3784,"depth":1417,"text":3785},{"id":3817,"depth":1417,"text":3818},{"id":3974,"depth":1417,"text":3975},{"id":4031,"depth":1417,"text":4032},{"id":4087,"depth":1417,"text":4088},{"id":4115,"depth":1417,"text":4116},{"id":4157,"depth":1417,"text":4158},{"id":4185,"depth":1417,"text":4186},{"id":4263,"depth":1417,"text":4264},{"id":1358,"depth":1417,"text":1359},"comparisons","An honest side-by-side comparison of FlowPick and Video DownloadHelper — which browser extension comes out ahead for HLS\u002FDASH downloads, privacy, and ease of use.",{},{"title":3757,"description":4374},"blog\u002Fflowpick-vs-video-downloadhelper",[4379,3774,4380,4381],"comparison","alternative","browser extension","mEUV21acGoOW2rzWE2DgJhXg-2oR6Kch3ORN_LBWhV8",{"id":4384,"title":1369,"author":717,"body":4385,"category":3197,"date":3743,"description":4993,"extension":1437,"image":819,"meta":4994,"navigation":1440,"path":1368,"readingTime":4995,"seo":4996,"stem":4997,"tags":4998,"__hash__":5001},"blog\u002Fblog\u002Fgetting-started-with-hls-m3u8-download.md",{"type":719,"value":4386,"toc":4964},[4387,4393,4396,4400,4406,4419,4422,4428,4438,4442,4449,4455,4458,4462,4465,4494,4497,4501,4504,4530,4533,4555,4558,4562,4566,4569,4574,4587,4592,4595,4600,4603,4608,4613,4616,4622,4627,4633,4637,4640,4645,4649,4657,4660,4673,4677,4680,4738,4744,4748,4751,4757,4763,4766,4777,4782,4786,4790,4793,4796,4800,4803,4810,4814,4821,4825,4829,4832,4835,4839,4842,4845,4849,4851,4874,4878,4881,4885,4888,4892,4895,4898,4902,4913,4916,4918,4920],[722,4388,4389,4390,4392],{},"You're watching a paid course, or a livestream replay you really want to save. You right-click the video — no \"save\" option. You try a download tool — you get a 3KB ",[749,4391,751],{}," file that VLC can't even open.",[722,4394,4395],{},"That's what HLS streaming looks like from the user's perspective. This guide breaks down how it works under the hood and walks you through getting the full video out.",[732,4397,4399],{"id":4398},"what-is-hls-and-why-does-it-exist","What Is HLS and Why Does It Exist?",[722,4401,4402,4405],{},[740,4403,4404],{},"HLS"," — HTTP Live Streaming — was created by Apple in 2009, originally for iPhone streaming. The core idea is simple: instead of sending one giant video file, chop it into small chunks and deliver them one by one over standard HTTP.",[722,4407,4408,4409,4412,4413,4415,4416,4418],{},"The playlist file that ties all the segments together is the ",[740,4410,4411],{},"M3U8 file"," — a plain text file with a ",[749,4414,2804],{}," extension. The ",[749,4417,2804],{}," URL you see in your browser's Network panel is the video's table of contents, not the video itself.",[722,4420,4421],{},"A typical M3U8 looks like this:",[959,4423,4426],{"className":4424,"code":4425,"language":964},[962],"#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:10\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:9.960000,\nsegment_000.ts\n#EXTINF:9.960000,\nsegment_001.ts\n...\n#EXT-X-ENDLIST\n",[749,4427,4425],{"__ignoreMap":967},[722,4429,4430,4431,4434,4435,4437],{},"Each ",[749,4432,4433],{},"#EXTINF"," line defines a segment's duration, followed by that segment's URL. A one-hour video with 10-second segments means 360 ",[749,4436,751],{}," files. The player downloads them in sequence, and to the user it looks like a continuous stream.",[937,4439,4441],{"id":4440},"adaptive-bitrate-abr","Adaptive Bitrate (ABR)",[722,4443,4444,4445,4448],{},"Most HLS streams are actually ",[740,4446,4447],{},"adaptive bitrate streams",". Instead of a single M3U8, there's a master M3U8 listing multiple quality versions:",[959,4450,4453],{"className":4451,"code":4452,"language":964},[962],"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080\n1080p\u002Findex.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720\n720p\u002Findex.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360\n360p\u002Findex.m3u8\n",[749,4454,4452],{"__ignoreMap":967},[722,4456,4457],{},"The player picks the right quality based on your bandwidth and switches seamlessly as conditions change. This is why YouTube doesn't stutter on a bad connection — it's not waiting for a 4GB file to finish; it's just pulling the next 10 seconds.",[937,4459,4461],{"id":4460},"why-sites-prefer-hls-over-direct-mp4-links","Why Sites Prefer HLS Over Direct MP4 Links",[722,4463,4464],{},"Segmented delivery makes piracy significantly harder:",[1149,4466,4467,4473,4479,4485],{},[767,4468,4469,4472],{},[740,4470,4471],{},"Segment URLs often expire"," — a URL valid two hours ago may now return 403",[767,4474,4475,4478],{},[740,4476,4477],{},"AES-128 encryption"," can encrypt each segment individually, with key rotation",[767,4480,4481,4484],{},[740,4482,4483],{},"No single downloadable file"," — there's nothing to \"right-click save as\"",[767,4486,4487,4490,4491,4493],{},[740,4488,4489],{},"CDN distribution"," — the actual ",[749,4492,751],{}," files may be spread across dozens of servers worldwide",[722,4495,4496],{},"For legitimate use cases (archiving, offline learning, accessibility), this is genuinely frustrating. That's why HLS download tools exist.",[732,4498,4500],{"id":4499},"what-happens-without-the-right-tool","What Happens Without the Right Tool",[722,4502,4503],{},"Say you find the M3U8 URL in DevTools and try to download it directly:",[1149,4505,4506,4512,4518,4524],{},[767,4507,4508,4511],{},[740,4509,4510],{},"wget\u002Fcurl the M3U8",": you get the playlist text file, not the video",[767,4513,4514,4517],{},[740,4515,4516],{},"Browser \"Save link as\"",": downloads a few KB of M3U8 text, definitely not playable",[767,4519,4520,4523],{},[740,4521,4522],{},"Generic download tools",": either error out or grab only the first segment",[767,4525,4526,4529],{},[740,4527,4528],{},"youtube-dl\u002Fyt-dlp",": works for supported sites, but requires command line, Python, and constant updates as sites change",[722,4531,4532],{},"Actually downloading HLS requires software that can:",[764,4534,4535,4538,4541,4547,4552],{},[767,4536,4537],{},"Parse the M3U8 format",[767,4539,4540],{},"Resolve relative URLs to absolute URLs",[767,4542,4543,4544,4546],{},"Download hundreds of ",[749,4545,751],{}," segments (ideally in parallel)",[767,4548,4549,4550],{},"Decrypt AES-128 encrypted segments using the key from ",[749,4551,1930],{},[767,4553,4554],{},"Concatenate or remux into a playable container format (MP4 or TS)",[722,4556,4557],{},"That's a pipeline, not just a simple download.",[732,4559,4561],{"id":4560},"downloading-hls-streams-with-flowpick","Downloading HLS Streams with FlowPick",[937,4563,4565],{"id":4564},"method-1-browser-extension-recommended","Method 1: Browser Extension (Recommended)",[722,4567,4568],{},"The extension silently listens for M3U8 requests inside the browser — no need to manually hunt for URLs.",[722,4570,4571],{},[740,4572,4573],{},"Step 1: Install FlowPick",[722,4575,4576,4577,1870,4580,4583,4584,1141],{},"Install from the ",[944,4578,2508],{"href":2506,"rel":4579},[948],[944,4581,2513],{"href":2511,"rel":4582},[948],", or ",[944,4585,2518],{"href":2516,"rel":4586},[948],[722,4588,4589],{},[740,4590,4591],{},"Step 2: Open the Page and Hit Play",[722,4593,4594],{},"M3U8 requests are only made when the player initializes. Just opening the page isn't enough — the video has to start playing.",[722,4596,4597],{},[740,4598,4599],{},"Step 3: Click the FlowPick Icon",[722,4601,4602],{},"The toolbar icon shows a badge with the number of detected streams. Click it to open the popup.",[722,4604,4605],{},[816,4606],{"alt":4607,"src":819},"FlowPick popup showing detected HLS streams",[722,4609,4610],{},[740,4611,4612],{},"Step 4: Choose Your Quality",[722,4614,4615],{},"For adaptive streams, FlowPick reads the master M3U8 and shows all available quality options. Pick the resolution you want.",[722,4617,4618],{},[816,4619],{"alt":4620,"src":4621},"FlowPick quality selection interface","\u002Fscreenshots\u002Fquality-selector.png",[722,4623,4624],{},[740,4625,4626],{},"Step 5: Download and Merge",[722,4628,3543,4629,4632],{},[740,4630,4631],{},"Pick",". FlowPick downloads segments in parallel using your configured thread count (default 2, adjustable in settings), decrypts encrypted segments locally, and merges them into an MP4 or TS file (depending on your settings). Nothing goes through any server — files go directly from the CDN to your disk.",[937,4634,4636],{"id":4635},"download-progress","Download Progress",[722,4638,4639],{},"The bottom of the FlowPick popup shows real-time progress for the current download: segments downloaded \u002F total segments, current speed, estimated time remaining. For long videos with hundreds of segments, the whole process usually takes just a few minutes.",[722,4641,4642],{},[816,4643],{"alt":4644,"src":2915},"FlowPick download progress display",[937,4646,4648],{"id":4647},"method-2-online-tool-no-installation","Method 2: Online Tool (No Installation)",[722,4650,4651,4652,4656],{},"If you already have the M3U8 URL, you can use the ",[944,4653,4655],{"href":4654},"\u002Fm3u8-downloader","M3U8 Online Downloader"," directly — no extension needed.",[722,4658,4659],{},"Paste the URL, pick a quality, click download. Works great for public streams, but some platforms have CORS restrictions (the extension has an edge here since it runs with the page's own credentials).",[4661,4662,4663],"tip",{},[722,4664,4665,4666,4669,4670,1141],{},"How to find the M3U8 URL manually: open DevTools (F12) → Network tab → filter for ",[749,4667,4668],{},"m3u8"," → play the video → look for a request with Content-Type ",[749,4671,4672],{},"application\u002Fx-mpegurl",[732,4674,4676],{"id":4675},"output-format-mp4-or-ts","Output Format: MP4 or TS?",[722,4678,4679],{},"FlowPick supports two output formats, switchable in settings:",[850,4681,4682,4692],{},[853,4683,4684],{},[856,4685,4686,4688,4690],{},[859,4687],{},[859,4689,866],{},[859,4691,1300],{},[868,4693,4694,4705,4716,4727],{},[856,4695,4696,4699,4702],{},[873,4697,4698],{},"Compatibility",[873,4700,4701],{},"Best, supported by virtually all players",[873,4703,4704],{},"Some older players don't support it",[856,4706,4707,4710,4713],{},[873,4708,4709],{},"Processing speed",[873,4711,4712],{},"Requires FFmpeg WASM remuxing, slightly slower",[873,4714,4715],{},"Direct binary concatenation, extremely fast",[856,4717,4718,4721,4724],{},[873,4719,4720],{},"File size",[873,4722,4723],{},"5-15% smaller than TS (less container overhead)",[873,4725,4726],{},"Slightly larger",[856,4728,4729,4732,4735],{},[873,4730,4731],{},"Best for",[873,4733,4734],{},"Long-term storage, uploading, video editing",[873,4736,4737],{},"Speed, or if you plan to process further with FFmpeg",[722,4739,4740,4743],{},[740,4741,4742],{},"For everyday use, go with MP4."," Only consider TS if you plan to do further processing with FFmpeg command line, or if your connection is slow and you want to skip the remux step.",[732,4745,4747],{"id":4746},"encrypted-streams-aes-128","Encrypted Streams: AES-128",[722,4749,4750],{},"A lot of paid content uses encrypted HLS. The M3U8 will have a line like this:",[959,4752,4755],{"className":4753,"code":4754,"language":964},[962],"#EXT-X-KEY:METHOD=AES-128,URI=\"https:\u002F\u002Fapi.example.com\u002Fkey?token=abc123\"\n",[749,4756,4754],{"__ignoreMap":967},[722,4758,4759,4760,4762],{},"This means: each ",[749,4761,751],{}," segment is encrypted with a 128-bit AES key, and the key is fetched from that URI. Without the key, the raw segment bytes are meaningless.",[722,4764,4765],{},"FlowPick handles all of this automatically:",[764,4767,4768,4771,4774],{},[767,4769,4770],{},"Fetches the key from the URI (using the browser's cookies and session — so it works while logged in)",[767,4772,4773],{},"Decrypts each segment client-side using the browser's built-in Web Crypto API",[767,4775,4776],{},"Neither the key nor the segment data is ever sent to any external server",[722,4778,4779,4781],{},[740,4780,1222],{}," Widevine, PlayReady, or FairPlay DRM. These are hardware-level content protection systems where decryption keys are bound to the device and inaccessible to JavaScript. Netflix and Disney+ fall into this category.",[732,4783,4785],{"id":4784},"real-world-use-cases","Real-World Use Cases",[937,4787,4789],{"id":4788},"online-course-platforms-udemy-coursera-etc","Online Course Platforms (Udemy, Coursera, etc.)",[722,4791,4792],{},"Most course platforms use encrypted HLS to prevent students from downloading and then refunding. FlowPick can handle AES-128 encryption — as long as you're logged in and have access.",[722,4794,4795],{},"Workflow: open the course video → let it play for 5 seconds → open FlowPick → pick 720P (plenty clear for course content, smaller file) → Pick. Once downloaded, play it back immediately to verify audio sync.",[937,4797,4799],{"id":4798},"livestream-replay-recording","Livestream Replay Recording",[722,4801,4802],{},"After a livestream ends, there's usually a brief replay window (24-72 hours) before the platform deletes it or locks it behind a paywall. Downloading during this window is the cleanest — the stream is static, and URLs don't expire as rapidly as during a live broadcast.",[722,4804,4805,4806,4809],{},"How to tell: if it's a live broadcast, the M3U8 won't have the ",[749,4807,4808],{},"#EXT-X-ENDLIST"," tag. If that tag is present, the stream has ended and is a static, fully downloadable stream.",[937,4811,4813],{"id":4812},"content-with-embedded-subtitles","Content with Embedded Subtitles",[722,4815,4816,4817,4820],{},"Some platforms' HLS streams include multiple audio tracks, listed in the M3U8 with ",[749,4818,4819],{},"#EXT-X-MEDIA:TYPE=AUDIO",". FlowPick detects and displays available audio tracks for you to choose from.",[732,4822,4824],{"id":4823},"common-troubleshooting","Common Troubleshooting",[937,4826,4828],{"id":4827},"_0-streams-detected","\"0 streams detected\"",[722,4830,4831],{},"Most common cause: the video hasn't started playing yet. M3U8 requests are only made when the player initializes. Hit play, wait a second or two for buffering, then check again.",[722,4833,4834],{},"If the site embeds the player in an iframe (embedded players, course platforms), right-click the video → \"Open frame in new tab\" and use FlowPick on that tab.",[937,4836,4838],{"id":4837},"download-stopped-at-x","\"Download stopped at X%\"",[722,4840,4841],{},"Stream URLs expired. Most platforms' segment URLs are valid for 4-6 hours; live content may only last 30 minutes.",[722,4843,4844],{},"Fix: refresh the page, replay the video to get fresh URLs, then re-detect with FlowPick.",[937,4846,4848],{"id":4847},"downloaded-file-wont-play","\"Downloaded file won't play\"",[722,4850,3053],{},[1149,4852,4853,4862,4868],{},[767,4854,4855,4858,4859,4861],{},[740,4856,4857],{},"Corrupted segment",": a single bad ",[749,4860,751],{}," can break the merge. Try a lower quality version.",[767,4863,4864,4867],{},[740,4865,4866],{},"Non-standard TS format",": FlowPick uses FFmpeg WASM for remuxing, but edge cases exist.",[767,4869,4870,4873],{},[740,4871,4872],{},"Audio\u002Fvideo out of sync",": open in VLC, choose \"Always fix\" (Media → Convert\u002FSave → Profile → Video codec → check \"Fix\u002Fignore corrupted or missing A\u002FV sync\").",[937,4875,4877],{"id":4876},"only-got-a-single-ts-file","\"Only got a single .ts file\"",[722,4879,4880],{},"This can happen on Firefox or when the merge step silently fails. Switch to Chrome or Edge — merge support is better there, as Chrome's File System Access API allows direct large file writes to disk.",[937,4882,4884],{"id":4883},"download-speed-is-very-slow","Download speed is very slow",[722,4886,4887],{},"The default concurrency is 2 threads, which is conservative. In FlowPick settings, bump it to 4-6 — speed usually improves noticeably. Don't go too high — too many concurrent connections can trigger server-side rate limiting.",[732,4889,4891],{"id":4890},"large-file-notes","Large File Notes",[722,4893,4894],{},"FlowPick uses the browser's File System Access API for streaming writes, with theoretically no file size limit. 4K videos, multi-GB long-form content — no problem. The browser won't crash or run out of memory just because the file is large.",[722,4896,4897],{},"Chrome and Edge have the best large file support (full FSA streaming writes). Firefox also works but uses a different write strategy, with slightly less stability for very large files.",[732,4899,4901],{"id":4900},"quick-reference-hls-vs-the-other-format","Quick Reference: HLS vs. the Other Format",[722,4903,4904,4905,4907,4908,4910,4911,1141],{},"If you're seeing ",[749,4906,2817],{}," files instead of ",[749,4909,2804],{},", you're dealing with DASH — a related but different streaming protocol. For a detailed breakdown, see ",[944,4912,1387],{"href":1386},[722,4914,4915],{},"Both can be handled by FlowPick. The workflow is essentially the same; the main difference is that DASH has separate audio and video tracks that need merging, which takes slightly longer.",[1346,4917],{},[732,4919,1359],{"id":1358},[1149,4921,4922,4929,4936,4943,4950,4957],{},[767,4923,4924,4928],{},[740,4925,4926],{},[944,4927,2532],{"href":1441}," — what happens behind the download button; this breaks down the entire technical pipeline",[767,4930,4931,4935],{},[740,4932,4933],{},[944,4934,1387],{"href":1386}," — the other major streaming protocol and its key differences from HLS",[767,4937,4938,4942],{},[740,4939,4940],{},[944,4941,1396],{"href":1395}," — most Bilibili content uses DASH, livestreams use HLS",[767,4944,4945,4949],{},[740,4946,4947],{},[944,4948,3704],{"href":3200}," — HLS audio streams work with the same workflow",[767,4951,4952,4956],{},[740,4953,4954],{},[944,4955,1405],{"href":1404}," — same extension, images can be batch-grabbed too",[767,4958,4959,4963],{},[740,4960,4961],{},[944,4962,1414],{"href":1413}," — a side-by-side comparison with the oldest extension in this space",{"title":967,"searchDepth":1417,"depth":1417,"links":4965},[4966,4970,4971,4976,4977,4978,4983,4990,4991,4992],{"id":4398,"depth":1417,"text":4399,"children":4967},[4968,4969],{"id":4440,"depth":1425,"text":4441},{"id":4460,"depth":1425,"text":4461},{"id":4499,"depth":1417,"text":4500},{"id":4560,"depth":1417,"text":4561,"children":4972},[4973,4974,4975],{"id":4564,"depth":1425,"text":4565},{"id":4635,"depth":1425,"text":4636},{"id":4647,"depth":1425,"text":4648},{"id":4675,"depth":1417,"text":4676},{"id":4746,"depth":1417,"text":4747},{"id":4784,"depth":1417,"text":4785,"children":4979},[4980,4981,4982],{"id":4788,"depth":1425,"text":4789},{"id":4798,"depth":1425,"text":4799},{"id":4812,"depth":1425,"text":4813},{"id":4823,"depth":1417,"text":4824,"children":4984},[4985,4986,4987,4988,4989],{"id":4827,"depth":1425,"text":4828},{"id":4837,"depth":1425,"text":4838},{"id":4847,"depth":1425,"text":4848},{"id":4876,"depth":1425,"text":4877},{"id":4883,"depth":1425,"text":4884},{"id":4890,"depth":1417,"text":4891},{"id":4900,"depth":1417,"text":4901},{"id":1358,"depth":1417,"text":1359},"Learn what M3U8 and HLS streaming are, why they're hard to download, and how to grab any HLS stream step by step using FlowPick.",{},14,{"title":1369,"description":4993},"blog\u002Fgetting-started-with-hls-m3u8-download",[4668,1449,4999,5000,3209],"streaming","download","vrNj393Xt8JzVurKno2jtlGoQC3adRQ2O0xg6Zi8iXw",{"id":5003,"title":5004,"author":717,"body":5005,"category":3197,"date":3743,"description":5630,"extension":1437,"image":5305,"meta":5631,"navigation":1440,"path":1395,"readingTime":5632,"seo":5633,"stem":5634,"tags":5635,"__hash__":5638},"blog\u002Fblog\u002Fhow-to-download-bilibili-videos.md","How to Download Bilibili Videos to Your Computer (2026 Latest Guide)",{"type":719,"value":5006,"toc":5603},[5007,5010,5017,5020,5024,5031,5047,5054,5070,5074,5084,5160,5163,5167,5170,5242,5245,5249,5252,5261,5264,5268,5279,5282,5286,5289,5300,5306,5310,5313,5316,5321,5325,5330,5333,5344,5347,5351,5354,5358,5362,5365,5368,5379,5382,5417,5424,5430,5434,5440,5460,5463,5467,5470,5487,5489,5491,5494,5505,5508,5512,5515,5519,5522,5530,5533,5540,5543,5547,5550,5554,5557,5560,5564,5567,5569,5571,5601],[722,5008,5009],{},"Bilibili is one of the most interesting video platforms on the internet — a blend of YouTube, Reddit, and anime streaming. As of 2025, it has over 340 million monthly active users, with content ranging from hand-drawn animation to university open courses, from game speedruns to 8-hour livestream replays.",[722,5011,5012,5013,5016],{},"The official app lets premium members download for offline viewing, but the format is proprietary ",[749,5014,5015],{},".blv",", playable only in the app, only on the device that downloaded it, and it expires when the membership ends. That's not what most people mean by \"offline.\"",[722,5018,5019],{},"This guide covers how to download Bilibili videos as standard MP4 files that stay on your machine for good.",[732,5021,5023],{"id":5022},"what-format-does-bilibili-actually-use","What Format Does Bilibili Actually Use?",[722,5025,5026,5027,5030],{},"Bilibili migrated from Flash to HTML5 video in 2019, and most content is now delivered via ",[740,5028,5029],{},"DASH (Dynamic Adaptive Streaming over HTTP)",", which means:",[1149,5032,5033,5039,5044],{},[767,5034,5035,5036],{},"Video and audio tracks are transmitted as ",[740,5037,5038],{},"independent streams",[767,5040,5041,5042,2209],{},"Each track is chopped into hundreds of small ",[749,5043,758],{},[767,5045,5046],{},"A central MPD manifest file describes where everything is",[722,5048,5049,5050,5053],{},"If you open DevTools on a Bilibili page and look at the Network panel, you'll typically see a JSON API response containing an MPD URL or a list of segment URLs — not a direct ",[749,5051,5052],{},".mp4"," link. That JSON response is what the player uses to construct the DASH stream.",[722,5055,5056,747,5059,5062,5063,5066,5067,1141],{},[740,5057,5058],{},"Livestreams",[749,5060,5061],{},"live.bilibili.com",") switch to ",[740,5064,5065],{},"HLS\u002FM3U8"," instead of DASH, using an entirely different delivery pipeline. For how M3U8 works, see the ",[944,5068,5069],{"href":1368},"Complete HLS Download Guide",[732,5071,5073],{"id":5072},"anime-vs-creator-uploads-format-differences","Anime vs. Creator Uploads: Format Differences",[722,5075,5076,5077,3267,5080,5083],{},"It's worth noting: Bilibili content isn't monolithic. ",[740,5078,5079],{},"Regular creator uploads",[740,5081,5082],{},"anime\u002Fpremium content"," use different backend distribution logic:",[850,5085,5086,5099],{},[853,5087,5088],{},[856,5089,5090,5093,5096],{},[859,5091,5092],{},"Content Type",[859,5094,5095],{},"Delivery Format",[859,5097,5098],{},"Notes",[868,5100,5101,5113,5125,5136,5148],{},[856,5102,5103,5105,5110],{},[873,5104,5079],{},[873,5106,5107,5108,1634],{},"DASH (",[749,5109,758],{},[873,5111,5112],{},"Most everyday videos",[856,5114,5115,5118,5122],{},[873,5116,5117],{},"Anime episodes",[873,5119,5107,5120,1634],{},[749,5121,758],{},[873,5123,5124],{},"Login required, some require premium",[856,5126,5127,5130,5133],{},[873,5128,5129],{},"Interactive videos",[873,5131,5132],{},"DASH + special branching logic",[873,5134,5135],{},"FlowPick captures the played branch",[856,5137,5138,5140,5145],{},[873,5139,5058],{},[873,5141,5142,5143,1634],{},"HLS (",[749,5144,2804],{},[873,5146,5147],{},"Completely different pipeline",[856,5149,5150,5153,5157],{},[873,5151,5152],{},"Livestream replays",[873,5154,5107,5155,1634],{},[749,5156,758],{},[873,5158,5159],{},"Converts to static stream after broadcast ends",[722,5161,5162],{},"FlowPick handles all of these, but the workflow rhythm differs slightly — the main difference is that anime has stricter CDN signature tokens with shorter validity windows, so you want to complete the download in one go.",[732,5164,5166],{"id":5165},"quality-tier-breakdown","Quality Tier Breakdown",[722,5168,5169],{},"Bilibili's quality tiers can be confusing because some resolutions require login and some require a premium membership:",[850,5171,5172,5182],{},[853,5173,5174],{},[856,5175,5176,5179],{},[859,5177,5178],{},"Resolution",[859,5180,5181],{},"Requirement",[868,5183,5184,5192,5200,5207,5214,5224,5233],{},[856,5185,5186,5189],{},[873,5187,5188],{},"360P",[873,5190,5191],{},"No account needed",[856,5193,5194,5197],{},[873,5195,5196],{},"480P",[873,5198,5199],{},"Free account (logged in)",[856,5201,5202,5205],{},[873,5203,5204],{},"720P",[873,5206,5199],{},[856,5208,5209,5212],{},[873,5210,5211],{},"1080P",[873,5213,5199],{},[856,5215,5216,5219],{},[873,5217,5218],{},"1080P+ \u002F 60fps",[873,5220,5221],{},[740,5222,5223],{},"Premium membership",[856,5225,5226,5229],{},[873,5227,5228],{},"4K \u002F HDR",[873,5230,5231],{},[740,5232,5223],{},[856,5234,5235,5238],{},[873,5236,5237],{},"Dolby Vision",[873,5239,5240],{},[740,5241,5223],{},[722,5243,5244],{},"FlowPick captures whatever the player is actually delivering to you. If you have a premium membership, the player loads high-bitrate streams, and FlowPick sees those. Free accounts top out at standard 1080P.",[732,5246,5248],{"id":5247},"step-by-step-downloading-bilibili-videos","Step-by-Step: Downloading Bilibili Videos",[937,5250,4573],{"id":5251},"step-1-install-flowpick",[722,5253,4576,5254,3567,5257,5260],{},[944,5255,2508],{"href":2506,"rel":5256},[948],[944,5258,2513],{"href":2511,"rel":5259},[948],". One click, no registration.",[722,5262,5263],{},"If you're already logged into Bilibili, stay logged in — FlowPick needs that to access the quality tiers tied to your account.",[937,5265,5267],{"id":5266},"step-2-open-the-video-and-start-playing","Step 2: Open the Video and Start Playing",[722,5269,5270,5271,5274,5275,5278],{},"Go to ",[749,5272,5273],{},"bilibili.com\u002Fvideo\u002FBVxxxx"," (any video page). The video must ",[740,5276,5277],{},"actually start playing",", not just load the page. If autoplay is off, click the play button.",[722,5280,5281],{},"This matters: the Bilibili player doesn't request the DASH manifest until playback begins. FlowPick is waiting for that request.",[937,5283,5285],{"id":5284},"step-3-open-the-flowpick-popup","Step 3: Open the FlowPick Popup",[722,5287,5288],{},"Click the FlowPick icon in your browser toolbar. The badge number shows how many streams were detected. On Bilibili, you'll typically see:",[1149,5290,5291,5294,5297],{},[767,5292,5293],{},"The main video, shown as a DASH\u002FMPD entry (may have multiple quality levels)",[767,5295,5296],{},"A separate audio-only entry",[767,5298,5299],{},"Sometimes ad streams (smaller file sizes — ignore those)",[722,5301,5302],{},[816,5303],{"alt":5304,"src":5305},"FlowPick detecting DASH streams on Bilibili","\u002Fscreenshots\u002Fdash-detection.png",[937,5307,5309],{"id":5308},"step-4-select-the-video-entry","Step 4: Select the Video Entry",[722,5311,5312],{},"Click the DASH entry that corresponds to your video (look for the one with the larger estimated file size — that's the actual content, not a 30-second pre-roll ad).",[722,5314,5315],{},"FlowPick parses the DASH manifest and shows all available quality levels. Pick the resolution you want.",[722,5317,5318],{},[816,5319],{"alt":5320,"src":4621},"FlowPick selecting Bilibili video quality",[937,5322,5324],{"id":5323},"step-5-name-and-download","Step 5: Name and Download",[722,5326,5327,5328,1141],{},"Give the file a name, set the thread count (default is 2; 4-6 is fine for typical broadband — no need to max it out), and click ",[740,5329,4631],{},[722,5331,5332],{},"FlowPick will then:",[764,5334,5335,5338,5341],{},[767,5336,5337],{},"Download video and audio segments in parallel",[767,5339,5340],{},"Merge audio and video using FFmpeg WebAssembly running inside the browser",[767,5342,5343],{},"Save the merged MP4 to your downloads folder",[722,5345,5346],{},"On typical broadband, a 20-minute 1080P video usually takes 2-4 minutes.",[937,5348,5350],{"id":5349},"step-6-confirm-download-complete","Step 6: Confirm Download Complete",[722,5352,5353],{},"The FlowPick popup shows download progress. Once done, the file is in your browser's default download directory, ready to play in any player.",[722,5355,5356],{},[816,5357],{"alt":4644,"src":2915},[732,5359,5361],{"id":5360},"handling-multi-part-long-videos","Handling Multi-Part Long Videos",[722,5363,5364],{},"Bilibili lets creators split long videos into multiple \"parts.\" A 3-hour documentary might be uploaded as P1, P2, P3, displayed as a playlist on the page.",[722,5366,5367],{},"Each part is an independent DASH stream. You need to:",[764,5369,5370,5373,5376],{},[767,5371,5372],{},"Play P1, download with FlowPick",[767,5374,5375],{},"Switch to P2 (or click the part in the sidebar), play, download",[767,5377,5378],{},"Repeat for remaining parts",[722,5380,5381],{},"If you want to merge them into one file, use any video editor or ffmpeg afterwards:",[959,5383,5385],{"className":3087,"code":5384,"language":3089,"meta":967,"style":967},"ffmpeg -f concat -safe 0 -i filelist.txt -c copy combined.mp4\n",[749,5386,5387],{"__ignoreMap":967},[1557,5388,5389,5391,5394,5397,5400,5403,5405,5408,5411,5414],{"class":1559,"line":1560},[1557,5390,1447],{"class":2284},[1557,5392,5393],{"class":1977}," -f",[1557,5395,5396],{"class":1977}," concat",[1557,5398,5399],{"class":1977}," -safe",[1557,5401,5402],{"class":1701}," 0",[1557,5404,3098],{"class":1977},[1557,5406,5407],{"class":1977}," filelist.txt",[1557,5409,5410],{"class":1977}," -c",[1557,5412,5413],{"class":1977}," copy",[1557,5415,5416],{"class":1977}," combined.mp4\n",[722,5418,5419,5420,5423],{},"Where ",[749,5421,5422],{},"filelist.txt"," contains:",[959,5425,5428],{"className":5426,"code":5427,"language":964},[962],"file 'part1.mp4'\nfile 'part2.mp4'\nfile 'part3.mp4'\n",[749,5429,5427],{"__ignoreMap":967},[732,5431,5433],{"id":5432},"downloading-bilibili-livestreams","Downloading Bilibili Livestreams",[722,5435,5436,5437,5439],{},"Livestreams on ",[749,5438,5061],{}," use HLS instead of DASH. The process is similar but with key differences:",[1149,5441,5442,5448,5454],{},[767,5443,5444,5447],{},[740,5445,5446],{},"You can only record from the moment you start."," No rewind — if you missed the first 30 minutes, those segments are already gone from the CDN.",[767,5449,5450,5453],{},[740,5451,5452],{},"Stream URLs expire quickly"," — typically 30-60 minutes. A download that times out may fail mid-way.",[767,5455,5456,5459],{},[740,5457,5458],{},"Prefer replay URLs."," If the streamer has replays enabled, the replay becomes a static DASH stream (not live HLS) and can be cleanly downloaded after the broadcast ends.",[722,5461,5462],{},"Livestream capture tip: start FlowPick early. Don't wait until something exciting happens.",[732,5464,5466],{"id":5465},"anime-download-notes","Anime Download Notes",[722,5468,5469],{},"Anime CDN tokens typically have shorter validity windows than regular uploads (user reports suggest under 2 hours) and may be IP-bound. A few tips:",[1149,5471,5472,5478,5484],{},[767,5473,5474,5477],{},[740,5475,5476],{},"Don't switch networks mid-download"," (e.g., from WiFi to mobile hotspot) — it'll break token validation",[767,5479,5480,5483],{},[740,5481,5482],{},"Don't leave a download task hanging."," A 20-minute anime episode usually downloads in 2-3 minutes. Leaving it unattended for hours and coming back — it's probably failed",[767,5485,5486],{},"If a download fails, just refresh the page, re-detect, and start a fresh download",[732,5488,3029],{"id":3028},[937,5490,4828],{"id":4827},[722,5492,5493],{},"Most likely the video hasn't started yet. Try:",[1149,5495,5496,5499,5502],{},[767,5497,5498],{},"Manually pressing play",[767,5500,5501],{},"Waiting for the first 5 seconds of buffering",[767,5503,5504],{},"Refreshing the page (player initialization state sometimes expires)",[722,5506,5507],{},"Still nothing: Bilibili occasionally wraps the player in an iframe. Right-click the video → \"Open frame in new tab\" → try FlowPick there.",[937,5509,5511],{"id":5510},"download-stalls-at-70-80","Download stalls at 70-80%",[722,5513,5514],{},"Stream URLs have expired. Bilibili CDN signed URLs are typically valid for 6 hours; paid content may be shorter. Reload the page, re-detect, start a fresh download. Don't start a download and leave it unattended for long periods.",[937,5516,5518],{"id":5517},"output-file-has-no-audio","Output file has no audio",[722,5520,5521],{},"The audio stream wasn't captured. Usually happens when:",[1149,5523,5524,5527],{},[767,5525,5526],{},"The video auto-advanced to the next part",[767,5528,5529],{},"A popup or ad interrupted audio stream initialization",[722,5531,5532],{},"Reload, play from the beginning (not muted), re-detect before downloading.",[937,5534,5536,5537,5539],{"id":5535},"output-is-in-blv-format","Output is in ",[749,5538,5015],{}," format",[722,5541,5542],{},"That's not FlowPick — you accidentally used the official Bilibili app's \"cache\" feature. Make sure you're using the browser extension on the web player, not the app.",[937,5544,5546],{"id":5545},"only-getting-360p-despite-having-premium","Only getting 360P despite having premium",[722,5548,5549],{},"FlowPick captures whatever stream the player actually requests. If the page shows 360P, the player chose 360P — click the quality switcher in the player, manually switch to a higher quality, then re-detect.",[732,5551,5553],{"id":5552},"large-file-downloads","Large File Downloads",[722,5555,5556],{},"Bilibili 4K videos can reach several GB. FlowPick uses the browser's File System Access API for streaming writes, with no file size limit. Memory usage stays stable throughout the download — the browser won't freeze just because the file is large.",[722,5558,5559],{},"Use Chrome or Edge for large file downloads — these two browsers have the most complete FSA streaming write support.",[732,5561,5563],{"id":5562},"legal-note","Legal Note",[722,5565,5566],{},"FlowPick captures the video your browser is already rendering. Bilibili's terms of service restrict redistribution of downloaded content. In jurisdictions where it's permitted, downloading for personal offline viewing exists in a gray area — check your local laws. Don't redistribute content you didn't create.",[1346,5568],{},[732,5570,1359],{"id":1358},[1149,5572,5573,5580,5587,5594],{},[767,5574,5575,5579],{},[740,5576,5577],{},[944,5578,1387],{"href":1386}," — a deep dive into Bilibili's primary streaming format",[767,5581,5582,5586],{},[740,5583,5584],{},[944,5585,1369],{"href":1368}," — Bilibili livestreams use HLS; this covers the full HLS workflow",[767,5588,5589,5593],{},[740,5590,5591],{},[944,5592,1405],{"href":1404}," — batch-grab images from Bilibili posts and comment sections",[767,5595,5596,5600],{},[740,5597,5598],{},[944,5599,1414],{"href":1413}," — tool comparison for DASH-heavy platforms like Bilibili",[2563,5602,3169],{},{"title":967,"searchDepth":1417,"depth":1417,"links":5604},[5605,5606,5607,5608,5616,5617,5618,5619,5627,5628,5629],{"id":5022,"depth":1417,"text":5023},{"id":5072,"depth":1417,"text":5073},{"id":5165,"depth":1417,"text":5166},{"id":5247,"depth":1417,"text":5248,"children":5609},[5610,5611,5612,5613,5614,5615],{"id":5251,"depth":1425,"text":4573},{"id":5266,"depth":1425,"text":5267},{"id":5284,"depth":1425,"text":5285},{"id":5308,"depth":1425,"text":5309},{"id":5323,"depth":1425,"text":5324},{"id":5349,"depth":1425,"text":5350},{"id":5360,"depth":1417,"text":5361},{"id":5432,"depth":1417,"text":5433},{"id":5465,"depth":1417,"text":5466},{"id":3028,"depth":1417,"text":3029,"children":5620},[5621,5622,5623,5624,5626],{"id":4827,"depth":1425,"text":4828},{"id":5510,"depth":1425,"text":5511},{"id":5517,"depth":1425,"text":5518},{"id":5535,"depth":1425,"text":5625},"Output is in .blv format",{"id":5545,"depth":1425,"text":5546},{"id":5552,"depth":1417,"text":5553},{"id":5562,"depth":1417,"text":5563},{"id":1358,"depth":1417,"text":1359},"A complete tutorial on downloading Bilibili videos using the FlowPick browser extension, supporting HLS livestreams, DASH videos, and regular MP4 content.",{},11,{"title":5004,"description":5630},"blog\u002Fhow-to-download-bilibili-videos",[5636,5637,1449,1450,3209],"bilibili","video download","b79L91otLL5IrCf6vu_-SxURHQHOfjz_CXhmYhRIxzM",{"id":5640,"title":5641,"author":717,"body":5642,"category":1434,"date":3743,"description":6509,"extension":1437,"image":6020,"meta":6510,"navigation":1440,"path":1386,"readingTime":5849,"seo":6511,"stem":6512,"tags":6513,"__hash__":6517},"blog\u002Fblog\u002Fwhat-is-dash-mpd-streaming.md","What Is DASH Streaming? MPD File Explained for Non-Developers",{"type":719,"value":5643,"toc":6491},[5644,5654,5657,5661,5664,5670,5677,5681,5684,5763,5771,5775,5782,5785,5933,5936,5962,5976,5980,5987,5990,5993,6004,6015,6021,6025,6043,6134,6141,6144,6148,6151,6160,6166,6176,6186,6192,6196,6199,6202,6252,6255,6263,6267,6270,6323,6326,6330,6333,6362,6365,6368,6372,6386,6411,6418,6421,6423,6427,6430,6434,6440,6444,6447,6449,6451,6488],[722,5645,5646,5647,5649,5650,5653],{},"You try to download a video and instead of an MP4, you get a 12KB ",[749,5648,2817],{}," file. You open it in a text editor — it's a wall of XML, hundreds of ",[749,5651,5652],{},"\u003CSegmentURL>"," tags pointing to files that aren't on your disk.",[722,5655,5656],{},"That's MPEG-DASH. Here's what it is, why platforms use it, and how to actually get the video out.",[732,5658,5660],{"id":5659},"what-problem-dash-solves","What Problem DASH Solves",[722,5662,5663],{},"Rewind to 2008. YouTube was delivering fixed-bitrate FLV files. When your connection was slow and someone uploaded a 720p video, you just watched the spinner. Your only option was to manually switch to 360p.",[722,5665,5666,5667],{},"This was embarrassing for platforms. The solution the industry landed on: ",[740,5668,5669],{},"instead of delivering a fixed file, deliver a manifest describing all available quality levels and let the player decide which to request based on real-time bandwidth measurements.",[722,5671,5672,5673,5676],{},"Apple's HLS and the ISO-standard DASH (2012) both solved this. DASH — ",[740,5674,5675],{},"Dynamic Adaptive Streaming over HTTP"," — became the international standard: YouTube, Vimeo, Dailymotion, and most enterprise video platforms use it.",[732,5678,5680],{"id":5679},"which-major-platforms-use-dash","Which Major Platforms Use DASH",[722,5682,5683],{},"Knowing which platforms use DASH helps you anticipate what you'll run into when downloading:",[850,5685,5686,5698],{},[853,5687,5688],{},[856,5689,5690,5693,5696],{},[859,5691,5692],{},"Platform",[859,5694,5695],{},"Streaming Protocol",[859,5697,5098],{},[868,5699,5700,5711,5722,5732,5742,5752],{},[856,5701,5702,5705,5708],{},[873,5703,5704],{},"YouTube",[873,5706,5707],{},"DASH",[873,5709,5710],{},"Primarily DASH, some content also uses HLS",[856,5712,5713,5716,5719],{},[873,5714,5715],{},"Bilibili",[873,5717,5718],{},"DASH (regular content) + HLS (livestreams)",[873,5720,5721],{},"Audio\u002Fvideo separated, requires merging",[856,5723,5724,5727,5729],{},[873,5725,5726],{},"Vimeo",[873,5728,5707],{},[873,5730,5731],{},"Enterprise content also uses DASH",[856,5733,5734,5737,5739],{},[873,5735,5736],{},"Twitch",[873,5738,4404],{},[873,5740,5741],{},"Livestreams use HLS, VODs sometimes use DASH",[856,5743,5744,5747,5749],{},[873,5745,5746],{},"Coursera \u002F edX",[873,5748,5707],{},[873,5750,5751],{},"Course videos commonly use it",[856,5753,5754,5757,5760],{},[873,5755,5756],{},"Tencent Video \u002F Youku \u002F iQiyi",[873,5758,5759],{},"DASH + DRM",[873,5761,5762],{},"High-value content layered with Widevine",[722,5764,5765,5766,5770],{},"YouTube is an interesting case — it started deploying DASH in 2011, was one of the earliest large-scale adopters, and now serves billions of streaming requests daily entirely on this mechanism. Bilibili is also a heavy DASH user — regular videos use DASH, livestreams use HLS. ",[740,5767,5768],{},[944,5769,1396],{"href":1395}," walks through the full Bilibili download workflow and is a great real-world case for understanding DASH.",[732,5772,5774],{"id":5773},"the-mpd-file-not-a-video-a-recipe","The MPD File: Not a Video, a Recipe",[722,5776,5777,5778,5781],{},"Every DASH stream starts with an ",[740,5779,5780],{},"MPD file"," — Media Presentation Description. It's an XML document describing the entire stream: duration, available quality levels, codec information, and the paths to find every video and audio segment.",[722,5783,5784],{},"A real, simplified MPD:",[959,5786,5790],{"className":5787,"code":5788,"language":5789,"meta":967,"style":967},"language-xml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003CMPD type=\"static\" mediaPresentationDuration=\"PT1H23M47S\">\n  \u003CPeriod>\n    \u003CAdaptationSet contentType=\"video\" mimeType=\"video\u002Fmp4\">\n      \u003CRepresentation id=\"v1080\" bandwidth=\"4500000\" width=\"1920\" height=\"1080\">\n        \u003CSegmentTemplate\n          initialization=\"video\u002F1080p\u002Finit.mp4\"\n          media=\"video\u002F1080p\u002Fseg-$Number$.m4s\"\n          startNumber=\"1\" \u002F>\n      \u003C\u002FRepresentation>\n      \u003CRepresentation id=\"v720\" bandwidth=\"2000000\" width=\"1280\" height=\"720\">\n        \u003CSegmentTemplate\n          initialization=\"video\u002F720p\u002Finit.mp4\"\n          media=\"video\u002F720p\u002Fseg-$Number$.m4s\"\n          startNumber=\"1\" \u002F>\n      \u003C\u002FRepresentation>\n    \u003C\u002FAdaptationSet>\n    \u003CAdaptationSet contentType=\"audio\" mimeType=\"audio\u002Fmp4\">\n      \u003CRepresentation id=\"a128\" bandwidth=\"128000\">\n        \u003CSegmentTemplate\n          initialization=\"audio\u002Finit.mp4\"\n          media=\"audio\u002Fseg-$Number$.m4s\"\n          startNumber=\"1\" \u002F>\n      \u003C\u002FRepresentation>\n    \u003C\u002FAdaptationSet>\n  \u003C\u002FPeriod>\n\u003C\u002FMPD>\n","xml",[749,5791,5792,5797,5802,5807,5812,5817,5822,5827,5832,5837,5842,5847,5852,5857,5862,5867,5871,5877,5883,5889,5894,5900,5906,5911,5916,5921,5927],{"__ignoreMap":967},[1557,5793,5794],{"class":1559,"line":1560},[1557,5795,5796],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[1557,5798,5799],{"class":1559,"line":1417},[1557,5800,5801],{},"\u003CMPD type=\"static\" mediaPresentationDuration=\"PT1H23M47S\">\n",[1557,5803,5804],{"class":1559,"line":1425},[1557,5805,5806],{},"  \u003CPeriod>\n",[1557,5808,5809],{"class":1559,"line":1618},[1557,5810,5811],{},"    \u003CAdaptationSet contentType=\"video\" mimeType=\"video\u002Fmp4\">\n",[1557,5813,5814],{"class":1559,"line":1660},[1557,5815,5816],{},"      \u003CRepresentation id=\"v1080\" bandwidth=\"4500000\" width=\"1920\" height=\"1080\">\n",[1557,5818,5819],{"class":1559,"line":1688},[1557,5820,5821],{},"        \u003CSegmentTemplate\n",[1557,5823,5824],{"class":1559,"line":2267},[1557,5825,5826],{},"          initialization=\"video\u002F1080p\u002Finit.mp4\"\n",[1557,5828,5829],{"class":1559,"line":2272},[1557,5830,5831],{},"          media=\"video\u002F1080p\u002Fseg-$Number$.m4s\"\n",[1557,5833,5834],{"class":1559,"line":2278},[1557,5835,5836],{},"          startNumber=\"1\" \u002F>\n",[1557,5838,5839],{"class":1559,"line":3201},[1557,5840,5841],{},"      \u003C\u002FRepresentation>\n",[1557,5843,5844],{"class":1559,"line":5632},[1557,5845,5846],{},"      \u003CRepresentation id=\"v720\" bandwidth=\"2000000\" width=\"1280\" height=\"720\">\n",[1557,5848,5850],{"class":1559,"line":5849},12,[1557,5851,5821],{},[1557,5853,5854],{"class":1559,"line":1442},[1557,5855,5856],{},"          initialization=\"video\u002F720p\u002Finit.mp4\"\n",[1557,5858,5859],{"class":1559,"line":4995},[1557,5860,5861],{},"          media=\"video\u002F720p\u002Fseg-$Number$.m4s\"\n",[1557,5863,5865],{"class":1559,"line":5864},15,[1557,5866,5836],{},[1557,5868,5869],{"class":1559,"line":2583},[1557,5870,5841],{},[1557,5872,5874],{"class":1559,"line":5873},17,[1557,5875,5876],{},"    \u003C\u002FAdaptationSet>\n",[1557,5878,5880],{"class":1559,"line":5879},18,[1557,5881,5882],{},"    \u003CAdaptationSet contentType=\"audio\" mimeType=\"audio\u002Fmp4\">\n",[1557,5884,5886],{"class":1559,"line":5885},19,[1557,5887,5888],{},"      \u003CRepresentation id=\"a128\" bandwidth=\"128000\">\n",[1557,5890,5892],{"class":1559,"line":5891},20,[1557,5893,5821],{},[1557,5895,5897],{"class":1559,"line":5896},21,[1557,5898,5899],{},"          initialization=\"audio\u002Finit.mp4\"\n",[1557,5901,5903],{"class":1559,"line":5902},22,[1557,5904,5905],{},"          media=\"audio\u002Fseg-$Number$.m4s\"\n",[1557,5907,5909],{"class":1559,"line":5908},23,[1557,5910,5836],{},[1557,5912,5914],{"class":1559,"line":5913},24,[1557,5915,5841],{},[1557,5917,5919],{"class":1559,"line":5918},25,[1557,5920,5876],{},[1557,5922,5924],{"class":1559,"line":5923},26,[1557,5925,5926],{},"  \u003C\u002FPeriod>\n",[1557,5928,5930],{"class":1559,"line":5929},27,[1557,5931,5932],{},"\u003C\u002FMPD>\n",[722,5934,5935],{},"Structure breakdown:",[1149,5937,5938,5946,5954],{},[767,5939,5940,5945],{},[740,5941,5942],{},[749,5943,5944],{},"\u003CAdaptationSet>",": a group of equivalent tracks — all video options, or all audio options",[767,5947,5948,5953],{},[740,5949,5950],{},[749,5951,5952],{},"\u003CRepresentation>",": one specific quality\u002Fcodec combination within that group",[767,5955,5956,5961],{},[740,5957,5958],{},[749,5959,5960],{},"\u003CSegmentTemplate>",": the URL template for segment files",[722,5963,5964,5965,5968,5969,1870,5972,5975],{},"The template ",[749,5966,5967],{},"video\u002F1080p\u002Fseg-$Number$.m4s"," expands to ",[749,5970,5971],{},"seg-1.m4s",[749,5973,5974],{},"seg-2.m4s","… potentially thousands of files.",[732,5977,5979],{"id":5978},"audiovideo-separation-dashs-core-design","Audio\u002FVideo Separation: DASH's Core Design",[722,5981,5982,5983,5986],{},"The thing that frustrates downloaders most about DASH is that ",[740,5984,5985],{},"audio and video are two independent streams",". This isn't malice — it's by design:",[722,5988,5989],{},"Imagine you want to offer a video with 5 language audio tracks. If audio and video were bundled together, you'd need to store 5 complete copies of the video file — 5x the server storage cost. DASH's approach: one video track, 5 audio tracks. The player combines them based on the language setting. Storage costs drop dramatically.",[722,5991,5992],{},"For downloaders, this means:",[764,5994,5995,5998,6001],{},[767,5996,5997],{},"You must download both the video track and the audio track",[767,5999,6000],{},"After downloading, you must merge them into one file",[767,6002,6003],{},"Download only video — no sound. Download only audio — no picture",[722,6005,6006,6007,6010,6011,1141],{},"FlowPick automatically detects all ",[749,6008,6009],{},"AdaptationSet"," entries in the MPD and downloads and merges them for you. The merge step sounds simple, but running FFmpeg WASM in a browser has its share of pitfalls — for a detailed breakdown, see ",[740,6012,6013],{},[944,6014,2532],{"href":1441},[722,6016,6017],{},[816,6018],{"alt":6019,"src":6020},"DASH audio and video tracks downloaded separately then merged","\u002Fscreenshots\u002Fdash-downloader.png",[732,6022,6024],{"id":6023},"dash-vs-hls-whats-the-difference","DASH vs. HLS: What's the Difference?",[722,6026,6027,6028,6031,6032,6034,6035,6037,6038,6034,6040,6042],{},"If you've read ",[944,6029,6030],{"href":1368},"How to Download M3U8\u002FHLS Streams",", you know HLS uses ",[749,6033,2804],{}," files and ",[749,6036,751],{}," segments. DASH uses ",[749,6039,2817],{},[749,6041,758],{}," segments. Same goal — adaptive streaming — but the details differ significantly.",[850,6044,6045,6055],{},[853,6046,6047],{},[856,6048,6049,6051,6053],{},[859,6050],{},[859,6052,5707],{},[859,6054,4404],{},[868,6056,6057,6068,6083,6099,6112,6123],{},[856,6058,6059,6062,6065],{},[873,6060,6061],{},"Origin",[873,6063,6064],{},"ISO\u002FIEC 23009, 2012",[873,6066,6067],{},"Apple, 2009",[856,6069,6070,6073,6078],{},[873,6071,6072],{},"Manifest format",[873,6074,6075,6076,1634],{},"XML (",[749,6077,2817],{},[873,6079,6080,6081,1634],{},"Plain text (",[749,6082,2804],{},[856,6084,6085,6088,6094],{},[873,6086,6087],{},"Segment format",[873,6089,6090,6091,6093],{},"fMP4 (",[749,6092,758],{},") or MP4",[873,6095,6096,6097,1634],{},"MPEG-TS (",[749,6098,751],{},[856,6100,6101,6104,6109],{},[873,6102,6103],{},"Audio handling",[873,6105,6106],{},[740,6107,6108],{},"Independent track",[873,6110,6111],{},"Merged with video",[856,6113,6114,6117,6120],{},[873,6115,6116],{},"Encryption",[873,6118,6119],{},"CENC (Widevine\u002FPlayReady)",[873,6121,6122],{},"AES-128 (key obtainable)",[856,6124,6125,6128,6131],{},[873,6126,6127],{},"Who uses it",[873,6129,6130],{},"YouTube, Vimeo, enterprise platforms",[873,6132,6133],{},"Twitch, Apple platforms",[722,6135,6136,6137,6140],{},"The key practical difference: ",[740,6138,6139],{},"DASH separates audio and video",". The video file has no audio; the audio file has no video. You need to download both and merge them.",[722,6142,6143],{},"Another important difference is encryption: with HLS's common AES-128 encryption, the key can be obtained through a normal HTTP request (while logged in). DASH's CENC encryption goes through the Widevine\u002FPlayReady authorization system, where keys are bound to the device and inaccessible to browser extensions.",[732,6145,6147],{"id":6146},"why-dash-videos-are-normally-impossible-to-download","Why DASH Videos Are Normally Impossible to Download",[722,6149,6150],{},"Putting it all together, here's why nothing seems to work:",[722,6152,6153,6156,6157,6159],{},[740,6154,6155],{},"1. There is no video file."," The MPD is a blueprint. The actual content is scattered across hundreds of ",[749,6158,758],{}," segments on a CDN.",[722,6161,6162,6165],{},[740,6163,6164],{},"2. Audio and video are independent tracks."," Even if you could download all the video segments, the file has no sound. The audio segments are an entirely separate download path.",[722,6167,6168,6171,6172,6175],{},[740,6169,6170],{},"3. Segment URLs usually contain signature tokens."," A typical segment URL looks like: ",[749,6173,6174],{},"https:\u002F\u002Fcdn.example.com\u002Fvideo\u002Fseg-42.m4s?token=eyJhbGc...&expires=1735689600",". This token is time-limited and bound to your session.",[722,6177,6178,6181,6182,6185],{},[740,6179,6180],{},"4. The init segment matters."," Both video and audio tracks start with an \"initialization segment\" (",[749,6183,6184],{},"init.mp4",") containing codec parameters. Without it, players can't parse subsequent segments.",[722,6187,6188,6191],{},[740,6189,6190],{},"5. Content protection (CENC)."," High-value content layers Widevine or PlayReady DRM on top of DASH. Every segment is encrypted, and keys are delivered through a separate authorization server.",[732,6193,6195],{"id":6194},"how-flowpick-downloads-dash-streams","How FlowPick Downloads DASH Streams",[722,6197,6198],{},"FlowPick intercepts MPD requests inside the browser tab. When you're logged into a platform, your session cookies travel with the request — FlowPick sees the same manifest your player sees.",[722,6200,6201],{},"The download pipeline:",[764,6203,6204,6216,6222,6228,6234,6240,6246],{},[767,6205,6206,6209,6210,3267,6212,6215],{},[740,6207,6208],{},"Parse the MPD XML"," — extract all ",[749,6211,6009],{},[749,6213,6214],{},"Representation"," entries",[767,6217,6218,6221],{},[740,6219,6220],{},"Show quality options"," — display available video resolutions and audio tracks",[767,6223,6224,6227],{},[740,6225,6226],{},"Parallel download"," — video and audio segments download simultaneously, thread count set in settings (default 2, adjustable)",[767,6229,6230,6233],{},[740,6231,6232],{},"Fetch init segments first"," — these files are small but essential for merging",[767,6235,6236,6239],{},[740,6237,6238],{},"Assemble in memory"," — concatenate init + all segments in order",[767,6241,6242,6245],{},[740,6243,6244],{},"Merge video and audio"," — using FFmpeg compiled to WebAssembly, running entirely within the browser tab",[767,6247,6248,6251],{},[740,6249,6250],{},"Write to disk"," — via Chrome\u002FEdge's File System Access API, or direct blob download on Firefox",[722,6253,6254],{},"The entire pipeline runs client-side. Nothing is uploaded. Segments never pass through a third-party server.",[722,6256,6257,6258,6262],{},"If you have a standalone MPD URL, you can also paste it directly into the ",[944,6259,6261],{"href":6260},"\u002Fdash-downloader","DASH Online Downloader"," — no installation needed.",[732,6264,6266],{"id":6265},"online-tool-vs-extension-which-to-use","Online Tool vs. Extension: Which to Use",[722,6268,6269],{},"For DASH downloads, each approach has its scenarios:",[850,6271,6272,6281],{},[853,6273,6274],{},[856,6275,6276,6278],{},[859,6277,3486],{},[859,6279,6280],{},"Recommended",[868,6282,6283,6291,6299,6307,6315],{},[856,6284,6285,6288],{},[873,6286,6287],{},"Have a standalone MPD URL, public stream",[873,6289,6290],{},"Online tool, no installation",[856,6292,6293,6296],{},[873,6294,6295],{},"Content requires login",[873,6297,6298],{},"Browser extension, carries session cookies",[856,6300,6301,6304],{},[873,6302,6303],{},"Platform uses POST to fetch the manifest",[873,6305,6306],{},"Browser extension, intercepts at network layer",[856,6308,6309,6312],{},[873,6310,6311],{},"Platform obfuscates the MPD URL",[873,6313,6314],{},"Browser extension, auto-detection",[856,6316,6317,6320],{},[873,6318,6319],{},"Segment URLs have time-limited tokens",[873,6321,6322],{},"Browser extension, real-time access",[722,6324,6325],{},"Online tools are limited by browser cross-origin (CORS) policies — if the video hosting server hasn't configured CORS headers, direct downloads from the online tool will fail. The extension is more reliable in these cases.",[732,6327,6329],{"id":6328},"identifying-dash-in-the-wild","Identifying DASH in the Wild",[722,6331,6332],{},"Want to know if a site uses DASH? Open DevTools before the video loads:",[764,6334,6335,6345,6353,6356],{},[767,6336,6337,6340,6341,6344],{},[740,6338,6339],{},"F12"," → ",[740,6342,6343],{},"Network"," tab",[767,6346,6347,6348,3567,6350],{},"Filter for ",[749,6349,2817],{},[749,6351,6352],{},"manifest",[767,6354,6355],{},"Play the video",[767,6357,6358,6359],{},"Look for a request with ",[749,6360,6361],{},"Content-Type: application\u002Fdash+xml",[722,6363,6364],{},"Found it — that's the entry point. That request's URL is what you'd paste into the online tool, and what FlowPick automatically intercepts.",[722,6366,6367],{},"Some platforms obfuscate the URL or use POST instead of GET to fetch the manifest. The online tool won't work in these cases, but the browser extension will — it captures at the network interception layer regardless of the method.",[732,6369,6371],{"id":6370},"livestreams-a-completely-different-beast","Livestreams: A Completely Different Beast",[722,6373,6374,6375,6378,6379,6382,6383,848],{},"Static DASH streams (movies, recorded courses) have ",[749,6376,6377],{},"type=\"static\""," and a known ",[749,6380,6381],{},"mediaPresentationDuration",". Livestreams use ",[749,6384,6385],{},"type=\"dynamic\"",[959,6387,6389],{"className":5787,"code":6388,"language":5789,"meta":967,"style":967},"\u003CMPD type=\"dynamic\"\n     availabilityStartTime=\"2026-07-01T09:00:00Z\"\n     minimumUpdatePeriod=\"PT5S\"\n     timeShiftBufferDepth=\"PT1H\">\n",[749,6390,6391,6396,6401,6406],{"__ignoreMap":967},[1557,6392,6393],{"class":1559,"line":1560},[1557,6394,6395],{},"\u003CMPD type=\"dynamic\"\n",[1557,6397,6398],{"class":1559,"line":1417},[1557,6399,6400],{},"     availabilityStartTime=\"2026-07-01T09:00:00Z\"\n",[1557,6402,6403],{"class":1559,"line":1425},[1557,6404,6405],{},"     minimumUpdatePeriod=\"PT5S\"\n",[1557,6407,6408],{"class":1559,"line":1618},[1557,6409,6410],{},"     timeShiftBufferDepth=\"PT1H\">\n",[722,6412,6413,6414,6417],{},"The MPD constantly updates (every 5 seconds in this example), and only a rolling window of segments is available. Once a segment falls outside ",[749,6415,6416],{},"timeShiftBufferDepth",", it's gone from the CDN.",[722,6419,6420],{},"For livestreams, FlowPick captures from the moment you start recording — there's no going back beyond the current buffer. For past livestream content, look for the replay\u002FVOD URL, which is a static DASH or HLS stream that can be cleanly downloaded.",[732,6422,3029],{"id":3028},[937,6424,6426],{"id":6425},"downloaded-the-mpd-file-cant-open-it","Downloaded the MPD file, can't open it",[722,6428,6429],{},"The MPD is not a video file — it's a manifest. You need a tool to parse it and download all segments in order, then merge them. Opening an MPD directly in a video player usually doesn't work (VLC is an exception — it supports direct DASH URL playback).",[937,6431,6433],{"id":6432},"audio-and-video-are-separate-after-downloading","Audio and video are separate after downloading",[722,6435,6436,6437,6439],{},"You only downloaded one of the two tracks — either video or audio. Check whether FlowPick downloaded both ",[749,6438,6009],{}," entries — look in the popup for a separate audio entry that should be selected. If both tracks were downloaded but not merged, you may have hit FFmpeg WASM memory limits (file too large). Try a lower quality tier.",[937,6441,6443],{"id":6442},"segments-downloaded-successfully-but-merge-failed","Segments downloaded successfully but merge failed",[722,6445,6446],{},"Common with very large files (>4GB). FFmpeg WASM has memory limits in a 32-bit address space. When this happens, FlowPick attempts to switch to an alternative merge strategy. Use the latest version of Chrome — FSA streaming writes handle large files most reliably.",[1346,6448],{},[732,6450,1359],{"id":1358},[1149,6452,6453,6460,6467,6474,6481],{},[767,6454,6455,6459],{},[740,6456,6457],{},[944,6458,1369],{"href":1368}," — DASH's sibling protocol, slightly different approach",[767,6461,6462,6466],{},[740,6463,6464],{},[944,6465,2532],{"href":1441}," — how FFmpeg WASM merges audio and video tracks in the browser",[767,6468,6469,6473],{},[740,6470,6471],{},[944,6472,1396],{"href":1395}," — Bilibili is a DASH-heavy platform, real-world case study",[767,6475,6476,6480],{},[740,6477,6478],{},[944,6479,3704],{"href":3200}," — how to download DASH's independent audio tracks separately",[767,6482,6483,6487],{},[740,6484,6485],{},[944,6486,1414],{"href":1413}," — honest comparison, not an ad",[2563,6489,6490],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":967,"searchDepth":1417,"depth":1417,"links":6492},[6493,6494,6495,6496,6497,6498,6499,6500,6501,6502,6503,6508],{"id":5659,"depth":1417,"text":5660},{"id":5679,"depth":1417,"text":5680},{"id":5773,"depth":1417,"text":5774},{"id":5978,"depth":1417,"text":5979},{"id":6023,"depth":1417,"text":6024},{"id":6146,"depth":1417,"text":6147},{"id":6194,"depth":1417,"text":6195},{"id":6265,"depth":1417,"text":6266},{"id":6328,"depth":1417,"text":6329},{"id":6370,"depth":1417,"text":6371},{"id":3028,"depth":1417,"text":3029,"children":6504},[6505,6506,6507],{"id":6425,"depth":1425,"text":6426},{"id":6432,"depth":1425,"text":6433},{"id":6442,"depth":1425,"text":6443},{"id":1358,"depth":1417,"text":1359},"Learn what MPEG-DASH and MPD manifest files are, how adaptive streaming works, and why DASH videos are harder to download than regular MP4s.",{},{"title":5641,"description":6509},"blog\u002Fwhat-is-dash-mpd-streaming",[1450,6514,4999,6515,6516],"mpd","adaptive","explained","yDXr1OhMD0v1_5j43tZzb-YERXk-bilUlExaDUck-dk",1783158222924]