HTML5视频缓冲属性特性

25
我正在设计一个自定义的HTML5视频播放器。因此,它将有自己的自定义滑块来模拟视频进度,所以我需要了解整个HTML5视频缓冲过程。
我遇到了这篇文章:Video Buffering。它说缓冲对象由几个按开始时间线性排序的时间范围组成。但我找不到以下内容:
  1. 假设视频开始播放。它继续播放直到1:45(可能偶尔会停顿等待更多数据),然后我突然跳到32:45。现在,如果我在最初加载并播放完之前跳回1:27(在跳跃之前的时间范围内),它会立即开始播放吗?还是因为我进行了跳跃,那部分被丢失,必须再次获取?无论哪种方式,所有这些情况的行为是否一致?
  2. 假设我进行了5或6次跳跃,每次跳跃后等待几秒钟以加载一些数据。这是否意味着buffered对象将存储所有这些时间范围?还是可能会丢失其中一些?这是一种堆栈类型的东西,随着进一步跳跃而加载更多时间范围,较早的范围将被弹出吗?
  3. 检查buffered对象是否具有从0开始(忘记实时流)并以视频持续时间结束的一个时间范围,能否确保已完全加载整个视频资源?如果不是,是否有某种方法可以知道已下载整个视频,并且任何部分都可以寻址,从而视频可以连续播放直到结束而不会延迟?
W3C规范在这方面不是很清楚,我也找不到适当的大型(大于一小时)远程视频资源进行测试。
5个回答

36

视频缓冲的方式取决于浏览器的实现,因此可能因浏览器而异。

各种浏览器可以使用不同的因素来确定保留或丢弃缓冲区的一部分。旧段、磁盘空间、内存和性能是典型的因素。

唯一的方法是“查看”浏览器正在加载的内容。

例如,在Chrome中,我播放了几秒钟,然后跳到大约30秒,你可以看到它开始从那个位置加载另一部分。

(缓冲区似乎也绑定到关键帧,因此可以在该缓冲区中解码n帧。这意味着缓冲区可以在实际位置之前稍微开始加载数据)。

Example

我提供了一个大约1分钟长的演示视频 - 然而,这不足以进行适当的测试。请随意提供包含更长视频的视频链接(或者如果您希望我使用此更新演示,请分享)。

主要功能将遍历视频元素上的buffered对象。它将呈现所有存在的部分到位于视频下方的画布中,并以红色显示。

您可以单击(但不能拖动)此查看器以将视频移动到不同位置。

/// buffer viewer loop (updates about every 2nd frame)
function loop() {

    var b = vid.buffered,  /// get buffer object
        i = b.length,      /// counter for loop
        w = canvas.width,  /// cache canvas width and height
        h = canvas.height,
        vl = vid.duration, /// total video duration in seconds
        x1, x2;            /// buffer segment mark positions

    /// clear canvas with black
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, w, h);

    /// red color for loaded buffer(s)
    ctx.fillStyle = '#d00';

    /// iterate through buffers
    while (i--) {
        x1 = b.start(i) / vl * w;
        x2 = b.end(i) / vl * w;
        ctx.fillRect(x1, 0, x2 - x1, h);
    }

    /// draw info
    ctx.fillStyle = '#fff';

    ctx.textBaseline = 'top';
    ctx.textAlign = 'left';
    ctx.fillText(vid.currentTime.toFixed(1), 4, 4);

    ctx.textAlign = 'right';
    ctx.fillText(vl.toFixed(1), w - 4, 4);

    /// draw cursor for position
    x1 = vid.currentTime / vl * w;

    ctx.beginPath();
    ctx.arc(x1, h * 0.5, 7, 0, 2 * Math.PI);
    ctx.fill();

    setTimeout(loop, 29);
}

当您暂停视频并运行buffered.end(index)时,它将返回1并停止工作。这是常见的行为吗?我找不到相关的问题。 - Danillo Corvalan

