超出缓冲配额

Joe Medley
Joe Medley

如果您使用的是媒体来源扩展 (MSE),您最终需要处理的一个问题是缓冲区过满。如果发生这种情况,您将获得所谓的 QuotaExceededError。在本文中,我将介绍一些处理它的方法

什么是 QuotaExceededError?

基本上,如果您尝试向 SourceBuffer 对象添加过多数据,则会获得 QuotaExceededError。(向父 MediaSource 元素添加更多 SourceBuffer 对象也会抛出此错误。这不在本文的讨论范围之内。)如果 SourceBuffer 包含的数据过多,调用 SourceBuffer.appendBuffer() 会在 Chrome 控制台窗口中触发以下消息。

配额控制台错误。

关于这一点,需要注意以下几点。首先,请注意名称 QuotaExceededError 未显示在消息中的任何位置。为此,请在可以捕获错误的位置设置断点,并在监视或作用域窗口中检查错误。我如下所示。

配额观察窗口。

其次,没有明确的方法来弄清 SourceBuffer 可以处理多少数据。

在其他浏览器中的行为

在撰写本文时,Safari 不会在其许多 build 中抛出 QuotaExceededError。而是使用两步算法移除帧,如果有足够的空间来处理 appendBuffer(),则会停止。首先,它会以 30 秒的分块的形式释放当前时间之前 0 到 30 秒之间的帧。接下来,它会在 currentTime 之后从倒退到 30 秒内释放 30 秒的帧。如需了解详情,请参阅 2014 年的 Webkit 变更集

幸运的是,Chrome、Edge 和 Firefox 确实会抛出此错误。如果您使用的是其他浏览器,则需要自行进行测试。尽管您可能不是为真实媒体播放器构建的应用,但弗朗索瓦·博福特的来源缓冲区限制测试至少可以让您观察这种行为。

我可以附加多少数据?

确切数字因浏览器而异。由于您无法查询当前附加的数据量,因此必须跟踪自己附加的数据量。至于要观看的内容,这里是 我在撰写本文时所能搜集的最优质数据对于 Chrome 来说,这些数字是上限,这意味着当系统遇到内存紧张时,这些数字可以减小。

Chrome Chromecast* Firefox Safari Edge
视频 150MB 30MB 100MB 290MB 未知
音频 12MB 2MB 15MB 14MB 未知
  • 或其他内存有限的 Chrome 设备。

我该怎么办?

由于支持的数据量变化很大,而您无法在 SourceBuffer 中找到多少数据量,因此必须通过处理 QuotaExceededError 来间接获取这些数据。下面我们来了解几种实现此目的的方法。

您可以通过多种方法处理 QuotaExceededError。实际上,最好结合使用一种或多种方法。您应该根据提取多少数据来执行工作,并尝试附加到 HTMLMediaElement.currentTime 之外,并根据 QuotaExceededError 调整该大小。此外,使用某种类型的清单(例如 mpd 文件 (MPEG-DASH) 或 m3u8 文件 (HLS)) 也可帮助您跟踪要附加到缓冲区的数据。

现在,我们来看看处理 QuotaExceededError 的几种方法。

  • 移除不需要的数据并重新附加。
  • 附加较小的 fragment。
  • 降低播放分辨率。

虽然它们可以组合使用,但我一次介绍一个。

移除不需要的数据并重新附加内容

实际上,这应该叫做“移除最不可能使用的数据,然后重新尝试附加可能即将使用的数据”。标题太长。 你只需记住我的意思就行了。

移除近期数据并不像调用 SourceBuffer.remove() 那样简单。如需从 SourceBuffer 中移除数据,其更新标志必须为 false。否则,请先调用 SourceBuffer.abort(),然后再移除任何数据。

调用 SourceBuffer.remove() 时需要注意以下几点。

  • 这可能会对视频播放产生负面影响。例如,如果您想让视频很快重放或循环播放,则可能不需要移除视频开头。同样,如果您或用户尝试转到视频中已移除数据的部分,您必须重新附加该数据才能满足该要求。
  • 尽可能保守地移除广告。请注意移除从 currentTime 处的关键帧开始处或之前的当前播放帧组,因为这样做可能会导致播放停滞。如果清单中没有此类信息,可能需要由 Web 应用从字节流中解析出来。媒体清单或应用了解媒体中的关键帧间隔有助于引导应用选择移除范围,以防止移除当前正在播放的媒体。无论您移除哪些内容,都不要移除当前正在播放的图片组,甚至不要移除除此之外的前几组图片。一般来说,除非您确定不再需要相应媒体,否则不要在当前时间之后移除相应内容。如果在进度条指针附近移开,可能会导致失灵。
  • Safari 9 和 Safari 10 未正确实现 SourceBuffer.abort()。事实上,它们会抛出错误,从而停止播放。幸运的是,您可以点击此处此处查看开放的 bug 跟踪器。在此期间,您必须以某种方式解决此问题。Shaka Player 做到了,方法是在这些版本的 Safari 上移除一个空的 abort() 函数。

附加较小的 fragment

具体流程如下所示。这可能并不适用于所有情况,但好处在于可以根据需要调整较小分块的大小。此外,它也不需要返回到网络,某些用户可能会产生额外的流量费用。

const pieces = new Uint8Array([data]);
(function appendFragments(pieces) {
    if (sourceBuffer.updating) {
    return;
    }
    pieces.forEach(piece => {
    try {
        sourceBuffer.appendBuffer(piece);
    }
    catch e {
        if (e.name !== 'QuotaExceededError') {
        throw e;
        }

        // Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
        const reduction = pieces[0].byteLength * 0.8;
        if (reduction / data.byteLength < 0.04) {
        throw new Error('MediaSource threw QuotaExceededError too many times');
        }
        const newPieces = [
        pieces[0].slice(0, reduction),
        pieces[0].slice(reduction, pieces[0].byteLength)
        ];
        pieces.splice(0, 1, newPieces[0], newPieces[1]);
        appendBuffer(pieces);  
    }
    });
})(pieces);

降低播放分辨率

这类似于移除近期数据并重新附加数据。事实上,两者可以同时完成,但下面的示例仅显示了降低分辨率的过程。

使用此方法时,有几点需要注意:

  • 您必须附加一个新的初始化段。每次更改表示法时,都必须执行此操作。新的初始化段必须用于随后的媒体段。
  • 附加媒体的呈现时间戳应尽可能与缓冲区中数据的时间戳相匹配,但不得快进。 重叠的已缓冲数据可能会导致卡顿或短暂停顿,具体取决于浏览器。无论您附加什么内容,都不要与进度条指针重叠,因为这样会抛出错误。
  • 定位操作可能会中断播放。您可能会想要跳转到特定位置,然后从该位置继续播放。请注意,这将会导致播放中断,直到跳转完成为止。