M3U8 跨域(CORS)排障

浏览器播放 HLS 时,会分别请求 m3u8 清单和 ts/fmp4 分片。任意一个请求跨域未放行,都可能导致播放失败。很多人误以为“主清单能打开就没问题”,实际上播放器后续还会继续请求子清单、分片、密钥文件,任何一步被浏览器拦截都会表现为播放失败。CORS 问题的核心不是网络不可达,而是浏览器安全策略拒绝前端读取跨域响应。

一、典型现象与误区

最常见现象是控制台出现跨域错误,或 Network 里请求状态看起来是 200,但播放器仍报错。另一个高频误区是只给 m3u8 加跨域头,没有给 ts/fmp4 或 key 文件加同样策略,导致“首片可播、后续失败”。如果你启用了自定义请求头、Cookie 或 Authorization,还可能触发预检请求(OPTIONS),这时服务端必须正确响应预检,否则正式请求不会发送。

二、排查顺序(建议固定)

先确认清单和分片是否同源,不同源就必须统一跨域策略。再检查响应头是否完整返回,尤其是 Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers。如果前端需要带凭证,不能使用通配符来源,必须返回明确域名并带 Access-Control-Allow-Credentials: true。第三步检查 CDN 是否覆盖了源站响应头,很多问题出在边缘节点模板没有同步更新。

三、Nginx 基础示例

location ~* \.(m3u8|ts)$ {
  add_header Access-Control-Allow-Origin * always;
  add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
  add_header Access-Control-Allow-Headers "*" always;
}

四、生产环境建议

生产环境不建议长期使用全放开策略,最好按业务域名白名单返回来源。若涉及鉴权,清单、分片、密钥三类资源应使用一致的跨域与鉴权组合,避免规则分裂。部署完成后,务必在浏览器开发者工具中抽查主清单、子清单、分片、密钥四类请求的响应头。最后把 CORS 校验纳入上线前检查项,这样能在发布阶段就拦截大量"上线后才发现无法播放"的问题。

五、完整 Nginx 配置示例

以下是一个更完整的 Nginx 配置,包含了对预检请求的处理、对凭证请求的支持,以及针对不同文件类型的缓存策略:

server {
    listen 80;
    server_name stream.example.com;
    root /var/www/stream;

    # 处理预检请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $http_origin always;
        add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Range, Authorization' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400 always;
        return 204;
    }

    location ~* \\.m3u8$ {
        add_header 'Access-Control-Allow-Origin' $http_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Cache-Control' 'no-cache' always;
        try_files $uri =404;
    }

    location ~* \\.ts$ {
        add_header 'Access-Control-Allow-Origin' $http_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Cache-Control' 'public, max-age=600' always;
        try_files $uri =404;
    }
}

六、实际排查案例

某开发团队反馈,他们的直播流在 Chrome 中无法播放,但在 Safari 中正常。排查过程如下:

这个案例说明,跨域问题往往具有隐蔽性,不同浏览器的表现可能完全不同。排查时要结合浏览器开发者工具,逐一验证每个资源的响应头。

七、常见问题速查