Skip to content

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(回落原生)
谁在解析JavaScriptJavaScript操作系统
受 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 就是必选项,不是可选项。


相关