无法真正无缝切换视频是因为单个video元素改src并监听ended事件存在黑屏、跳帧及ios报错问题,必须采用多实例+主动时间判断+视觉过渡三者配合。

直接用单个 <video></video> 元素反复改 src 并监听 ended 事件,无法实现真正无缝切换——黑屏、跳帧、iOS 报错是常态。必须用多实例 + 主动时间判断 + 视觉过渡三者配合。
为什么不能依赖 ended 事件做切换
浏览器对 ended 的触发时机完全不一致:有的卡在音频末尾,有的因 B 帧解码延迟几十毫秒才发,HLS 流还可能根本没触发。更糟的是,ended 在缓冲失败或网络中断时也会误触发,直接导致 DOMException: The element has no supported sources。
真正可控的做法是监听 timeupdate,主动判断播放进度:
- 设置阈值(如
0.1秒),当video.currentTime >= video.duration - 0.1时启动切换 - 切换前确保
video.readyState === 4(HAVE_ENOUGH_DATA),否则play()会静默失败 - 强制设
video.playbackRate = 1,避免变速播放干扰时间计算
复用 DOM 元素比反复创建更稳
每次切换都 document.createElement('video') 或清空 innerHTML,会销毁硬件解码上下文,首帧延迟明显,音画不同步风险高。
推荐只维护一个 <video></video> 元素,但需严格重置状态:
- 切换前调用
video.pause()和video.currentTime = 0 - 用
video.load()清除旧缓冲,但别等canplaythrough—— 它在低带宽或 HLS 下可能永不触发 -
video.src = nextUrl后立刻video.play().catch(e => console.warn('play failed:', e)),现代浏览器在用户手势链路内基本能成功
双视频视觉过渡才是“无缝”的关键
纯 JS 切换做不到毫秒级衔接。人眼可察觉卡顿的阈值约 50ms,必须靠 CSS 控制两个 <video></video> 元素的显隐来欺骗视觉:
- 准备
videoA(当前播放)和videoB(后台预加载),两者尺寸、poster、宽高比必须完全一致,否则 DOM 重排会闪动 -
videoB在videoA进入最后 0.3 秒时就调用load(),并尝试play()(静音) - 当
videoA.currentTime >= videoA.duration - 0.05,立即videoA.style.display = 'none'、videoB.style.display = 'block'
iOS Safari 的静音限制必须绕过
iOS Safari 要求首次 play() 必须由用户手势触发,且后续所有 play() 调用必须在同一用户交互链路内,否则报 NotAllowedError: play() can only be initiated by user gesture。
唯一可靠解法:
- 页面加载后,第一次播放必须绑定在按钮
click或touchstart上(不能是setTimeout或load事件) - 之后的切换操作要复用这个手势链路:比如用户点击按钮触发第一次播放,后续所有
play()都应在该 click 回调的同步或 Promise 链中执行
复杂点不在代码量,而在时间阈值、readyState 检查、iOS 手势链路这三者的耦合——漏掉任意一个,无缝就会退化成肉眼可见的卡顿或白屏。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











