You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Seeking与Seeked事件同时触发,如何获取视频拖动起止时间?

问题描述

我正在开发一个项目,需要记录用户观看视频时的所有操作,比如停止位置、视频观看时长、拖动(seek)的开始与结束时间。大部分功能已经实现,但遇到了问题:无法正确获取拖动的起止时间,怀疑是seeking和seeked事件的触发逻辑没处理好。不想使用任何插件或库,以下是我的代码,希望有人指出遗漏的地方:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <video controls src="sample.mp4"></video>
</body>
</html>
const sendRequest = (operation, data = false)=>{
    const formData = new FormData();
    formData.append('operation', operation);
    if(data !== false) formData.append('data', JSON.stringify(data));
    return fetch('https://www.example.com/operations.php', {method: 'post', body: formData});
};

// Video Analytics Start

const video = document.querySelector('video');
let playPauseStatus = {};
let videoWatchTime = 0;
let watchingVideo = false;
let seekStatus = {};

video.addEventListener('play', ()=>{
    playPauseStatus.play = Math.floor(video.currentTime);
    watchingVideo = true;
});
video.addEventListener('pause', ()=>{
    playPauseStatus.pause = Math.floor(video.currentTime);
    watchingVideo = false;
    sendRequest('4', playPauseStatus).then(()=> playPauseStatus = {});
});
video.addEventListener('seeking', ()=>{
     seekStatus.start = Math.floor(video.currentTime);
     watchingVideo = false;
});
video.addEventListener('seeked', ()=>{
     seekStatus.end = Math.floor(video.currentTime);
     sendRequest('5', seekStatus).then(()=> seekStatus = {});
});

setInterval(()=>{
    if(watchingVideo === true) videoWatchTime += 0.5;
}, 500);

// Video Analytics End


// Pushing Analytics Start

document.addEventListener('visibilitychange', ()=>{
    if(document.visibilityState === 'hidden'){
      sendRequest('6', {'video_watch_time' : videoWatchTime});
    }
    video.pause();
});

// Pushing Analytics End

问题分析与修复方案

1. 拖动起止时间记录错误的核心问题

你当前的代码每次触发seeking事件都会覆盖seekStatus.start,但用户拖动进度条时,seeking事件会被连续触发多次(比如按住鼠标拖动进度条的过程中),最终seekStatus.start会变成拖动过程中的某个中间值,而非真正的拖动起始点。

2. 观看时长统计逻辑不准确

用setInterval累加时长的方式存在误差(比如浏览器后台时定时器会延迟),更可靠的方式是通过记录时间戳来计算实际观看时长。

3. 拖动后未恢复播放状态的时长统计

用户拖动视频后,如果之前处于播放状态,你的代码没有恢复watchingVideo的状态,会导致后续观看时长无法被统计。


修复后的完整代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <video controls src="sample.mp4"></video>
</body>
</html>
const sendRequest = (operation, data = false)=>{
    const formData = new FormData();
    formData.append('operation', operation);
    if(data !== false) formData.append('data', JSON.stringify(data));
    return fetch('https://www.example.com/operations.php', {method: 'post', body: formData});
};

// Video Analytics Start

const video = document.querySelector('video');
let playPauseStatus = {};
let videoWatchTime = 0;
let watchingVideo = false;
let seekStatus = {};
let isSeeking = false; // 标记是否正在拖动
let playStartTime = null; // 记录播放开始的时间戳

// 计算并累加观看时长的工具函数
const calculateWatchTime = () => {
    if (playStartTime && watchingVideo) {
        const now = Date.now();
        videoWatchTime += (now - playStartTime) / 1000;
        playStartTime = now;
    }
};

video.addEventListener('play', ()=>{
    playPauseStatus.play = Math.floor(video.currentTime);
    watchingVideo = true;
    playStartTime = Date.now(); // 记录播放开始时间戳
});

video.addEventListener('pause', ()=>{
    calculateWatchTime(); // 暂停时计算并累加时长
    playPauseStatus.pause = Math.floor(video.currentTime);
    watchingVideo = false;
    playStartTime = null;
    sendRequest('4', playPauseStatus).then(()=> playPauseStatus = {});
});

video.addEventListener('seeking', ()=>{
    if (!isSeeking) { // 仅在第一次进入拖动时记录起始时间
        seekStatus.start = Math.floor(video.currentTime);
        isSeeking = true;
    }
    calculateWatchTime(); // 拖动时先计算已观看时长
    watchingVideo = false;
    playStartTime = null;
});

video.addEventListener('seeked', ()=>{
    seekStatus.end = Math.floor(video.currentTime);
    sendRequest('5', seekStatus).then(()=> seekStatus = {});
    isSeeking = false; // 拖动结束,重置状态
    // 如果视频当前是播放状态,恢复观看时长统计
    if (!video.paused) {
        watchingVideo = true;
        playStartTime = Date.now();
    }
});

// Video Analytics End


// Pushing Analytics Start

document.addEventListener('visibilitychange', ()=>{
    if(document.visibilityState === 'hidden'){
        calculateWatchTime(); // 页面隐藏时计算最终时长
        sendRequest('6', {'video_watch_time' : Math.floor(videoWatchTime)});
    }
    video.pause();
});

// Pushing Analytics End

关键修复点说明
  • 新增isSeeking标志位,确保只记录拖动的起始点,而非拖动过程中的中间值。
  • 改用时间戳计算观看时长,避免setInterval带来的误差,统计更精准。
  • 拖动结束后,判断视频是否处于播放状态,自动恢复观看时长统计。
  • 在seeking、pause、visibilitychange事件中都调用calculateWatchTime,确保所有场景下的时长都被正确累加。

内容的提问来源于stack exchange,提问作者Abdul Raheem Dumrai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:22:18