如果您使用媒體來源擴充功能 (MSE),最終需要處理的問題之一就是緩衝區過大。發生這種情況時,您會收到所謂的 QuotaExceededError
。本文將介紹一些處理方式。
什麼是 QuotaExceededError?
基本上,如果您嘗試在 SourceBuffer
物件中加入過多資料,就會得到 QuotaExceededError
。(將更多 SourceBuffer
物件新增至父項 MediaSource
元素也會擲回此錯誤。這不在本文的討論範圍內)。如果 SourceBuffer
中的資料過多,呼叫 SourceBuffer.appendBuffer()
會在 Chrome 資訊主頁視窗中觸發下列訊息。
![配額控制台錯誤。](https://developer.chrome.google.cn/static/blog/quotaexceedederror/image/quota-console-error-1898f4c0fdf15.png?hl=zh-tw)
這裡有幾點需要注意。首先,請注意訊息中沒有出現 QuotaExceededError
這個名稱。如要查看這項資訊,請在可擷取錯誤的位置設定中斷點,然後在監控或範圍視窗中檢查。我已在下方顯示這項資訊。
![配額監控視窗。](https://developer.chrome.google.cn/static/blog/quotaexceedederror/image/quota-watch-window-78bfbd50fe4e6.png?hl=zh-tw)
其次,沒有任何確切的方法可以找出 SourceBuffer
可以處理的資料量。
其他瀏覽器的行為
在撰寫本文時,Safari 在許多版本中並未擲回 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
。
- 移除不必要的資料,然後重新附加。
- 附加較小的片段。
- 降低播放解析度。
雖然可以同時使用這兩種方法,但我會逐一介紹。
移除不必要的資料並重新附加
這個方法其實應該稱為「移除最不可能很快就會使用的資料,然後重試附加可能很快就會使用的資料」。標題過長。 你只需要記得我真正的意思。
移除近期資料並非像呼叫 SourceBuffer.remove()
那麼簡單。如要從 SourceBuffer
移除資料,其更新標記必須為 false。如果不是,請先呼叫 SourceBuffer.abort()
,再移除任何資料。
呼叫 SourceBuffer.remove()
時,請留意下列事項。
- 這可能會對播放造成負面影響。舉例來說,如果想讓影片很快重播或循環播放,就不要移除影片開頭。同樣地,如果您或使用者尋找的影片片段已移除資料,您必須再次附加該資料,才能滿足該尋找動作。
- 盡可能保留原始內容。請注意,如果從
currentTime
或之前的主影格開始移除目前播放的幀組,可能會導致播放中斷。如果資訊清單中沒有這類資訊,網頁應用程式可能需要從位元組串流中剖析這類資訊。媒體資訊清單或應用程式對媒體中關鍵影格間隔的瞭解,有助於引導應用程式選擇移除範圍,避免移除目前播放的媒體。無論您移除哪些內容,請勿移除目前播放的圖片群組,甚至是前幾張圖片。一般來說,除非您確定不再需要媒體,否則請勿移除超過目前時間點的資料。如果移除靠近播放頭的內容,可能會導致播放停止。 - Safari 9 和 Safari 10 未正確實作
SourceBuffer.abort()
。事實上,它們會擲回錯誤,導致播放作業停止。幸好,我們有這裡和這裡的開放式錯誤追蹤器。在此期間,您必須以某種方式解決這個問題。Shaka Player 會在這些版本的 Safari 上模擬空白的abort()
函式。
附加較小的片段
我已在下方說明程序。這項做法可能不適用於所有情況,但優點是較小的區塊大小可視需求調整。而且不需要回傳網路,這可能會為部分使用者產生額外的資料費用。
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);
降低播放解析度
這與移除最近的資料並重新附加資料類似。事實上,這兩種做法可以同時進行,但下方的範例只會顯示降低解析度。
使用這項技巧時,請留意下列事項:
- 您必須附加新的初始化區段。您必須在每次變更表示法時執行這項操作。新的初始化區段必須適用於後續的媒體區段。
- 附加媒體的呈現時間戳記應盡可能與緩衝區中資料的時間戳記相符,但不得超前。重疊緩衝資料可能會導致斷斷續續或短暫停頓,具體情況視瀏覽器而定。無論您附加什麼,請勿重疊播放頭,否則會擲回錯誤。
- 尋找可能會中斷播放。您可能會想跳到特定位置,然後從該位置繼續播放。請注意,這會導致播放中斷,直到搜尋完成為止。