Blog

背景视频的双缓冲切换系统

前端视频性能动画

问题

网站首页用全屏视频做背景,需要在多个视频之间切换(比如顺序播放、用户点击随机跳转)。直接改 srcplay(),会因为加载延迟出现黑屏闪烁——短则半秒,长则几秒。

这不是网速问题。哪怕视频文件在本地,浏览器解码也需要时间。

双缓冲

借鉴图形学里双缓冲(Double Buffering)的思路,用 4 个 元素分成两组:

- 活跃组 — 当前正在播放的,用户看得见(opacity: 1)
- 空闲组 — 提前加载好下一个视频,藏起来(opacity: 0)

需要切换时,不是去改活跃组的 src,而是把已经预加载好的空闲组翻上来

具体结构:横屏和竖屏各需要一套(靠 CSS @media (orientation) 控制显示),所以最终是 L1/P1(活跃) + L2/P2(空闲) 共 4 个 video 元素。但每次切换只操作当前朝向的那一对。

切换流程

用户触发切换(上/下一个,或随机跳转)
  ↓
switching 加锁(防止连点发起多次切换)
  ↓
取当前朝向的空闲 video,设 src 为新视频
  ↓
监听 canplaythrough 事件(浏览器告诉你可以流畅播放了)
  ↓
翻转 activeBuf(活跃↔空闲),CSS opacity 0.6s 过渡
  ↓
700ms 后(等交叉淡入完成),往旧活跃组(现在变空闲了)预加载 N+1
  ↓
switching 解锁

关键细节:

- 只等 canplaythrough,不等 loadeddataloadeddata 只有一帧就触发,播放会卡。canplaythrough 确保缓冲够了才开始过渡。
- 预加载完立刻 .pause()。否则 autoplay 属性会让它在 opacity: 0 时偷偷播,切过去时已经播到半路了。
- 8 秒超时兜底。如果 canplaythrough 一直不来(极少发生),超时后强行翻转,不卡住。
- 切换后 700ms 才往旧缓冲写新 src。如果立即写入,交叉淡入期间可能漏出旧缓冲的画面。

单朝向加载

切换时只给当前屏幕朝向(横屏或竖屏)的 video 设 src。另一朝向的保持空 src。这样每次切换只请求一个视频文件,减少 50% 的 HTTP 请求。用户转屏幕后,后续切换自动适配新朝向。

但有个坑:同一缓冲区对里的另一个朝向如果不清理,旋转屏幕时会露出残留内容。所以每次给当前朝向设 src 后,要把同一对的另一朝向 src 清空并调 .load() 强制释放。

代际守卫

用户可以在"动漫模式"和"现实模式"之间切换,切换时视频列表和播放逻辑同时变化。问题出在 setTimeout 上——模式切换后,旧模式发出的延迟回调可能在几百毫秒后才执行,此时 video 元素已经被新模式改了。

解决方案是引入一个 switchGen 计数器:每次模式切换自增,所有 timeout 和回调在执行前检查当前代际是否匹配。不匹配就丢弃,防止旧回调覆盖新状态。

微信 X5 兼容

微信内置浏览器的 X5 内核禁止 autoplay,连 video.play() 都被拦截,除非有用户手势。解决方式是:引导页的"进入"按钮点击作为一次性用户交互解锁,在这个点击事件里对所有 video 元素调 play(),之后页面内的自动播放就不再被拦截。

---

这个系统的核心思路就是用空间换时间——多放几个 video 元素在 DOM 里,把加载动作提前到用户看见之前完成。复杂点全在并发控制和状态管理上。