M3U8 下载失败怎么办?HLS 流媒体排查
M3U8 下载失败通常是播放列表未检测到、在线工具受到跨域限制,或流媒体需要原视频页面中的访问信息。本文按 HLS 的实际下载流水线逐步拆解,告诉你失败发生在哪一环、为什么、怎么修。
先理解:HLS 下载流水线
HLS 不是一条 URL 拉一个文件那么简单。它是一条流水线,任何一环出错,最终表现都是"下载失败"或"文件坏了"。先认清这条线,排查才有方向:
① 检测 M3U8 ② 解析清单 ③ 下载分片 ④ 解密 ⑤ 合并/转封装
↓ ↓ ↓ ↓ ↓
webRequest m3u8-parser Worker Pool WebCrypto FFmpeg WASM
拦截请求 区分 Master/ 并发拉 .ts AES-128-CBC TS → MP4
Media 重试退避 (如需要) 或直接拼接
| 环节 | 失败表现 | 本文对应章节 |
|---|---|---|
| ① 检测 | 弹出窗口显示"未检测到媒体" | 检测不到 M3U8 |
| ② 解析 | 清单下下来了但画质/分片列表为空 | 清单解析异常 |
| ③ 下载 | 进度卡住、403、404、CORS | 分片下载失败 |
| ④ 解密 | 解密报错或合并后无声/花屏 | 加密流解密失败 |
| ⑤ 合并 | 文件生成但无法播放、音画不同步 | 合并后无法播放 |
EXT-X-KEY、多音轨)的完整说明,请参阅 视频嗅探 — HLS 流。想看真实清单长什么样、为什么"朴素下载器"会翻车,可以翻 HLS 深度篇。快速处理
在深入排查前,先跑一遍这套 90% 场景能解决的流程:
- 打开原视频页面并播放几秒(很多站点懒加载,不播就不会请求 M3U8)。
- 安装扩展后没刷新过页面的,刷新一次(扩展的
webRequest监听是页面加载后才生效的)。 - 用扩展重新检测,选择目标清晰度。
- 选 MP4 输出,再点一次下载。
如果还是不行,按下面的章节定位。
检测不到 M3U8
症状:打开 FlowPick 弹出窗口显示"未检测到媒体资源",或列表里只有缩略图/封面,没有 .m3u8 条目。

常见原因
| 原因 | 怎么确认 | 解决 |
|---|---|---|
| 视频还没开始加载 | 没播过就打开扩展 | 播放 3-5 秒再打开扩展 |
| 扩展安装后没刷新页面 | 装扩展前页面已加载完毕 | 刷新页面重新触发请求 |
| 使用 MSE 而非原生 HLS | 开发者工具里看不到 .m3u8 请求 | 见下方"MSE 站点"小节 |
| 清单在跨域 iframe 里 | Network 面板能看到,但扩展拿不到 | 直接打开 iframe 源页面再检测 |
| 广告拦截器等扩展冲突 | 暂时禁用其他网络类扩展后能检测到 | 加白名单或调整加载顺序 |
| 自定义/私有协议 | Network 里走的是 ws://、blob: 或加密流 | FlowPick 暂不支持,见 已知限制 |
用开发者工具确认
按 F12 → Network 面板 → 在过滤框输入 m3u8:

