Appearance
iOS 为什么这么特殊
几乎所有"在我电脑上好好的,iPhone 上就不行"的播放问题,都能追到同一个根因。
根因:iOS 全系没有标准 MSE
MSE(Media Source Extensions)是一个浏览器 API,它允许 JavaScript 自己解析流、把数据段喂给 <video>:
没有 MSE: <video src="a.mp4"> → 浏览器全权负责
有 MSE: JS 解析流 → 生成数据段 → 喂给 <video>hls.js 和 flv.js 都建立在 MSE 之上。 没有 MSE,它们根本起不来。
而 iOS 上所有浏览器都是 WebKit 内核——App Store 规则要求如此。 你在 iPhone 上装的 Chrome、Firefox、Edge,内核全是 Safari 的 WebKit, 只是换了个壳。所以:
换浏览器解决不了 iOS 的播放问题
"让用户装个 Chrome 试试"在 iOS 上是无效建议。内核是同一个。
推论一:iOS 播不了 FLV
两条路都堵死:
走 flv.js → 需要 MSE → ❌ 没有
走原生播放 → Safari 的 <video> 不认 FLV 容器 → ❌这不是"支持得不好",是物理上不可能。传单一 FLV 源给 iOS 用户, 结果一定是播不了。
SDK 的处置:SourceRouter 检测到 iOS 会强制选 HLS;如果你只传了 FLV, 它直接抛 E_MEDIA_NOT_SUPPORTED ——明确失败,而不是黑屏让你查半天。
严格说不是"永远不可能"
iOS 17.1+ 引入了 ManagedMediaSource(MSE 的受控变体),平台层面已经开了口子。 但本 SDK 用的 flv.js 已停止维护、没有迁移到 MMS(它只认 window.MediaSource), 所以当前判定仍然成立。
将来若评估换成 mpegts.js 这类支持 MMS 的内核,这个结论需要重新讨论——要走 ADR。
推论二:iOS 上 HLS 是唯一选项(但不再走原生)
Safari 的 <video> 原生支持 HLS,所以 HLS 在 iOS 上一定能播 —— 这条没变。
变的是我们走不走那条路:
这一节在 ADR-056 之后被改写过
本 SDK 从前确实让 iOS / Safari 走原生 HLS。ADR-056 把判据 从「是不是 iOS / Safari」改成了「有没有 MSE / ManagedMediaSource」 —— iOS 17.1 起有 ManagedMediaSource,于是 iOS ≥ 17.1 现在走 hls.js。
改的理由是:走原生时 setQuality / qualitychange / ready.quality 在 iOS 上 静默失效,而清晰度控制是契约承诺。代价是 iOS 上失去 AirPlay。
| 非 iOS(走 hls.js) | iOS ≥ 17.1(走 hls.js,经 MMS) | iOS < 17.1(回落原生) | |
|---|---|---|---|
| 谁在解析 | JavaScript | JavaScript | 操作系统 |
| 受 CORS 约束 | ✅ 严格 | ✅ 严格 | ❌ 不走 CORS |
| 能读到清晰度档位 | ✅ | ✅ 能(真机实测 2 档) | ❌ 读不到 |
| 能拦截请求加 header(用本 SDK) | ❌ | ❌ | ❌ |
| AirPlay 投屏 | — | ❌ 失去 | ✅ 平台白送 |
「能拦截请求加 header」整列都是 ❌
这列从前写着「非 iOS ✅」。hls.js 这个库确实有 xhrSetup / 自定义 loader,但本 SDK 不暴露它 —— source.onBeforeRequest 是个零消费点的幽灵字段,已由 ADR-057 废弃。 所以从消费方视角,鉴权在所有平台上都只有签名 URL 一条路(ADR-022)。
推论三:清晰度档位 —— iOS ≥ 17.1 能读到了
清晰度菜单的数据来自 hls.js 的 levels。只要走的是 hls.js 就读得到, iOS ≥ 17.1 现在也在这条路上(ADR-056)。
仍然读不到的只剩 iOS < 17.1 —— 既没有 MSE 也没有 ManagedMediaSource, 只能回落原生,那时没有 hls.js 实例,ready 事件的 quality 是空数组, 清晰度由系统 ABR 全权接管,你既读不到也控制不了。
实践影响没变:清晰度菜单仍然必须处理空档位 —— 除了 iOS < 17.1, 单码率流本来也只有一档。不加判断的话用户会看到一个空菜单:
vue
<QualityMenu v-if="levels.length >= 2" ... />参考实现 TeamVideoPlayer.vue 里就是这么写的。
推论四:iOS 上没法给视频请求加 header
这是本 SDK 移除 source.headers 的直接原因(ADR-022)。
Safari 播 HLS 时,.m3u8 和 .ts 的请求由系统底层发出, JS 层根本拦不到,没有任何办法给它们附加 header。
如果 SDK 保留 headers 能力,结果就是:
Chrome 上测试:✅ 一切正常
上线后 iPhone:❌ 全挂与其提供一个"在最重要的平台上失效"的能力,不如强制所有人走签名 URL—— 这是唯一四端一致的方案。详见 鉴权与签名过期。
推论五:iOS 后台切回容易挂
iOS 对后台标签页的资源回收非常激进。播放器切到后台再回来, 可能出现挂死、音频丢失(坑 #5 / #24)。
VisibilityPlugin 的处置是重建 player 而不是简单 pause/resume—— 后者在 iOS 上会崩。
还有一个:iOS 26 微信全屏会崩
调 <video> 的原生 webkitEnterFullscreen() 在 iOS 微信里直接崩溃/白屏(坑 #7)。
FullscreenGuardPlugin 只在这个环境把该方法替换成 no-op,阻断会崩的路径, 全屏改走 CSS 伪全屏兜底。destroy 时还原,不留副作用。
总结一张表
| 你以为 | iOS 实际 |
|---|---|
| 换个浏览器就好了 | 内核都是 WebKit,没用 |
| FLV 支持得不好 | 物理上播不了 |
| 加个 header 做鉴权 | 拦不到请求,只能用签名 URL |
| 读清晰度列表做菜单 | iOS ≥ 17.1 读得到(ADR-056);iOS < 17.1 才是空数组 |
| 后台切回 resume 一下 | 会崩,必须重建 |
所以只要你的用户里有 iPhone,HLS 就是必选项,不是可选项。