7
根据

buffered 属性保存有关当前所有缓冲时间范围的信息。据我理解,如果缓冲部分丢失,则会从对象中删除它(如果发生这种情况)。

特别是最后一个链接似乎非常有用,可以更好地理解此事(因为它提供了代码示例),但请记住这些是 Mozilla 文档,其他浏览器的支持可能不同。

回答您的问题:

假设视频开始播放,它继续播放到 1:45(偶尔停顿,等待进一步数据),之后我突然跳到 32:45。现在过了一些时间,如果我跳回到 1:27(在最初加载和播放通过之前跳跃的时间范围内),它是否会立即开始播放,因为它在之前已经加载过?

如果那部分的缓冲区没有被卸载,当跳回时它应该立即播放。我认为合理地假设,如果整体缓冲区大小超过某个体积,则某些缓冲区或缓冲区范围会在某个时候被卸载。

假设我进行了 5 或 6 次这样的跳跃,每次跳跃后等待几秒钟以加载一些数据。那么缓冲对象是否将保存所有这些时间范围?

是的,所有缓冲范围都应通过属性可读。

检查缓冲对象是否具有从0开始(忘记实时流)并以视频持续时间长度结束的一个时间范围,这样做是否能确保整个视频资源已完全加载?

是的,这是最后一个链接中的代码示例。显然,这是一种确定整个视频是否已加载完毕的适用方法。

if (buf.start(0) == 0 && buf.end(0) == v.duration)

问题在于我尝试对缓冲区进行实验。我让视频流运行了2-3分钟,然后跳到10个奇数,再让它流动2分钟,然后到20个奇数,以此类推。然后我期望buffered中会有3个范围,但我只发现一个正在缓冲的范围。 - SexyBeast
1
@Cupidvogel 嗯,要么你在每个位置花了足够的时间,以便它有足够的时间下载视频的大连续部分,要么我所说的卸载实际上是在那里发生的。无论哪种方式,buffered属性都应该反映正在发生的情况。 - Cobra_Fast
2
@Cupidvogel 我刚刚查看了一些Firefox源代码,他们确实有一个非常精细的缓冲管理系统,在某些情况下会丢弃缓冲段。例如,如果缓冲段在当前播放位置之前,并且它不是当前片段的一部分,并且用户没有中断播放一段时间,那么它将被丢弃。大部分代码都在源树的content/media/目录下。 - Cobra_Fast
这很糟糕,不是吗?你说Firefox,我在Chrome、Safari中也观察到了同样的问题。 - SexyBeast
@Cupidvogel 例如YouTube也有同样的问题。当你将视频看到接近结尾并跳回开头时,它会重新开始下载,尽管进度条显示该部分已经下载过了。我没有看到任何真正的解决方案,只能忍受它。 - Cobra_Fast
显示剩余4条评论

3

尽管被接受的答案很好,但我决定更新它的代码示例,原因如下:

  • 进度渲染任务应该仅在progress事件中触发。
  • 进度渲染任务与其他一些任务混在一起,例如绘制时间戳和播放头位置。
  • 代码通过ID引用了多个DOM元素,而没有使用document.getElementById()
  • 所有变量名都被混淆了。
  • 我认为正向的for()循环比反向的while()循环更加优雅。

请注意,我已经删除了播放头和时间戳,以保持代码的整洁,因为这个答案专注于视频缓冲区的可视化。

在线视频缓冲区可视化链接

被接受的答案中loop()函数的重写:

function drawProgress(canvas, buffered, duration){
    // I've turned off anti-aliasing since we're just drawing rectangles.
    var context = canvas.getContext('2d', { antialias: false });
    context.fillStyle = 'blue';

    var width = canvas.width;
    var height = canvas.height;
    if(!width || !height) throw "Canvas's width or height weren't set!";
    context.clearRect(0, 0, width, height); // clear canvas

    for(var i = 0; i < buffered.length; i++){
        var leadingEdge = buffered.start(i) / duration * width;
        var trailingEdge = buffered.end(i) / duration * width;
        context.fillRect(leadingEdge, 0, trailingEdge - leadingEdge, height);
    }
}

