network.connection.change 事件不完全可靠,因仅在 effectiveType 或 downlink 发生显著变化时触发,无法捕获渐进式带宽下降;需配合 3–5 秒定时轮询、三级阈值与防抖逻辑实现精准自适应清晰度切换。
不完全可靠。浏览器只在 effectiveType 或 downlink 发生「显著变化」时才触发 change 事件,比如从 '4g' 切到 'slow-2g',但若 downlink 从 4.8 → 4.2 Mbps,它大概率不会触发——这恰恰是视频卡顿开始的临界点。
所以不能只依赖 change 事件做清晰度切换。真实项目中必须配合定时轮询(例如每 3–5 秒)读取 navigator.connection.downlink 和 navigator.connection.effectiveType,再结合当前播放状态判断是否需要干预。
connection,老版本需 fallback 到 navigator.onLine + 历史加载耗时估算downlink 是以 Mbps 为单位的浮点数,但它不是实时网速,而是浏览器基于历史 RTT、丢包、协议栈行为的「保守估算」。Chrome 给出的值通常比真实测速低 20%–40%,且在弱网下抖动剧烈(比如 0.8 → 1.3 → 0.6 连续跳变)。
直接拿 downlink > 5 切 1080p 很容易误判。更稳妥的做法是加缓冲区间和防抖:
downlink >= 8 → 高清(1080p),3 → 标清(720p),<code>downlink → 流畅(480p)
video.buffered.length > 0 && video.buffered.end(0) - video.currentTime ,否则延迟切换
不能把 navigator.connection 直接塞进响应式 store 或 ref——它本身不是响应式对象,且属性不可监听。正确做法是封装一个「可订阅的状态源」,内部手动触发更新。
以 Svelte 为例,推荐用 derived + 定时器组合:
import { derived, get } from 'svelte/store';import { networkStatus } from './stores'; // 自定义 store<p>const connection = navigator.connection;const pollInterval = 3000;</p><p>const networkSignal = derived(networkStatus, ($status) => {if (!connection) return { effectiveType: '4g', downlink: 5 };return {effectiveType: connection.effectiveType,downlink: connection.downlink,saveData: connection.saveData};});</p><p>// 启动轮询(注意:需在组件 onMount 中调用,避免 SSR 报错)function startNetworkPolling() {const id = setInterval(() => {networkStatus.set({ /<em> 重新读取并更新 </em>/ });}, pollInterval);return () => clearInterval(id);}
Vue 和 React 同理:不要 watch navigator.connection,而是 watch 你自己的封装状态变量,并在 setupEffect / useEffect 内管理轮询生命周期。
一是 video.src 赋值后没重置 currentTime,导致新视频从头开始播;二是没处理 loadedmetadata 就调 play(),引发 Promise reject(尤其在 iOS Safari 上)。
src 更新后监听 loadedmetadata 事件,再恢复播放位置:video.currentTime = prevTime
video.muted = true; video.play(),否则 iOS 会拒绝播放srcObject(如果有 WebRTC 流混用场景),防止内存泄漏真正难的不是“怎么切”,而是“什么时候不该切”——比如用户正拖拽进度条、刚点暂停、或视频处于 rebuffering 状态。这些边界条件不处理,自动清晰度反而比手动还卡。