【问题标题】:How to best track how long a video was played?如何最好地跟踪视频播放了多长时间?
【发布时间】:2017-07-07 16:38:25
【问题描述】:

当用户观看视频时,我想进行 2 个 AJAX 调用。当用户观看完视频并且播放的时间等于或超过视频的持续时间时(因为用户也可以倒带)。 timePlayed>=duration && event.type=="ended"。我成功地拨打了这个电话。

我苦恼的是,我还想在视频观看率超过 80% 并且视频播放时间也超过 80% 时拨打电话,以防止用户只是快进。

为了让它工作,我必须修改我的 videoStartedPlaying() 方法,这是我在尝试设置间隔时遇到问题的地方。现在,通过设置间隔,它就像一个无限循环。

var video_data = document.getElementById("video");

var timeStarted = -1;
var timePlayed = 0;
var duration = 0;

// If video metadata is loaded get duration
if(video_data.readyState > 0)
    getDuration.call(video_data);
//If metadata not loaded, use event to get it
else {
    video_data.addEventListener('loadedmetadata', getDuration);
}

// remember time user started the video
function videoStartedPlaying() {
    timeStarted = new Date().getTime()/1000;
    setInterval(function(){
        playedFor = new Date().getTime()/1000 - timeStarted;
        checkpoint = playedFor / duration;
        percentComplete = video_data.currentTime/video_data.duration;

        // here I need help of how to best accomplish this
        if (percentComplete >= 0.8 && checkpoint >= 0.8) {
            // AJAX call here
        }
    }, 2000);
}

function videoStoppedPlaying(event) {
    // Start time less then zero means stop event was fired vidout start event
    if(timeStarted>0) {
        var playedFor = new Date().getTime()/1000 - timeStarted;
        timeStarted = -1;
        // add the new amount of seconds played
        timePlayed+=playedFor;
    }

    // Count as complete only if end of video was reached
    if(timePlayed>=duration && event.type=="ended") {
        // AJAX call here
    }
}

function getDuration() {
    duration = video_data.duration;
}

video_data.addEventListener("play", videoStartedPlaying);
video_data.addEventListener("playing", videoStartedPlaying);
video_data.addEventListener("ended", videoStoppedPlaying);
video_data.addEventListener("pause", videoStoppedPlaying);

我真的很感激这方面的任何帮助,因为我似乎束手无策。

非常感谢!

编辑: 感谢评论,我想出了这个:

const video = document.getElementById("video");
const set = new Set();
const percent = .8;
let toWatch;

function mediaWatched (curr) {
  alert(`${curr}% of media watched`)
}

function handleMetadata(e) {
  toWatch = Math.ceil(video.duration * percent);
  console.log(toWatch, video.duration);
}

function handleTimeupdate (e) {
  set.add(Math.ceil(video.currentTime));
  let watched = Array.from(set).pop();
  if (set.has(toWatch) && watched === toWatch) {
    video.removeEventListener("timeupdate", handleTimeupdate);
    console.log(watched);
    mediaWatched(
      Math.round(watched / Math.ceil(video.duration) * 100)
    );
  }
}

video.addEventListener("loadedmetadata", handleMetadata);

video.addEventListener("timeupdate", handleTimeupdate);
<video width="400" height="300" controls="true" poster="" id="video">
    <source type="video/mp4" src="http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_2mb.mp4" />
</video>

现在,例如,如果我快进到大约 50% 的长度,然后让它播放,它会在达到电影的 80% 时触发,但它不应该因为我快进到 50% 并且基本上只看了30%。

这有意义吗?我怎样才能实现这种行为?

【问题讨论】:

  • 就我个人而言,我的做法会稍有不同...我会决定我想要的粒度(例如 1 秒)并创建一个数组,该数组包含与视频初始化时间一样多的插槽0. 我会在timeupdated 触发的视频上设置一个事件,并将数组中的插槽(基于currentTime)设置为“1”。当 80% 的空位被填满时(从一开始和/或全部),您的逻辑就可以确定他们接下来可以做什么......
  • 哦,对于上面的代码,只需在进行 ajax 调用后设置一个标志,然后不要再调用它,或者删除侦听器,使其根本不会被调用
  • @Offbeatmammal 感谢您的评论!由于您向timeupdated 暗示,我想出了一个稍微不同的方法。现在,我现在遇到的问题是,如果我快进到大约 50% 的长度,然后让它播放,只要达到电影的 80%,它就会触发,但不应该因为我快进到 50%基本上只看了30%。那有意义吗?我怎样才能实现这种行为?
  • 这就是为什么我建议逐秒构建(或任何您需要的粒度)数组...这样,如果 80% 的条目设置为 1 而不是 0,那么您知道它们已经观看了 80% 的可用秒数...
  • 这种方法的问题在于,如果用户一遍又一遍地重复播放相同的 5 秒,而不是观看整个 10 分钟,那么您的计数器仍然会增加。我的建议是将您的视频(通过数组)标记为多个块,并使用视频本身上的timeupdated 事件跟踪正在观看的块并标记它。然后,您可以使用计时器检查数组的状态是否 >80% 设置为 true,然后进行 ajax 调用