3
  1. 几乎所有的浏览器都会将缓冲数据保存在缓存中,直到该会话结束。只有当服务器清空所有缓存数据时,用户才会面临需要从已加载视频点重新加载的问题。HTML5视频标签将支持此功能,并将保存视频加载到的位置。

  2. 这并不意味着会话已丢失,而是表示对象(如果您正在使用Flash播放器)正在查找特定点的某些数据,或者HTML5视频标记由于Internet连接失败或其他服务器错误而出现了一些问题。

  3. 元数据会自动加载,除非您使用以下代码 <audio preload="none"... 这将使浏览器不从服务器下载任何内容,您可以将其用作:
    <audio preload="auto|metadata|none"... 如果使用“none”,则除非用户单击播放按钮,否则不会下载任何内容,并且元数据将从服务器下载名称、时间和其他元数据,但不会下载文件,而“auto”将在页面加载后立即开始下载。

我建议您阅读一些jQuery文档。因为jQuery可以通过ajax API更改和更新内容,这将非常有帮助。祝您成功!干杯。


0

这只是这个优秀答案 https://dev59.com/WWMl5IYBdhLWcg3wa2bV#18624833 的一个变体。

我只是让它在不需要任何工作的情况下运行,并添加了一些额外的功能。一切都是自动的。

  • 目前适用于全屏视频播放,如Netflix或HBOGO
  • 自动创建画布
  • 自动更新宽度为100%视口宽度
  • 作为书签小程序工作
  • 不会阻挡视线(透明,2像素高)

enter image description here

function prepare() {
    console.log('prepare');

    _v = $('video')[0];
    _v.insertAdjacentHTML('afterend',
    `<canvas
        id="WowSuchName"
        height="1"
        style="
            position: absolute;
            bottom: 0;
            left: 0;
            opacity: 0.4;
        "></canvas>`);

    _c = WowSuchName
    _cx = _c.getContext('2d');

    window.addEventListener('resize', resizeCanvas, false);

    function resizeCanvas() {
        console.log('resize');
        _c.width = window.innerWidth;
    }
    resizeCanvas();
}

/// buffer viewer loop (updates about every 2nd frame)
function loop() {
    if (!window.WowSuchName) { prepare(); }

    var b = _v.buffered,  /// get buffer object
        i = b.length,     /// counter for loop
        w = _c.width,     /// cache canvas width and height
        h = _c.height,
        vl = _v.duration, /// total video duration in seconds
        x1, x2;           /// buffer segment mark positions

    /// clear canvas
    _cx.clearRect(0, 0, w, h);

    /// color for loaded buffer(s)
    _cx.fillStyle = '#888';

    /// iterate through buffers
    while (i--) {
        x1 = b.start(i) / vl * w;
        x2 = b.end(i) / vl * w;
        _cx.fillRect(x1, 0, x2 - x1, h);
    }

    /// draw cursor for position
    _cx.fillStyle = '#fff';
    x1 = _v.currentTime / vl * w;
    _cx.fillRect(x1-1, 0, 2, h);

    setTimeout(loop, 29);
}

loop();

书签代码在这里

