Skip to content

踩坑记录:HTTPS 页面下 xgplayer 拉流被 Mixed Content 屏蔽

更新: 5/1/2026 字数: 0 字 时长: 0 分钟

一、问题背景

最近在做一个基于 xgplayer 的直播播放页面。项目有几个比较特殊的约束:

  • 双版本共存:package.json 里同时装了 xgplayer@2.17.10 和通过 npm alias 装的 xgplayer-3(实际是 xgplayer@3.0.21-rc.10),历史业务绑在 v2,新功能想用 v3。
  • 直播插件 xgplayer-flv-live@3.0.0-alphatt.141 只兼容 v3,默认 import 'xgplayer' 会解析到 v2,所以先处理了一波补丁(用 patch-package 把它里面的 'xgplayer' 改成 'xgplayer-3')。
  • 页面运行在 HTTPS 环境(内部系统走的是公司统一的 HTTPS 域名)。

等插件这边跑通,进到真正播放环节,新的坑就来了。

二、问题现象与初始猜测

打开页面,视频区一片黑,DevTools Network 里看到直播流请求的状态赫然写着:

(已屏蔽: mixed-content)

请求根本没有发出去,不是超时、不是 CORS、不是 4xx/5xx,而是浏览器层直接拦截。

一开始我的判断有点跑偏:

  • 第一反应是"是不是 xgplayer-flv-live 的补丁没打好,import 路径错了导致拉流失败?"
  • 然后怀疑"是不是 CDN 那边证书/鉴权出问题?"
  • 又怀疑"是不是 v2/v3 冲突,把 flv 相关的 worker 加载坏了?"

但仔细看那个 "已屏蔽: mixed-content" 的字样,才意识到这是浏览器安全策略层面的问题,跟我 xgplayer 本身没啥关系——是协议不匹配

三、踩坑过程

尝试 1:怀疑是插件补丁问题

先回头检查 patch-package 生成的 patch,确认 xgplayer-flv-live 里所有 'xgplayer' 都改成了 'xgplayer-3',构建产物里也确认 v3 被正确引入。结论:和插件无关,排除。

尝试 2:认真看报错,定位到 Mixed Content

回到那一行 "已屏蔽: mixed-content",翻出浏览器的混合内容规则才想起来:

页面协议资源协议行为
HTTPSHTTPS / WSS放行
HTTPSHTTP(fetch/XHR/WS,主动混合)直接屏蔽
HTTPSHTTP(img/video src,被动混合)警告但可能加载
HTTP任意放行

HTTP-FLV 本质是 fetch 流式拉取,属于主动混合内容,浏览器不给"仍要加载"的选项,直接拍死。我的页面是 HTTPS,拉流地址是 http://xxx/live.flv,所以必挂。

尝试 3:想过但没走的几条路

  1. 把页面降级为 HTTP:只对本地调试友好,线上不可能,放弃。
  2. upgrade-insecure-requests CSP:能把 http:// 自动升级成 https://,但前提是源站 HTTPS 真的可用;而且对 ws:// 不生效。当时没 100% 确认 CDN 两侧都开好,先搁置。
  3. 自建 HTTPS 反代:带宽成本高,只适合应急,纯直播场景属于杀鸡用牛刀,pass。
  4. 推动直播服务方开通 HTTPS,然后前端写死 https://:最干净的根治方案,但涉及跨团队沟通 + 改代码,短期内没法立刻上。

尝试 4:最终命中的「零成本解法」

翻资料时想起协议相对 URL(Protocol-relative URL)这个老写法:把流地址里的 http: 去掉,只保留 //:

//cdn.xxx.com/live/stream.flv

浏览器会按当前页面协议自动补齐:HTTP 页面下发 HTTP 请求,HTTPS 页面下发 HTTPS 请求。试了一下,HTTPS 页面下正常播放,HTTP 入口也没挂——一行改动搞定。

能成功的隐含前提是:CDN 源站本来就同时开了 HTTP 和 HTTPS,只是我们代码里硬编码了 http:// 才触发 mixed-content。改成 // 后直接命中 HTTPS,问题消失。