- 看到请求:说明站点确实在用 HLS,问题在扩展检测侧(多半是没刷新页面或被其他扩展拦截)。
- 看不到请求:站点可能用了 MSE(Media Source Extensions),把分片直接喂给
<video>,根本不发.m3u8。这类站点 FlowPick 检测不到,属于浏览器层限制。
MSE 站点怎么办
如果 Network 里看到的是一堆 .m4s / .chunk 请求但没有清单文件,那它用的是 MSE + 自定义分片逻辑(YouTube 就是典型)。两条出路:
- 找
.mpd(DASH)入口,改用 DASH 下载器,参考 DASH 没声音排查。 - 找到
<video>标签的src,如果是blob:开头,那是 MSE 生成的,无法直接下载——只能等站点自己发原生 HLS/DASH 清单。
webRequest.onBeforeRequest、Content-Type 识别)和 25+ 种 MIME 类型的完整列表,请参阅 视频嗅探 — 检测原理。检测不到时的系统排查流程,见 常见问题排查 — 媒体检测问题。清单解析异常
症状:M3U8 检测到了,但画质列表是空的,或分片数量异常少(比如只有 1 个),下载出来只有几 KB。
Master 还是 Media
HLS 清单分两层。如果你在弹出窗口里看到的是 Media Playlist(分片清单本身),可能没有画质选项可选;反过来如果你拿到的只是 Master Playlist 的 URL,但解析出来的变体都是空的,多半是清单内容不完整。
Master Playlist ← 列出 360p / 720p / 1080p 变体
└── v_720.m3u8 ← Media Playlist,列出几百个 .ts 分片
└── seg-001.ts, seg-002.ts, ...
自己检查清单内容
把 M3U8 URL 复制出来,用 curl 看一眼内容(替换成你的 URL):
curl -sL "https://cdn.example.com/index.m3u8"
正常 Master Playlist 应该能看到 #EXT-X-STREAM-INF 行:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720
v_720.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080
v_1080.m3u8
正常 Media Playlist 应该能看到 #EXTINF + 分片 URL:
#EXTM3U
#EXT-X-TARGETDURATION:6
#EXT-X-VERSION:3
#EXTINF:6.0,
seg-001.ts
#EXTINF:6.0,
seg-002.ts
#EXTINF:4.2,
seg-003.ts
#EXT-X-ENDLIST
常见异常
| 看到的内容 | 问题 | 处理 |
|---|---|---|
| 整个 HTML 页面(登录页/错误页) | URL 被防盗链重定向 | 见 403 与防盗链 |
只有 #EXTM3U 一行 | 清单被截断或响应不完整 | 重新打开页面获取新 URL |
有 #EXT-X-KEY 但没分片 | 加密流密钥失败导致后续不输出 | 见 加密流解密失败 |
有 #EXT-X-ENDLIST 但分片数 < 5 | 直播流刚开播或已结束 | 直播流需在结束后下载回放 |
| 分片 URL 是相对路径 | 正常,解析器会补全基址 | 无需处理 |
CODECS 字段、不连续性标签等),这些是"朴素下载器"翻车的高发地。完整解析逻辑见 HLS 深度篇 和 视频嗅探 — Master vs Media Playlist。分片下载失败
症状:进度条走到一半报错、卡住不动,或控制台大量 403/404/CORS 报错。
403 与防盗链
分片或清单请求返回 403 Forbidden,几乎都是防盗链校验没通过。常见三类:
| 类型 | 表现 | FlowPick 处理 |
|---|---|---|
| Referer 校验 | 直接 curl 能下,浏览器扩展有时 403 | v1.1.1 起扩展自动注入原页面 Referer |
| Cookie / 登录态 | 未登录时 403,登录后正常 | 在原视频页面使用扩展即可携带 Cookie |
| 短时 Token | URL 里带 ?token=xxx&expires=yyy,过几分钟失效 | 见下方"过期链接" |
扩展版的防盗链代理(v1.1.1+)
从 v1.1.1 起,FlowPick 内置了防盗链代理预览与下载:图片列表、视频封面、悬停预览、图片/音频/文档/字幕下载,都会自动走代理,带上原页面的 Referer 和 Cookie,并在保存前检查响应——避免把防盗链返回的 HTML 错误页当成图片或音频存下来。
在线工具为什么经常 403
在线工具跑在 flowpick.com 域名下,浏览器不会把目标站点的 Cookie 和原页面 Referer 带过去,所以只要源站做了防盗链,在线工具基本必挂。带防盗链的站点请直接用扩展,在原视频页面操作。
用 curl 复现 403
确认是 Referer 问题最快的方法:
# 不带 Referer,多半 403
curl -I "https://cdn.example.com/seg-001.ts"
# 带上原页面 Referer,应该 200
curl -I -H "Referer: https://www.example.com/watch/123" "https://cdn.example.com/seg-001.ts"
第二个能 200,就坐实是 Referer 校验。
过期链接(动态 Token)
直播回放和部分平台的点播用短期 Token:
https://cdn.example.com/seg-001.ts?token=ab12cd34&expires=1692300000
expires 是 Unix 时间戳,过这个时间就 403/404。表现:
- 刚开始下载好好的,下到一半突然全部分片 403。
- 重新打开页面后又能下,但过一会儿又挂。
处理:
- 检测到清单后尽快开始下载,别在弹出窗口里犹豫太久。
- 大文件被 Token 截断时,刷新原页面,让扩展重新抓一份新清单,继续未完成的部分(FlowPick 的失败重试会处理临时波动,但 Token 过期是硬失败,必须换新清单)。
404 分片已删除
多见于直播回放——直播结束后平台只保留有限时间的回放分片,超期分片直接 404。
- 换个清晰度试试(不同清晰度可能对应不同分片文件)。
- 确认回放是否仍在有效期内。
CORS 错误
控制台报 has been blocked by CORS policy,基本都是在线工具遇到的。扩展有 host_permissions,不受同源策略限制。
| 工具 | CORS 表现 | 处理 |
|---|---|---|
| 浏览器扩展 | 不会触发 CORS | — |
| 在线工具 | 清单/分片跨域拉取被拦 | 改用扩展;或确认源站是否返回 Access-Control-Allow-Origin |
下载中途网络中断
临时网络抖动由重试机制覆盖。持续失败时:
- 检查网络稳定性(特别是代理/VPN 环境,见 常见问题排查 — 代理/VPN 环境)。
- 降低并发线程数到 2-3,减少同时连接数(部分 CDN 会拒绝过高并发)。
- 检查防火墙是否拦截了流媒体 CDN 域名。
加密流解密失败
症状:清单里有 #EXT-X-KEY,下载能跑完,但合并后无声/花屏/无法播放,或控制台报解密错误。
先区分 AES-128 还是 DRM
FlowPick 只支持 AES-128 这种"裸"加密。清单里看到这些标签的,统统下不了:
# AES-128 —— 支持
#EXT-X-KEY:METHOD=AES-128,URI="https://cdn.example.com/key.bin",IV=0x...
# SAMPLE-AES / FairPlay —— 不支持(DRM 邻近)
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://..."
# Widevine / PlayReady(DASH 里常见)—— 不支持
<ContentProtection schemeIdUri="..."/>
AES-128 解密失败的常见原因
| 原因 | 怎么确认 | 处理 |
|---|---|---|
| 密钥 URL 403/404 | 控制台报密钥拉取失败 | 在原页面用扩展(带 Cookie/Referer);密钥也可能带 Token |
| 密钥需要登录态 | 未登录时密钥 403,登录后正常 | 先登录再下载 |
| IV 缺失 | 清单里 #EXT-X-KEY 没有 IV= | FlowPick 会按规范用分片序号推导 IV,无需手动处理 |
| 密钥不是 16 字节 | curl 下来长度不对 | 源站异常,非 FlowPick 可修 |
用 curl 验证密钥
# 看密钥是否拿得到,注意要带 Referer/Cookie
curl -sL -H "Referer: https://www.example.com/watch/123" \
"https://cdn.example.com/key.bin" | wc -c
# 正常应该输出 16
输出不是 16,密钥本身有问题;输出 16 但扩展还是解密失败,多半是扩展请求时没带上 Cookie/Referer(升级到 v1.1.1+,防盗链代理会自动带)。
DRM 内容怎么办
Widevine、PlayReady、FairPlay、SAMPLE-AES 这些 DRM 保护内容,FlowPick 明确拒绝处理——这是特性不是 bug。声称能解 DRM 的下载器要么在撒谎要么违法。法律和技术底线见 流媒体下载合法性指南。
合并后无法播放
症状:文件下下来了,大小也正常,但 VLC/播放器打不开,或只有画面没声音,或音画不同步。
先用 VLC 测试
VLC 是兼容性最好的播放器,先用它排除"播放器问题":
- VLC 能播、系统播放器不能播 → 编解码/容器兼容问题,换播放器。
- VLC 也播不了 → 文件本身有问题。
常见原因
| 表现 | 原因 | 处理 |
|---|---|---|
| 文件只有几 KB | 下到了清单不是视频 | 重新选择资源,确认选的是视频不是 .m3u8 |
| 有画面没声音 | 多音轨清单只下了视频轨道 | 见下方"多音轨" |
| 音画不同步 | HLS 分片 PTS 不连续 | 切 TS 输出格式,或用 FFmpeg 重编码 |
| 花屏/损坏 | 个别分片下载失败导致合并不完整 | 重新下载,确保所有分片完整 |
| MP4 打不开但 TS 能播 | 转封装时 stsd box 写错 | 见下方"TS vs MP4" |
多音轨清单
真实 HLS 清单(Netflix、Disney+ 那种)常把音频和视频分开放:
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac-128",NAME="English",DEFAULT=YES,URI="audio/eng_128.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=3000000,...,AUDIO="aac-128"
v_720.m3u8
如果你的下载器只抓 v_720.m3u8 然后拼分片,得到的就是无声视频。FlowPick 会自动同时拉音频轨道并合并,但前提是你选了包含音频的变体而不是只选视频流。多音轨清单的完整处理见 HLS 深度篇 — EXT-X-MEDIA。
TS vs MP4 输出格式
遇到 MP4 打不开的情况,先切到 TS 输出格式试试。原因:
- TS 分片是裸拼接,MPEG-TS 自带 codec 信息,几乎不会错。
- MP4 转封装需要解析
CODECS字段、写对stsdbox,遇到非标准 codec 标识会翻车。
TS 能播、MP4 不能播,多半是转封装问题,可以先用 TS 救急;要 MP4 的话用 FFmpeg 命令行手动转一次:
ffmpeg -i output.ts -c copy output.mp4
-c copy 不重新编码,秒级完成。
大文件 / 浏览器卡死
症状:下载大文件(>1GB)时浏览器卡顿、崩溃,或下载到一定比例直接中断。
这是写入策略问题,不是 HLS 本身的问题。FlowPick 三级写入策略:
| 策略 | 触发条件 | 上限 |
|---|---|---|
| File System Access API | Chrome/Edge 86+ | 无硬上限 |
| StreamSaver.js | 支持 Service Worker 的浏览器 | 无硬上限 |
| Blob 兜底 | 其他情况 | 1.5GB 硬限制 |
Blob 模式下超 1.5GB 会被直接拒绝。解决方案:
- 用 Chrome 86+ 或 Edge 86+,启用"选择保存目录"功能。
- Firefox 不支持 FSA,大文件走 StreamSaver 或 OPFS 临时存储(v1.1.1+ 优化)。
- 实在不行,降低并发数到 1-2,减少内存占用。
诊断工具速查
排查时按 F12 打开开发者工具,下面是几个最常用的检查点:
| 检查 | 面板 | 操作 |
|---|---|---|
| M3U8 有没有被请求 | Network | 过滤框输入 m3u8,刷新页面播放视频 |
| 分片请求状态码 | Network | 过滤框输入 .ts 或 .m4s,看 Status 列 |
| 请求带了什么头 | Network → 选中请求 → Headers | 看 Request Headers 的 Referer/Cookie |
| 解密报错 | Console | 搜 decrypt 或 AES |
| 合并报错 | Console | 搜 ffmpeg 或 wasm |
生成环境诊断报告
复杂问题可以在 Console 里跑下面这段,生成环境信息便于反馈:
const report = {
ua: navigator.userAgent,
fsa: 'showDirectoryPicker' in window,
sab: typeof SharedArrayBuffer !== 'undefined',
coi: self.crossOriginIsolated,
storage: await navigator.storage?.estimate().catch(() => null),
ts: new Date().toISOString(),
}
console.log(JSON.stringify(report, null, 2))
报告只含浏览器环境信息,不含任何浏览记录或个人数据。
什么时候真的下不了
FlowPick 适合公开的标准 HLS 播放列表。以下场景是设计上不支持,不是 bug:
- DRM 保护(Widevine / PlayReady / FairPlay / SAMPLE-AES)
- 需要破解登录或付费墙的内容
- 使用 MSE 自定义分片、不发原生 M3U8/MPD 的站点
- 私有协议、WebSocket、WebRTC 传输的媒体
碰到这些,正确的行为是报错而不是静默产出损坏文件——这也是 FlowPick 的态度。法律和技术底线详见 流媒体下载合法性指南。