javascript:eval(atob("CmZ1bmN0aW9uIHByZXBhcmUoKSB7CiAgICBjb25zb2xlLmxvZygncHJlcGFyZScpOwoKICAgIF92ID0gJCgndmlkZW8nKVswXTsKICAgIF92Lmluc2VydEFkamFjZW50SFRNTCgnYWZ0ZXJlbmQnLAogICAgYDxjYW52YXMKICAgICAgICBpZD0iV293U3VjaE5hbWUiCiAgICAgICAgaGVpZ2h0PSIxIgogICAgICAgIHN0eWxlPSIKICAgICAgICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICAgICAgICBib3R0b206IDA7CiAgICAgICAgICAgIGxlZnQ6IDA7CiAgICAgICAgICAgIG9wYWNpdHk6IDAuNDsKICAgICAgICAiPjwvY2FudmFzPmApOwogICAgCiAgICBfYyA9IFdvd1N1Y2hOYW1lCiAgICBfY3ggPSBfYy5nZXRDb250ZXh0KCcyZCcpOwoKICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdyZXNpemUnLCByZXNpemVDYW52YXMsIGZhbHNlKTsKCiAgICBmdW5jdGlvbiByZXNpemVDYW52YXMoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ3Jlc2l6ZScpOwogICAgICAgIF9jLndpZHRoID0gd2luZG93LmlubmVyV2lkdGg7CiAgICB9CiAgICByZXNpemVDYW52YXMoKTsKfQoKLy8vIGJ1ZmZlciB2aWV3ZXIgbG9vcCAodXBkYXRlcyBhYm91dCBldmVyeSAybmQgZnJhbWUpCmZ1bmN0aW9uIGxvb3AoKSB7CiAgICBpZiAoIXdpbmRvdy5Xb3dTdWNoTmFtZSkgeyBwcmVwYXJlKCk7IH0KCiAgICB2YXIgYiA9IF92LmJ1ZmZlcmVkLCAgLy8vIGdldCBidWZmZXIgb2JqZWN0CiAgICAgICAgaSA9IGIubGVuZ3RoLCAgICAgLy8vIGNvdW50ZXIgZm9yIGxvb3AKICAgICAgICB3ID0gX2Mud2lkdGgsICAgICAvLy8gY2FjaGUgY2FudmFzIHdpZHRoIGFuZCBoZWlnaHQKICAgICAgICBoID0gX2MuaGVpZ2h0LAogICAgICAgIHZsID0gX3YuZHVyYXRpb24sIC8vLyB0b3RhbCB2aWRlbyBkdXJhdGlvbiBpbiBzZWNvbmRzCiAgICAgICAgeDEsIHgyOyAgICAgICAgICAgLy8vIGJ1ZmZlciBzZWdtZW50IG1hcmsgcG9zaXRpb25zCgogICAgLy8vIGNsZWFyIGNhbnZhcwovLyAgICAgX2N4LmZpbGxTdHlsZSA9ICcjMDAwJzsKLy8gICAgIF9jeC5maWxsUmVjdCgwLCAwLCB3LCBoKTsKICAgIF9jeC5jbGVhclJlY3QoMCwgMCwgdywgaCk7CgogICAgLy8vIGNvbG9yIGZvciBsb2FkZWQgYnVmZmVyKHMpCiAgICBfY3guZmlsbFN0eWxlID0gJyM4ODgnOwoKICAgIC8vLyBpdGVyYXRlIHRocm91Z2ggYnVmZmVycwogICAgd2hpbGUgKGktLSkgewogICAgICAgIHgxID0gYi5zdGFydChpKSAvIHZsICogdzsKICAgICAgICB4MiA9IGIuZW5kKGkpIC8gdmwgKiB3OwogICAgICAgIF9jeC5maWxsUmVjdCh4MSwgMCwgeDIgLSB4MSwgaCk7CiAgICB9CgogICAgLy8vIGRyYXcgY3Vyc29yIGZvciBwb3NpdGlvbgogICAgX2N4LmZpbGxTdHlsZSA9ICcjZmZmJzsKICAgIHgxID0gX3YuY3VycmVudFRpbWUgLyB2bCAqIHc7CiAgICBfY3guZmlsbFJlY3QoeDEtMSwgMCwgMiwgaCk7CgogICAgc2V0VGltZW91dChsb29wLCAyOSk7Cn0KCmxvb3AoKTsK"))

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