如果您使用的是媒体源扩展 (MSE),最终需要处理的一个问题就是缓冲区过满。出现这种情况时,您会收到所谓的 QuotaExceededError
。在本文中,我将介绍一些处理此问题的方法。
什么是 QuotaExceededError?
基本上,如果您尝试向 SourceBuffer
对象添加过多数据,就会得到 QuotaExceededError
。(向父 MediaSource
元素添加更多 SourceBuffer
对象也可能会抛出此错误。这不在本文的讨论范围之内。)如果 SourceBuffer
中的数据过多,调用 SourceBuffer.appendBuffer()
会在 Chrome 控制台窗口中触发以下消息。
关于此事,有几点需要注意。首先,请注意消息中未出现名称 QuotaExceededError
。如需查看,请在您可以捕获错误的位置设置断点,然后在监视窗口或作用域窗口中检查该错误。我已在下方展示了此问题。
其次,没有明确的方法来计算 SourceBuffer
可以处理的数据量。
在其他浏览器中的行为
在撰写本文时,Safari 的许多 build 都不会抛出 QuotaExceededError
。而是使用两步算法移除帧,如果有足够的空间来处理 appendBuffer()
,则会停止。首先,它会以 30 秒的数据块来释放当前时间之前 0 到 30 秒之间的帧。接下来,它会以 30 秒的块为单位释放帧,从 currentTime
后的时长回放近 30 秒。如需了解详情,请参阅 2014 年的 WebKit 更改集。
幸运的是,Chrome、Edge 和 Firefox 确实会抛出此错误。如果您使用的是其他浏览器,则需要自行进行测试。虽然这可能不是您为真实媒体播放器构建的代码,但 François Beaufort 的源缓冲区限制测试至少可以让您观察行为。
我可以附加多少数据?
具体数量因浏览器而异。由于您无法查询当前附加的数据量,因此您必须跟踪自己附加的数据量。至于要注意什么,下面是我在撰写本文时能收集到的最佳数据。对于 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
或之前的关键帧开始的当前正在播放的一组帧,因为这样做可能会导致播放中断。如果清单中不提供此类信息,网络应用可能需要从字节流中解析出此类信息。媒体清单或应用对媒体中关键帧间隔的了解有助于指导应用选择移除范围,以防止移除当前正在播放的媒体。无论您移除哪些内容,请勿移除当前正在播放的照片组,甚至不要移除之后的前几张照片。一般来说,除非您确定不再需要相应媒体,否则请勿移除当前时间之后的媒体。如果您在播放头附近移除,可能会导致卡顿。 - 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);
降低播放分辨率
这类似于移除近期数据并重新附加。实际上,两者可以一起完成,但以下示例仅显示了降低分辨率。
使用此技术时,请注意以下几点:
- 您必须附加一个新的初始化段。每当您更改表示法时,都必须执行此操作。新的初始化片段必须适用于后续的媒体片段。
- 附加媒体的呈现时间戳应尽可能与缓冲区中数据的时间戳匹配,但不能向前跳转。 重叠缓冲数据可能会导致卡顿或短暂停顿,具体取决于浏览器。无论您附加什么内容,请勿与播放头重叠,否则会抛出错误。
- 跳转可能会中断播放。您可能想要跳转到特定位置并从该位置继续播放。请注意,这将导致播放中断,直到跳转完成。