【发布时间】:2014-02-19 09:55:55
【问题描述】:
我正在尝试测试视频是否断断续续。我注意到当视频暂停缓冲时不会触发pause 事件。检测视频是否已暂停缓冲的最佳方法是什么?
【问题讨论】:
标签: javascript html html5-video
我正在尝试测试视频是否断断续续。我注意到当视频暂停缓冲时不会触发pause 事件。检测视频是否已暂停缓冲的最佳方法是什么?
【问题讨论】:
标签: javascript html html5-video
我通过每 x 毫秒检查玩家进度来做到这一点,例如50. 如果玩家没有达到预期的进步,那么我们正在缓冲。这是相当可靠的,因为我发现在视频缓冲的所有情况下都不会触发其他事件,例如 waiting 或 stalled。
请注意,间隔必须大于预期的帧间差异,但我相信您无论如何都不会想要那么精确。考虑到人类很可能无法感知该区域的差异,估计在 ±300 毫秒内的缓冲时间仍然可以。
检查用户是否没有主动暂停播放很重要。
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
}
【讨论】:
checkInterval需要大于currentTime的更新间隔。我尝试使用30.0 并没有得到“不再缓冲”状态,因为offset 太小了。
waiting 事件(如 brinchirls 的回答中所建议的)现在在不同的浏览器中是否更可靠。
checkInterval 更改为例如500、var offset = 1 / checkInterval 变为 1/500。每半秒检查一次,这将是一个非常小的偏移量。如果checkInterval 应该是您想要的任何间隔,那么偏移量是否应该以不同的方式计算?我会建议类似:(checkInterval - x) / 1000;。所以间隔的大小以秒为单位,减去一些余量x < checkInterval 以解释不准确
x 的值应该是多少……甚至可能(checkInterval / 2) / 1000 也可以。
您要查找的事件是waiting。
来自spec:
一个等待的 DOM 事件可以作为一个元素的结果被触发 由于其 readyState 属性,可能正在播放停止播放 更改为低于 HAVE_FUTURE_DATA 的值。
paused 状态不会改变,因为视频仍在“可能播放”(即“正在尝试”播放)。所以waiting 事件触发。当加载足够的数据时,playing 会触发。
您还可以通过查看networkState 和readyState 两个属性随时检查状态
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
}
【讨论】:
waiting 事件不会在所有情况下都被触发。我模拟了一个慢速连接,视频每隔几秒钟缓冲一次,没有看到waiting 事件被触发(stalled 也没有)。检查这一点的最佳方法是每隔几毫秒监控一次播放进度。
readyState 属性是无用的,因为它报告HAVE_FUTURE_DATA,而由于缺少数据,播放实际上被冻结。到目前为止,在我测试的三个浏览器中,FF似乎是唯一一个触发waiting事件的浏览器。
readyState 的支持似乎很粗略。看到这个错误code.google.com/p/chromium/issues/detail?id=144683 和code.google.com/p/chromium/issues/detail?id=73609 也许值得为waiting 提交另一个错误
您可以只检查缓冲的视频内容长度,如果它小于当前播放部分,则只需触发暂停事件。使用以下代码,您可以检查缓冲的视频长度。
$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));
}
});
【讨论】:
typeof !== "undefined"
根据 MDN 文档的“等待”事件是 -
当请求的操作(例如播放)延迟等待另一个操作(例如搜索)完成时发送。
所以搜索或网络请求将触发“等待”。 cmets 中的迈克尔确实指出,截至 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;
}
});
【讨论】:
您需要检查缓冲区是否小于当前视频时间。如果是这样,则视频正在缓冲。但是,您应该以较小的容差进行检查,以确保在确实需要缓冲之前检测到它。
例子:
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);
});
【讨论】: