From 86f77fe0bcad74d6434773233da5fbcec92cc9fa Mon Sep 17 00:00:00 2001 From: Brandon Casey <2381475+brandonocasey@users.noreply.github.com> Date: Tue, 16 Mar 2021 10:46:30 -0400 Subject: [PATCH] fix: catch remove errors, remove all data on QUOTA_EXCEEDED (#1101) --- src/segment-loader.js | 35 +++++++++++++++++------------------ src/source-updater.js | 6 +++++- test/segment-loader.test.js | 23 ++++++++++++----------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/segment-loader.js b/src/segment-loader.js index 65fd67e2e..9fec008bb 100644 --- a/src/segment-loader.js +++ b/src/segment-loader.js @@ -1099,9 +1099,10 @@ export default class SegmentLoader extends videojs.EventTarget { * @param {number} start - the start time of the region to remove from the buffer * @param {number} end - the end time of the region to remove from the buffer * @param {Function} [done] - an optional callback to be executed when the remove + * @param {boolean} force - force all remove operations to happen * operation is complete */ - remove(start, end, done = () => {}) { + remove(start, end, done = () => {}, force = false) { // clamp end to duration if we need to remove everything. // This is due to a browser bug that causes issues if we remove to Infinity. // videojs/videojs-contrib-hls#1225 @@ -1124,7 +1125,7 @@ export default class SegmentLoader extends videojs.EventTarget { } }; - if (!this.audioDisabled_) { + if (force || !this.audioDisabled_) { removesRemaining++; this.sourceUpdater_.removeAudio(start, end, removeFinished); } @@ -1137,7 +1138,7 @@ export default class SegmentLoader extends videojs.EventTarget { // the event that we're switching between renditions and from video to audio only // (when we add support for that), we may need to clear the video contents despite // what the new media will contain. - if (this.loaderType_ === 'main') { + if (force || this.loaderType_ === 'main') { this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); removesRemaining++; this.sourceUpdater_.removeVideo(start, end, removeFinished); @@ -2129,21 +2130,19 @@ export default class SegmentLoader extends videojs.EventTarget { // before retrying. const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER; - this.logger_(`On QUOTA_EXCEEDED_ERR, removing video from 0 to ${timeToRemoveUntil}`); - this.sourceUpdater_.removeVideo(0, timeToRemoveUntil, () => { - this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio from 0 to ${timeToRemoveUntil}`); - this.sourceUpdater_.removeAudio(0, timeToRemoveUntil, () => { - this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`); - this.waitingOnRemove_ = false; - // wait the length of time alotted in the back buffer to prevent wasted - // attempts (since we can't clear less than the minimum) - this.quotaExceededErrorRetryTimeout_ = window.setTimeout(() => { - this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue'); - this.quotaExceededErrorRetryTimeout_ = null; - this.processCallQueue_(); - }, MIN_BACK_BUFFER * 1000); - }); - }); + this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`); + this.remove(0, timeToRemoveUntil, () => { + + this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`); + this.waitingOnRemove_ = false; + // wait the length of time alotted in the back buffer to prevent wasted + // attempts (since we can't clear less than the minimum) + this.quotaExceededErrorRetryTimeout_ = window.setTimeout(() => { + this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue'); + this.quotaExceededErrorRetryTimeout_ = null; + this.processCallQueue_(); + }, MIN_BACK_BUFFER * 1000); + }, true); } handleAppendError_({segmentInfo, type, bytes}, error) { diff --git a/src/source-updater.js b/src/source-updater.js index 635224718..85309227d 100644 --- a/src/source-updater.js +++ b/src/source-updater.js @@ -170,7 +170,11 @@ const actions = { } sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`); - sourceBuffer.remove(start, end); + try { + sourceBuffer.remove(start, end); + } catch (e) { + sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`); + } }, timestampOffset: (offset) => (type, sourceUpdater) => { const sourceBuffer = sourceUpdater[`${type}Buffer`]; diff --git a/test/segment-loader.test.js b/test/segment-loader.test.js index 4c80b1852..25b004ada 100644 --- a/test/segment-loader.test.js +++ b/test/segment-loader.test.js @@ -4174,22 +4174,23 @@ QUnit.module('SegmentLoader', function(hooks) { QUnit.test('QUOTA_EXCEEDED_ERR triggers error if no room for single segment', function(assert) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { - const playlist = playlistWithDuration(40); - - loader.playlist(playlist); - loader.load(); - this.clock.tick(1); - - loader.sourceUpdater_.appendBuffer = ({type, bytes}, callback) => { - callback({type: 'QUOTA_EXCEEDED_ERR', code: QUOTA_EXCEEDED_ERR}); - }; + return new Promise((resolve, reject) => { + const playlist = playlistWithDuration(40); - standardXHRResponse(this.requests.shift(), muxedSegment()); + loader.playlist(playlist); + loader.load(); + this.clock.tick(1); - return new Promise((resolve, reject) => { // appenderrors are fatal, we don't want them in this case loader.one('appenderror', reject); loader.one('error', resolve); + + loader.sourceUpdater_.appendBuffer = ({type, bytes}, callback) => { + callback({type: 'QUOTA_EXCEEDED_ERR', code: QUOTA_EXCEEDED_ERR}); + }; + + standardXHRResponse(this.requests.shift(), muxedSegment()); + }); }).then(() => { // buffer was empty, meaning there wasn't room for a single segment from that