四、多种解决方案对比

方案实现复杂度侵入性性能/资源可维护性风险
A. 源站升级 HTTPS,前端写死 https://中(要推动服务方)低(只改 URL)最优最好,显式清晰无,根治
B. 协议相对 URL //xxx极低(删两字符)极低最优一般(隐式依赖双栈)file:// 下失效;WS 不适用;HTTP 入口仍走明文
C. CSP upgrade-insecure-requests低(加一行 meta/header)最优源站必须真支持 HTTPS;对 ws:// 无效
D. 自建 HTTPS 反代高(要搭 Nginx/部署)高(基建)差(全量过代理,占带宽)单点故障、成本高
E. 页面降级 HTTP极低---线上不可接受,仅调试
F. Chrome 启动参数绕过极低---只对自己生效,不能给用户

关键几条:

  • A 是长期最优解,但依赖外部配合。
  • B 是当下最快解,一行改动,HTTPS/HTTP 双栈通吃,适合过渡期。
  • C 在源站 HTTPS 已稳定时是更显式的 B,但对 WebSocket 场景失效。
  • D/E/F 都是非生产级兜底,不做首选。

五、最终采用的方案

选了:协议相对 URL(方案 B)

把直播地址从:

diff
- http://cdn.xxx.com/live/stream.flv
+ //cdn.xxx.com/live/stream.flv

一次性替换掉代码里所有硬编码的 http:// 拉流地址。

关键实现要点

  1. 统一从配置/接口下发流地址,前端收到后先做一次规范化:
    js
    function normalizeStreamUrl(url) {
      return url.replace(/^https?:/, ''); // 保留 //host/path
    }
  2. WebSocket 场景单独处理(协议相对写法对 ws:// 无效):
    js
    const wsProto = location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${wsProto}//xxx.com/live`;
  3. 本地开发注意:如果直接 file:// 双击打开 HTML,//cdn.xxx.com 会被当成 file://cdn.xxx.com,必挂;务必用 dev-server 跑。
  4. 回归验证:分别在 HTTPS 线上域名 + HTTP 内网入口各过一遍,确认都能拉流。

为什么选它而不是 A

  • A 需要联系直播服务方开证书、配置、回归,周期至少几天,而我的需求是当天得让页面能播
  • B 是零成本、零风险(源站双栈已存在)、改动量最小的方案,完美适配"救火"节奏。
  • 长期还是会推动 A,但 B 能作为过渡期的稳定兜底,两条路不冲突。

六、经验总结与踩坑教训

一开始哪里判断失误

  • 看到直播播不出来,下意识往业务代码/插件/CDN 上查,忽略了浏览器那行字面意思非常清楚的 "mixed-content"。
  • 对"主动混合内容"和"被动混合内容"的区别没概念,以为 <video src="http://..."> 能加载,就觉得 fetch 流也能加载——其实规则完全不同。

可迁移的经验

  1. "已屏蔽"类网络错误先看浏览器安全策略。Network 面板里凡是状态不是 HTTP 状态码而是中文括号的(已屏蔽/blocked/CORS error/...),80% 是浏览器层的拦截,不是业务错,先查策略再查代码。
  2. Mixed Content 的两档策略要记牢:主动混合(fetch/XHR/WS/script)无条件拦截,被动混合(img/video/audio)只警告。直播流、WebSocket、接口全属前者。
  3. 协议相对 URL //host/path 是过渡期神技,但有三个坑:file:// 不能用、WebSocket 不支持、HTTP 入口下仍走明文。
  4. 能根治就别只打补丁:源站 HTTPS 化 + 显式 https:// 才是长期方案,Google 早年就不推荐 protocol-relative 了。B 方案只是抢时间的过渡。
  5. 多版本包共存 + 插件补丁这种组合要格外小心,出问题时先用排除法(确认补丁、版本、打包产物都对),再去看更上层的浏览器/网络问题,否则很容易被表象带偏。