低功耗模式下video自动播放必然失败,需通过“元数据就绪→play()→catch NotAllowedError”三步闭环判断;canPlayType()仅反映解码能力,不体现系统策略限制,不可作为播放可行性依据。

iOS 低功耗模式下,video 元素会被系统静默禁用自动播放,哪怕加了 muted、autoplay、playsinline,play() 调用也会直接抛出 NotAllowedError,且不触发 canPlayType() 可检测的“不支持”状态——它看起来像支持,实则拒绝执行。不能靠一次判断就放弃,得用“元数据就绪 + 尝试播放 + 捕获失败”三步闭环。
为什么 canPlayType() 在低功耗模式下失效
canPlayType() 只反映解码能力,不反映系统策略限制。iOS 低功耗模式会拦截 play() 调用,但不会修改 video 元素的 readyState 或 canPlayType() 返回值。你可能看到 canPlayType('video/mp4') 返回 "probably",readyState 达到 HAVE_METADATA,但 play().catch() 仍会立即 reject。
- 别把
canPlayType()当作播放可行性判断依据,它只管“能不能解”,不管“让不让播” - 低功耗模式下,
play()失败是静默的,不会改变元素视觉状态,也不会触发error事件,只能靠catch捕获 - 同一台设备,普通模式能播,低功耗模式不能播——所以 UA 或机型检测完全无效
如何用 play().catch() 真实确认播放失败
必须在 readyState >= HTMLMediaElement.HAVE_METADATA 后调用 play(),并显式处理 promise rejection。仅监听 error 事件或检查 paused 状态不可靠,因为低功耗模式下 play() 不会进入播放流程,也不会改变 paused 值。
- 在
loadedmetadata或轮询readyState达标后触发play() - 写法必须是:
const playPromise = video.play(); playPromise.catch(e => { /* 降级逻辑 */ }); - 常见错误:漏掉
catch、把play()包在try/catch里(它返回 promise,不是同步抛错) - 失败原因包括:
NotAllowedError(低功耗/无手势)、AbortError(加载中断)、NotSupportedError(格式不支持),统一按降级处理即可
静态图降级时怎么避免布局跳动和重排
视频和图片必须共用同一套定位与缩放规则,且切换时不能触发布局重排(display: none 会触发)。关键是用 visibility: hidden + pointer-events: none 隐藏视频,再用绝对定位叠层展示图片。
立即学习“前端免费学习笔记(深入)”;
- 两者都设
position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; - 初始状态:视频
visibility: visible,图片visibility: hidden - 降级时只改
video.style.visibility = 'hidden'和img.style.visibility = 'visible',不改display - 图片用
loading="lazy",但首次降级需确保它已开始加载;可提前在document.head插入<link rel="preload" as="image" href="fallback.jpg"> - 图片格式优先选
.webp,fallback 到.jpg;避免.png(透明通道在深色背景上易发灰)
用户没交互时要不要一直重试播放
不要。iOS 要求播放必须由真实用户手势触发,轮询或定时器调用 play() 无效,反而增加 CPU 占用和电池消耗。一旦首次 play().catch() 失败,就应直接降级,不再尝试。
- 监听一次
touchstart或click(注意 iOS 16+ 对click触发更严格,touchstart更稳妥) - 仅在该事件回调中调用
play(),且只调一次 - 若用户始终未触发,就默认使用静态图;别用
setTimeout模拟点击或轮询document.hasFocus() - 降级后,即使后续用户点击,也不再尝试恢复视频——避免页面逻辑混乱
最易被忽略的点是:低功耗模式下,play() 失败不报错也不留痕迹,开发者容易误判为“视频还没加载好”,于是加延迟重试,结果只是白等。真正有效的判断,永远来自 play().catch() 的 promise rejection,而不是任何状态属性或定时猜测。


















