用 watchEffect 实现视频进度实时同步,核心是监听 currentTime 变化并防抖上报,以服务端为唯一可信源,通过 WebSocket 广播更新,跳转前校验 readyState 并处理页面失焦、暂停等干扰场景。

用 watchEffect 实现视频播放进度实时同步,核心在于监听播放器的 currentTime 变化,并将该值高效、响应式地同步到其他端(如另一台设备、后台服务或同页面多个播放器)。关键不是“轮询”,而是依赖 Vue 的响应式系统自动触发更新,同时避免频繁写入带来的性能问题和时序错乱。
监听本地播放器并触发同步动作
watchEffect 适合在 currentTime 变化时立即执行副作用(如发请求、更新共享状态)。需注意:只在用户主动操作或播放自然进行时同步,避免静音/暂停时无效上报。
- 确保
videoRef已挂载且有currentTime属性,可在onMounted后启动监听 - 添加防抖逻辑(例如 300ms 内只同步最后一次),防止拖拽进度条时高频触发
- 区分“由用户触发”和“由同步逻辑触发”的更新,避免双向循环同步(可用标志位或来源标识过滤)
跨端同步需统一时间基准与状态源
多端同步不是单纯复制 currentTime,而要解决时钟漂移、网络延迟和状态竞争。建议以某一方(如服务端或主控端)为唯一可信源(SSOT),其余端只读不写。
- 本地播放器变更后,调用 API 将
{ roomId, userId, currentTime, timestamp }上报至服务端 - 其他端通过 WebSocket 或长轮询接收服务端广播的最新进度,再用
videoRef.currentTime = data.currentTime跳转 - 跳转前检查视频是否已加载就绪(
videoRef.readyState >= 1),否则缓存并等待canplay事件
处理常见干扰场景
实际使用中,静音、缓冲、全屏切换、页面失焦都可能影响同步准确性,需针对性处理:
- 页面不可见(
document.hidden === true)时暂停watchEffect副作用,恢复可见后再补同步一次 - 监听
pause和playing事件,暂停时不主动上报,但播放恢复时校准一次进度(对比服务端最新值) - 拖拽结束(
seeked)后强制同步,比仅依赖timeupdate更可靠
Vue 组件内典型代码结构
以下为精简可落地的组合式 API 示例(假设使用 Pinia 管理共享状态):
const videoRef = ref(null)
const { currentProgress, syncProgress } = useVideoSyncStore() // 共享进度状态
watchEffect((onInvalidate) => {
if (!videoRef.value) return
const handleTimeUpdate = _.debounce(() => {
const time = videoRef.value.currentTime
// 仅当本地是控制方且非静默跳转时才上报
if (isMaster.value && !isSyncingFromRemote.value) {
syncProgress(time) // 触发 API 上报
}
}, 300)
const video = videoRef.value
video.addEventListener('timeupdate', handleTimeUpdate)
video.addEventListener('seeked', () => {
syncProgress(video.currentTime)
})
onInvalidate(() => {
video.removeEventListener('timeupdate', handleTimeUpdate)
video.removeEventListener('seeked', () => {})
})
})
// 接收远程同步(例如 WebSocket 回调)
watch(currentProgress, (newTime) => {
if (videoRef.value && isSyncingFromRemote.value) {
videoRef.value.currentTime = newTime
}
})










