HTML5 视频缓冲属性特性

IT技术 javascript html html5-video buffering
2021-03-09 17:35:51

我正在设计一个自定义HTML5视频播放器。因此,它将有自己的自定义滑块来模拟视频进度,因此我需要了解HTML5视频的整个缓冲过程

我看到了这篇文章:视频缓冲它表示缓冲对象由几个时间范围按开始时间的线性顺序组成。但我找不到以下内容:

  1. 说视频开始。它自己持续到 1:45(可能偶尔会停顿,等待进一步的数据),之后我突然跳到 32:45。现在过了一段时间,如果我跳回到 1:27(在我开始跳转之前最初加载和播放的时间范围内),它会像之前已经加载一样立即开始播放吗?或者是因为我做了一个跳跃,那部分丢失了,必须再次获取?无论哪种方式,所有这些场景的行为是否一致?

  2. 假设我进行了 5 或 6 次这样的跳转,每次都等待几秒钟以在跳转后加载一些数据。这是否意味着该buffered对象将存储所有这些时间范围?或者可能有些人迷路了?它是一种堆栈类型的东西,当更多的范围由于进一步的跳跃而加载时,较早的范围会被弹出吗?

  3. 检查buffered对象是否有一个从 0 开始(忘记直播)到视频时长结束的时间范围是否确保整个视频资源已完全加载?如果没有,有没有办法知道整个视频已经下载,任何部分都是可搜索的,从哪个视频可以连续播放到结束而没有片刻延迟?

W3C 规范对此不是很清楚,我也找不到合适的大(比如一个多小时)远程视频资源来测试。

5个回答

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

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

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

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

(缓冲区似乎也受关键帧的限制,因此可以对该缓冲区中的 n 帧进行解码。这意味着缓冲区可以在实际位置之前开始加载数据)。

例子

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

main 函数将遍历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 并停止工作。这是一种常见的行为吗?找不到相关问题。
2021-05-09 17:35:51

根据

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 和相同。然后我预计 中会有 3 个范围buffered,而我找到了一个,当前正在缓冲的一个。
2021-05-01 17:35:51
是的,我也是这么想的。但我只寻找 3 个部分,这似乎太小了,无法开始清除缓存。不,生成的单个缓冲区不会将前一个缓冲区和当前缓冲区组合在一起(它们相隔 10 分钟的时间跨度,间歇性部分仍未加载,因此无法将它们组合起来),它只是指到我寻找的起点,随着加载更多数据,终点会更新。
2021-05-07 17:35:51
这很糟糕,不是吗?你说 Firefox,我在 Chrome、Safari 中也观察到了同样的情况......
2021-05-18 17:35:51
@Cupidvogel 我刚刚翻阅了一些 Firefox 源代码,他们确实有一个非常精细的缓冲区管理系统,可以在某些情况下删除缓冲段。例如,如果当前播放位置之前的缓冲段不是当前段的一部分,并且播放在一段时间内没有被用户中断,则该段将被丢弃。其中大部分content/media/位于源代码树中。
2021-05-19 17:35:51
@Cupidvogel 好吧,要么您在每个位置都花费了足够的时间以有足够的时间下载视频的大部分连续部分,要么我所说的卸载实际上发生在那里。无论哪种方式,该buffered属性都应该反映正在发生的事情。
2021-05-20 17:35:51
  1. 几乎每个浏览器都将缓冲数据保存在该会话的缓存中。用户离开该页面后,缓存将过期。我不认为用户每次从视频加载点加载视频时都必须加载页面。只有当服务器清除所有缓存数据时,用户才会遇到此问题。HTML5 视频标签将支持此功能,并将视频保存到已加载的位置。

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

  3. 元数据是自动加载的,直到你使用它 <audio preload="none"...这将使浏览器不从服务器下载任何东西,你可以使用它:
    <audio preload="auto|metadata|none"...如果你不使用,除非用户点击播放按钮,否则不会下载任何东西,元数据将下载名称,时间和来自服务器的其他元数据,但不是文件,自动将在页面加载后立即开始下载。

我会一直推荐你阅读 jQuery 的一些文档。因为 jQuery 将允许您使用 ajax API 更改和更新内容,并且也会有所帮助。希望你成功!干杯。

尽管接受的答案的描述非常好,但我决定更新其代码示例,原因如下:

  • 进度渲染任务应该只在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);
    }
}

这只是这个优秀答案的变体https://stackoverflow.com/a/18624833/985454

我只是让它在不需要任何工作的情况下工作,并增加了一些额外的好处。一切都是自动的。

  • 目前用于全屏视频播放,例如 netflix 或 hbogo
  • 自动创建画布
  • 自动将宽度更新为 100% 视口宽度
  • 作为书签工作
  • 不会阻碍视线(透明,2px 高)

在此处输入图片说明

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"))