标签: javascript html video


【解决方案1】:

根据 cmets 中的讨论,这里有一个工作示例。

它包括几个处理程序,只是为了让设置数组和对内容求和更容易,这样你就知道什么时候达到了 80% 标记(尽管如果你想强制它们,你可能需要更改该逻辑,例如,明确地观看整个视频的前 80% 而不仅仅是总共 80%)。

其中有许多 console.log(...) 语句,因此您可以在浏览器控制台窗口中查看它在做什么......您可能希望在真正部署之前将它们取出。

我已经在 timeupdate 事件中设置了在哪里进行 ajax 调用的钩子,但您也可以始终在主循环中使用常规的 setInterval 计时器来检查 80% 并进行调用在那里,但这似乎更干净

大部分内容应该是不言自明的,但是如果有什么不清楚的地方,请在 cmets 中询问...

<video controls preload="auto" id="video" width="640" height="365" muted>
      <source src="http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_2mb.mp4" type="video/mp4">
    </video>

<script>

 // handler to let me resize the array once we know the length
 Array.prototype.resize = function(newSize, defaultValue) {
    while(newSize > this.length)
        this.push(defaultValue);
    this.length = newSize;
}

// function to round up a number
function roundUp(num, precision) {
  return Math.ceil(num * precision) / precision
} 

var vid = document.getElementById("video")
var duration = 0; // will hold length of the video in seconds
var watched = new Array(0);
var reported80percent = false;

vid.addEventListener('loadedmetadata', getDuration, false);
vid.addEventListener('timeupdate',timeupdate,false)

function timeupdate() {
    currentTime = parseInt(vid.currentTime);
    // set the current second to "1" to flag it as watched
    watched[currentTime] = 1;

    // show the array of seconds so you can track what has been watched
    // you'll note that simply looping over the same few seconds never gets
    // the user closer to the magic 80%...
    console.log(watched);

    // sum the value of the array (add up the "watched" seconds)
    var sum = watched.reduce(function(acc, val) {return acc + val;}, 0);
    // take your desired action on the ?80% completion
    if ((sum >= (duration * .8)) && !reported80percent) {
        // set reported80percent to true so that the action is triggered once and only once
        // could also unregister the timeupdate event to avoid calling unneeded code at this point
        // vid.removeEventListener('timeupdate',timeupdate)
        reported80percent = true;
        console.log("80% watched...")
        // your ajax call to report progress could go here...   
    }
}

function getDuration() {
    console.log("duration:" + vid.duration)
    // get the duration in seconds, rounding up, to size the array
    duration = parseInt(roundUp(vid.duration,1));
    // resize the array, defaulting entries to zero
    console.log("resizing arrary to " + duration + " seconds.");
    watched.resize(duration,0)
}

</script> 

【讨论】:

  • 很好的答案!由于您的提示,这比我所拥有的要干净得多,但本质上相似! 2 个快速问题:为什么 vid.removeEventListener('timeupdate',timeupdate) 被注释掉了?关闭它以节省资源不是更有意义吗?此外,如果他们的视频长达 2 小时,则该数组将有 7200 个值。这种方法仍然是一个好方法吗?
  • :) 我将 removeEventListener 注释掉了,因为我希望您能够看到它在需要时继续填充数组,绝对删除 cmets 以提高代码效率。包含 7200 个项目的数组应该不是问题,相当容易测试(手头没有长视频,因此尚未确认)
  • 听起来不错!真的很感激:-)。我将对其进行测试,如果最坏的情况出现问题,我也可以轻松地将其减少到每隔一秒。非常感谢!我花了 3 天时间才达到这一点,在几个小时内得到了您的帮助,然后您向我展示了一个比我所拥有的更清晰的示例,从而锦上添花 :-)
  • 在“现代”浏览器中,数组的最大理论大小是 42.9 亿个元素,所以应该没问题。只是用四个小时的视频伪造了它,性能似乎几乎没有受到影响(内存占用不是问题)-Chrome,OSX 供参考,YMMV
猜你喜欢
  • 2013-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-14
  • 2016-06-19
  • 1970-01-01
  • 2019-04-08
  • 2011-11-07
相关资源
最近更新 更多