diff --git a/packages/griffith-mp4/src/mse/controller.js b/packages/griffith-mp4/src/mse/controller.js index aa2ca835..3bad649d 100644 --- a/packages/griffith-mp4/src/mse/controller.js +++ b/packages/griffith-mp4/src/mse/controller.js @@ -1,8 +1,10 @@ +import {ua} from 'griffith-utils' import FragmentFetch from '../fetch' import MP4Parse from '../mp4/mp4Parse' import MP4Probe from '../mp4/mp4Probe' import FMP4 from '../fmp4/fmp4Generator' import {concatTypedArray} from '../fmp4/utils' +import {abortPolyfill} from './polyfill' const MAGIC_NUMBER = 20000 @@ -60,7 +62,16 @@ export default class MSE { handleAppendBuffer = (buffer, type) => { if (this.mediaSource.readyState === 'open') { - this.sourceBuffers[type].appendBuffer(buffer) + try { + this.sourceBuffers[type].appendBuffer(buffer) + } catch (error) { + // see https://developers.google.com/web/updates/2017/10/quotaexceedederror + if (error.name === 'QuotaExceededError') { + this.handleQuotaExceededError(buffer, type) + } else { + throw error + } + } } else { this[`${type}Queue`].push(buffer) } @@ -201,22 +212,41 @@ export default class MSE { changeQuality(newSrc) { this.src = newSrc this.qualityChangeFlag = true - this.removeBuffer() - - this.init().then(() => { - this.video.currentTime = this.video.currentTime - }) - } - removeBuffer() { + // remove old quality buffer before append new quality buffer for (const key in this.sourceBuffers) { const track = this.sourceBuffers[key] const length = track.buffered.length if (length > 0) { - track.remove(track.buffered.start(0), track.buffered.end(length - 1)) + this.removeBuffer( + track.buffered.start(0), + track.buffered.end(length - 1), + key + ) + } + } + + this.init().then(() => { + this.video.currentTime = this.video.currentTime + }) + } + + removeBuffer(start, end, type) { + const track = this.sourceBuffers[type] + if (track.updating) { + const {isSafari} = ua + + if (isSafari) { + // Safari 9/10/11/12 does not correctly implement abort() on SourceBuffer. + // Calling abort() before appending a segment causes that segment to be + // incomplete in buffer. + // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342 + abortPolyfill() } + track.abort() } + track.remove(start, end) } loadData(start = 0, end = MAGIC_NUMBER) { @@ -271,4 +301,16 @@ export default class MSE { this.mediaSource.endOfStream() } } + + handleQuotaExceededError = (buffer, type) => { + for (const key in this.sourceBuffers) { + const track = this.sourceBuffers[key] + + const currentTime = this.video.currentTime + this.removeBuffer(track.buffered.start(0) + 10, currentTime - 10, key) + } + + // re-append(maybe should lower the playback resolution) + this.handleAppendBuffer(buffer, type) + } } diff --git a/packages/griffith-mp4/src/mse/polyfill.js b/packages/griffith-mp4/src/mse/polyfill.js new file mode 100644 index 00000000..476b74f6 --- /dev/null +++ b/packages/griffith-mp4/src/mse/polyfill.js @@ -0,0 +1,11 @@ +// copy from https://github.com/google/shaka-player/blob/master/lib/polyfill/mediasource.js#L125 +function abortPolyfill() { + const addSourceBuffer = MediaSource.prototype.addSourceBuffer + MediaSource.prototype.addSourceBuffer = function(...varArgs) { + const sourceBuffer = addSourceBuffer.apply(this, varArgs) + sourceBuffer.abort = function() {} // Stub out for buggy implementations. + return sourceBuffer + } +} + +export {abortPolyfill}