如何检测HTML5视频是否因缓冲而暂停?

46

我正在尝试测试视频是否卡顿。 我注意到当视频因缓冲而暂停时,pause事件不会触发。 检测视频是否因缓冲而暂停的最佳方法是什么?

5个回答

54
我通过每隔x毫秒(例如50)检查播放器进度来实现这一点。如果播放器没有像预期的那样前进,那么我们就在缓冲。这是相当可靠的,因为我发现视频缓冲的其他事件,如waitingstalled并不在所有情况下触发。
请注意,时间间隔必须大于预期的帧间差异,但是我相信您也不需要那么精确。在±300ms范围内估计缓冲时间仍然可以,因为人类很可能无法感知该区域的差异。
重要的是要检查用户是否主动暂停了播放。
var checkInterval  = 50.0 // check every 50 ms (do not use lower values)
var lastPlayPos    = 0
var currentPlayPos = 0
var bufferingDetected = false
var player = document.getElementById('videoPlayer')

setInterval(checkBuffering, checkInterval)
function checkBuffering() {
    currentPlayPos = player.currentTime

    // checking offset should be at most the check interval
    // but allow for some margin
    var offset = (checkInterval - 20) / 1000

    // if no buffering is currently detected,
    // and the position does not seem to increase
    // and the player isn't manually paused...
    if (
            !bufferingDetected 
            && currentPlayPos < (lastPlayPos + offset)
            && !player.paused
        ) {
        console.log("buffering")
        bufferingDetected = true
    }

    // if we were buffering but the player has advanced,
    // then there is no buffering
    if (
        bufferingDetected 
        && currentPlayPos > (lastPlayPos + offset)
        && !player.paused
        ) {
        console.log("not buffering anymore")
        bufferingDetected = false
    }
    lastPlayPos = currentPlayPos
}

1
应该注意 checkInterval 需要比当前时间更新间隔更大。我尝试使用 30.0,但由于 offset 太小,从未得到“不再缓冲”的状态。 - potench
1
@Elmo 我非常确定。它对你不起作用吗?至少HTML规范没有改变 - 对我来说,这仍然是最可靠的方法。你可以检查一下在不同浏览器中是否waiting事件(如brianchirls的答案所建议)现在更可靠。 - slhck
@Elmo 确实,我不知道有类似的东西。当然,有很多提供自己的流媒体解决方案(使用DASH或其他技术)和相应分析的供应商,但我也没有一个好的概述。 - slhck
1
我是不是错了,或者在计算偏移量时代码中有一个错误?当将checkInterval更改为例如500时,var offset = 1 / checkInterval变为1/500。这将是每半秒检查的非常小的偏移量。如果checkInterval应该是您想要的任何间隔,那么偏移量是否应该以不同的方式计算?我建议像这样:(checkInterval - x) / 1000;。因此,时间间隔的大小以秒为单位,减去一些余量x < checkInterval以解决不准确性问题。 - David Schumann
1
@DavidNathan 是的,你说得完全正确。感谢你发现了这个问题。我从未改变过偏移量的值,所以从未发现这个错误。不确定 x 的值应该是多少... 可能甚至 (checkInterval / 2) / 1000 也可以。 - slhck
显示剩余2条评论

27

你要找的事件是waiting

根据规范

当正在播放的元素其readyState属性的值变为低于HAVE_FUTURE_DATA时,会因该元素可能停止播放而触发等待(waiting)事件。

paused状态不会改变,因为视频仍在“尝试”播放。所以触发了waiting事件。当足够的数据被加载后,将触发playing事件。

您还可以随时通过查看两个属性networkStatereadyState来检查状态。

if (video.networkState === video.NETWORK_LOADING) {
    // The user agent is actively trying to download data.
}

if (video.readyState < video.HAVE_FUTURE_DATA) {
    // There is not enough data to keep playing from this point
}

3
据我观察,waiting事件并不总是会被触发。我模拟了一个慢速连接,在视频每隔几秒钟出现缓冲时,并没有看到waiting事件被触发(也没有stalled)。最好的方法是与每隔几毫秒监控播放进度相结合来检查这一点。 - slhck
这是哪个浏览器和什么类型的视频文件?您可以在视频中任意寻找吗? - brianchirls
1
Chrome在Linux上使用HTML5兼容的MP4文件稳定运行。我能够进行搜索,但当带宽有限且缓冲区无法填满时,播放器会停顿,但是没有触发任何事件。这可能是API的一个错误,但我还没有进行任何跨浏览器的检查。 - slhck
2
@slhck是正确的。我遇到了完全相同的问题,通过每秒检查播放进度来解决了这个问题(我发现较低的值过度杀伤力)。我已经在Windows 7上测试了FF、Chrome和IE的最新版本。在Chrome和IE上,“readyState”属性是无用的,因为它报告“HAVE_FUTURE_DATA”,而实际上由于缺乏数据而导致播放被冻结。到目前为止,在我测试的三个浏览器中,FF似乎是唯一触发“waiting”事件的浏览器。 - Valentin Flachsel
3
看起来Chrome对于readyState和相关事件的支持还不够完善。请参考这个bug https://code.google.com/p/chromium/issues/detail?id=144683 和 https://code.google.com/p/chromium/issues/detail?id=73609 。也许值得再针对waiting问题提交一个新的bug。 - brianchirls
3
截至2019年,“等待”事件确实能够可靠地触发。我注意到很多这样的问题来自2014年,所以情况似乎已经有所改善。 - Michael Giovanni Pumo

