HLS 在手机能播放,投到电视却失败:投屏故障排查指南
从发送端、接收端、CORS、Range 请求、编解码器、鉴权和直播窗口等环节,诊断 HLS 本机可播但投屏失败的问题。
一条 HLS 流可能在手机或电脑上播放正常,连接电视后却在几秒内停止。这不能证明电视的 Wi-Fi 信号弱,也不能证明播放列表适用于所有设备。在许多投屏系统中,远端接收器会自行加载媒体;发送端已经成功的请求和解码能力,不再代表完整的播放链路。
本指南把发送端控制、接收端网络、媒体支持、鉴权和输出行为拆开检查。请只测试自己拥有或获准检查的流。分享日志前,务必删除签名查询参数、Cookie、设备标识、IP 地址和账户信息。
*验证方法 — 2026 年 9 月 21 日:*我们使用 Node.js 24.12.0 运行了一个只监听本机回环地址的 HTTP 测试服务,并构造了两个请求来源。服务器允许 https://sender.example.test,但没有为 https://receiver.example.test 返回 Access-Control-Allow-Origin。两个来源都收到了播放列表字节,以及对四字节 Range 请求的 206 响应;只有发送端得到了匹配的 CORS 响应头。这项测试只验证测试服务中的 HTTP 响应头差异和 Range 响应。Node 的 fetch 不执行浏览器的 CORS 限制。我们没有测试 Chromecast、Apple TV、AirPlay 接收器、智能电视、DRM 系统或真实媒体解码器。下文与设备有关的内容来自文档核对。测试脚本位于 scripts/test-cast-hls-origin-headers.cjs。
先确认播放任务转移到了哪里
“投屏”可能指几种完全不同的架构:浏览器标签页镜像、系统把媒体 URL 交给接收器,或者应用与自己的接收端程序通信。因此,电视可能只是在显示发送端渲染的画面,也可能正在运行一个独立播放器,由它自行发起 HLS 请求。
| 观察结果 | 可能说明什么 | 不能证明什么 |
|---|---|---|
| 电视接管后,镜像标签页仍继续播放 | 页面可能仍由发送端渲染 | 接收器能独立加载 HLS URL |
| 手机变成遥控器 | 媒体播放可能已经交给接收器 | 接收器拿到了相同的 Cookie 或鉴权上下文 |
| 电视显示标题或封面后退出 | 控制消息和部分元数据已经到达 | 播放列表、分段、编解码器或许可证已成功处理 |
| 电视失败后,本机仍能继续播放 | 当前发送端链路正常 | 接收端网络、响应头、解码器或直播位置正常 |
| 其他视频都能投屏 | 基本的设备发现和输出链路可用 | 当前 HLS 内容兼容该接收器 |
记录当前使用的是屏幕镜像、浏览器 Remote Playback、Google Cast、AirPlay、智能电视应用还是 HDMI。不要把这些路径的结果统称为“投屏结果”。MDN 将 Web Remote Playback API 标为兼容性有限,因此不能因为操作系统其他位置出现 Cast 或 AirPlay 按钮,就推断网页 API 一定可用。
分别收集发送端和接收端证据
发送端浏览器的开发者工具通常只显示发送端页面发出的请求,未必包含接收器请求。交接后发送端立即停止下载分段,可能是正常现象;如果没有接收端记录,发送端的 Network 面板无法告诉你接收器的哪个请求失败了。
对于自己控制的应用,应获取接收端日志,或查询交接时段的 CDN 请求记录。使用不会泄露隐私的会话 ID 或请求 ID 进行关联,不要公开媒体 URL。建议记录:
- 准确的交接时间和时区。
- 发送设备、浏览器或应用版本、网络,以及本机播放状态。
- 接收器型号、固件或接收端版本、网络和显示输出设备。
- 重定向后的最终播放列表 URL,以及接收端发出的第一个请求。
- 第一个失败请求的状态码、内容类型、Range 响应头、缓存结果和最终 URL。
- 选中的变体、编解码器、音频组、字幕轨和当前媒体位置。
- 控制状态变成了播放、缓冲、空闲还是错误。
如果无法获取接收端日志,服务器或 CDN 日志通常是确认电视是否请求过播放列表的第一份可靠证据。我们的开发者工具请求检查指南仍适用于发送端,但不能代替接收端追踪。
检查接收端链路的 CORS
Google 的 Web Receiver 文档说明,流媒体协议使用受 CORS 约束的异步请求,内容服务器决定媒体可以在哪些位置被使用。Google 还指出,自适应媒体和媒体轨需要正确的 CORS 响应头。因此,只允许包含 Cast 按钮的网站来源往往不够,因为接收端应用可能使用另一个来源。
我们的测试说明了一个常见误区:两个构造来源都拿到了清单的 HTTP 200,但接收端来源没有得到匹配的 Access-Control-Allow-Origin。服务器日志里出现 200,并不能证明接收端 JavaScript 可以读取响应。真正的接收器平台决定是否以及如何执行 CORS;Node 测试没有模拟这层限制。
要检查整条资源链,而不只是入口清单:
- 多变体播放列表和媒体播放列表。
- 初始化分段和媒体分段。
- 备用音频、字幕播放列表及其分段。
- 加密密钥和允许访问的许可证端点。
- 重定向后的目标地址,而不只是原始主机名。
- Range 请求,以及适用情况下的预检请求响应。
不要安装开放的公共代理,也不要把放宽生产访问控制当成长期修复。应在自己控制的基础设施上配置准确的来源、凭据、方法和请求头,再用真实接收器复测。
比较鉴权,但不要复制秘密信息
本机播放可能依赖浏览器 Cookie、Authorization 请求头、Referrer 或短期签名 URL。远端接收器不会自动继承发送端会话的全部信息。主播放列表可能能够加载,而子播放列表、密钥或分段却使用了过期或不完整的鉴权。
沿着接收器的第一个请求检查完整的播放列表链。比较各请求包含哪些查询参数和请求头、签名有效期有多长,以及重定向是否删除或改变了鉴权。如果流先播放片刻再停止,可以把令牌过期时间与失败时间进行比较,但不能只凭时间接近就断定是过期造成的。
不要把仍然有效的签名 URL 粘贴到公开问题中,也不要硬编码到网页。向服务负责人提供脱敏路径、状态码、有效期长度和请求 ID 即可。
核对 Range 行为和响应类型
即使本机播放只请求完整对象,接收器也可能使用字节 Range 请求。我们的测试对受控分段请求返回了 206 Partial Content、Content-Range 和 Accept-Ranges,但没有验证媒体容器或任何设备行为。
对于获准测试的真实媒体,应确认 Range 请求得到内部一致的响应:适用时状态为 206,Content-Range 有效,总长度正确,返回的是请求的字节,内容类型也应匹配媒体。注意 CDN 或鉴权层是否忽略、删除或拒绝 Range。某些资源和客户端路径返回 200 也可能合理,因此要结合实际请求与平台文档判断,不能把单一规则套到所有情况。
还要确认 .m3u8 地址返回以 #EXTM3U 开头的 HLS 文本,而不是 HTML 登录页或 CDN 验证页。我们的完整 HLS 播放故障排查指南说明了为什么状态成功也可能返回错误正文。
按接收器能力匹配编解码器,而不是按手机判断
手机与电视的硬件解码器、支持的 Profile、声道布局、最高分辨率、帧率、HDR 能力和容器限制都可能不同。发送端能够解码,不代表接收端一定兼容。
Google 发布了各 Cast 设备的媒体支持表,并为兼容的接收端应用提供 CastReceiverContext.canDisplayType()。其当前文档列出的编解码器和分辨率限制因设备而异;例如,文档明确说明 Transport Stream 容器不支持 HEVC。应查看当前目标设备表,不要只凭“4K”标签或一次手机播放成功做判断。
检查多变体播放列表中的 CODECS、RESOLUTION、FRAME-RATE、音频声道以及真实媒体内容。如果产品要求与设备支持情况允许,可以提供保守的 H.264/AAC 版本,但不能只是把不兼容媒体的标签改掉。遇到备用音频问题,可使用HLS 音轨故障排查指南。
把直播交接视为时间窗口问题
接收器加入直播时,会根据自己的播放列表快照和直播位置开始播放。如果播放列表缓存过久、分段在真正可用前就被公布,或接收器从即将消失的窗口边缘开始,本机播放仍可能正常,而接收端会失败。
保存连续两次接收端媒体播放列表响应,比较 #EXT-X-MEDIA-SEQUENCE、最新分段、缓存响应头、请求时间,以及接收端选中的第一个分段。确认该分段在交接时是否仍可用。不要根据手机仍在播放,就推断接收器也已经恢复;两个客户端可能处在不同的直播位置。
可用我们的陈旧播放列表与缺失分段诊断指南,区分没有更新的直播快照与新公布后却返回 404 的媒体对象。对于低延迟 HLS,应确认接收端路径支持内容实际使用的功能,不要假定所有 HLS 模式完全相同。
检查输出和控制状态
如果请求与解码看起来都正常,还应确认播放是否只是无声、输出到其他设备、被暂停,或受 HDMI、HDCP、显示模式影响。记录接收器音量、静音状态、音频路由、显示设备能力,以及视频时间是否继续推进。
发送端按钮显示“已连接”,只能证明控制会话建立,不能证明媒体播放成功。电视上的封面也可能在任何分段解码之前由元数据生成。应记录接收端媒体状态变化和第一个有意义的错误,不要把连接状态当成播放状态。
DRM 又增加了一层边界。受保护的流需要接收器支持的 DRM 路径、许可证交换、凭据和输出保护。通用接收器无法凭空生成许可证,也不能绕过保护。只能在官方授权的应用流程中测试。
建立可复现的设备矩阵
逐项测试受支持的组合,每次不要同时改变多个变量:
| 维度 | 至少应记录的内容 |
|---|---|
| 发送端 | 设备、操作系统、浏览器或应用版本、本机播放结果 |
| 接收器 | 准确型号、固件或接收端版本、有线或无线网络 |
| 媒体内容 | VOD、直播或低延迟、选中变体、编解码器、DRM 状态 |
| 交接位置 | 播放前、稳定播放中、拖动后、网络恢复后 |
| 结果 | 首帧耗时、接收端第一个失败请求、最终媒体状态 |
| 对照 | 同一发送端和接收端上的已知可用授权流 |
至少测试一个低码率和一个高码率版本、备用音频和字幕、拖动、暂停与继续,以及直播窗口推进后的交接。把桌面浏览器缩到电视尺寸,并不等于测试了接收器。无法获得的硬件或不支持的平台,应明确标记为未测试。
围绕接收端的第一个失败点写报告
一份可执行的报告可以这样写:“本机继续播放。18:42:11,X 型号接收器请求媒体播放列表并收到 200;随后第一个分段请求在重定向后返回 403。脱敏请求 ID 为 Y。”这种描述可以明确责任范围和下一步检查项。
报告应包含投屏架构、发送端与接收端版本、接收端第一个资源的类型和状态、CORS 与 Range 响应头、选中的编解码器或变体、鉴权有效期、直播序列号和媒体状态变化。不要包含原始凭据和私有 URL。
一手参考资料
- Google Cast:自定义 Web Receiver 与 CORS
- Google Cast:支持的媒体格式和设备编解码器限制
- Google Cast:Web Receiver 流媒体协议
- Google Cast:媒体轨的 CORS 要求
- MDN:Remote Playback API
- RFC 8216:HTTP Live Streaming
当 HLS 在本机正常、电视却无法播放时,先确认究竟是谁在请求媒体。再按顺序检查接收端 CORS、鉴权、Range 行为、编解码器支持、直播时间、解码和输出。发送端的一次成功请求,不能证明另一条接收端链路也正常。