4
根据 MDN 文档,“waiting” 指的是:当请求操作(例如播放)因等待另一个操作(例如查找)完成而被延迟时发送。因此,查找或网络请求将触发“waiting”。评论中的 Michael 指出,“waiting” 在 2019 年后是可靠的,所以我尝试了一下,它确实有效!
let slowInternetTimeout = null;

let threshold = 3000; //ms after which user perceives buffering

video.addEventListener('waiting', () => {
    slowInternetTimeout = setTimeout(() => {
        //show buffering
    }, threshold);
});
video.addEventListener('playing', () => {
    if(slowInternetTimeout != null){
        clearTimeout(slowInternetTimeout);
        slowInternetTimeout = null;
    }
});

1
您可以检查已缓冲的视频内容长度,如果小于当前播放部分,则触发暂停事件。使用以下代码,您可以检查缓冲视频的长度。
$vid = $("#video_id");

$vid.on('progress', function(e) {

    percentVidLoaded = null;
    // FF4+, Chrome
    if ($vid[0] && $vid[0].buffered && $vid[0].buffered.length > 0 && $vid[0].buffered.end && $vid[0].duration) {
        percentVidLoaded = $vid[0].buffered.end(0) / $vid[0].duration;
    }
    /* Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end()
     *  to be anything other than 0. If the byte count is available we use this instead.
     *  Browsers that support the else if do not seem to have the bufferedBytes value and
     *  should skip to there.
     */
    else if ($vid[0] && $vid[0].bytesTotal != undefined && $vid[0].bytesTotal > 0 && $vid[0].bufferedBytes != undefined) {
        percentVidLoaded = $vid[0].bufferedBytes / $vid[0].bytesTotal;
    }
    if (percentVidLoaded !== null) {
        percentVidLoaded = 100 * Math.min(1, Math.max(0, percentVidLoaded));
    }
});

1
使用 typeof !== "undefined" - dude
2
这只会读取缓冲区并将其转换为百分比。它不会检查缓冲区与视频当前时间是否匹配。 - dude
无论如何,video.currentTime的值并不是视频应该处于的currentTime,不幸的是它是真实的currentTime,因此无法根据缓冲百分比进行检查。 - Splact

0

您需要检查缓冲区是否小于当前视频时间。如果是,则表示视频正在缓冲。但是,您应该使用小容差来检查,以确保在实际需要缓冲之前检测到它。

例如:

var video = document.getElementById("myVideo");
var prevBuffer = {
    "buffer": null,
    "time": null
};
var isBuffering = function(){

    if(video && video.buffered && video.buffered.end && video.buffered.length > 0){
        var buffer = video.buffered.end(0);
        var time   = video.currentTime;

        // Check if the video hangs because of issues with e.g. performance
        if(prevBuffer.buffer === buffer && prevBuffer.time === time && !video.paused){
            return true;
        }
        prevBuffer = {
            "buffer": buffer,
            "time": time
        };
        // Check if video buffer is less
        // than current time (tolerance 3 sec)
        if((buffer - 3) < time){
            return true;
        }
    }
    return false;

};
video.addEventListener("play", function(e){
    // Make sure this handler is only called once
    e.target.removeEventListener(e.type, arguments.callee);
    // Give browsers 3secs time to buffer
    setTimeout(function(){
        // As "progress", "stalled" or "waiting" aren't fired
        // reliable, we need to use an interval
        var interval = setInterval(function(){
            if(isBuffering()){
                clearInterval(interval);
                console.log("Buffering");
            }
        }, 500);
    }, 3000);
});

即使缓冲区末尾大于当前时间,流也可能会停顿。我观察到在某些浏览器上,无论是否因为下溢而暂停,我们都将具有网络状态NETWORK_LOADING,以及readyState HAVE_ENOUGH_DATA和buffered.end() > currentTime + tolerance。这里的容差值是引起问题的原因。我们可以从视频元素中读取一个容差值,以便我们始终知道它是否正在缓冲吗? - angie
据我所知,没有特定的方法来观察缓冲区状态。除了你刚刚提到的 ("networkState") 和 "readyState" 属性之外,没有更多的内容。然而,它们是为不同的方法构建的。 这发生在哪个浏览器中? - dude
@angie 我刚刚编辑了我的答案。这可能也可以解决你的使用情况。 - dude
感谢您的编辑。这个3秒的值是基于观察得出的吗?3秒的容差可能会解决问题(尽管如果播放器很智能,它可能会因带宽而异,我不确定)。 - angie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接