Skip to content

Commit

Permalink
Update MediaSource polyfills for Safari 13
Browse files Browse the repository at this point in the history
Safari 13 is out, and needs polyfills just like Safari 12 did
(although one fewer).  This assumes that Safari 13+ all need the same
polyfills (to avoid surprise failures when Safari 14 comes out).

This also raises the minimum version from Safari 9 to Safari 11, since
usage stats for Safari 9 and 10 are vanishingly small.  Dropping
support for Safari 9 and 10 allows us to delete a complicated polyfill
used for those versions.

Change-Id: I8f833d915215fc19a89e3aea292b67c5d677c779
  • Loading branch information
joeyparrish committed Oct 8, 2019
1 parent 9e92167 commit 65370b4
Showing 1 changed file with 21 additions and 136 deletions.
157 changes: 21 additions & 136 deletions lib/polyfill/mediasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,50 +46,45 @@ shaka.polyfill.MediaSource = class {
// Chromecast cannot make accurate determinations via isTypeSupported.
shaka.polyfill.MediaSource.patchCastIsTypeSupported_();
} else if (shaka.util.Platform.isApple()) {
const version = navigator.appVersion;
const match = navigator.appVersion.match(/Version\/(\d+)/);
const version = parseInt(match[1], /* base */ 10);

// TS content is broken on Safari in general.
// See https://github.com/google/shaka-player/issues/743
// and https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.rejectTsContent_();

if (version.includes('Version/8')) {
// Safari 8 does not implement appendWindowEnd. If we ignore the
// incomplete MSE implementation, some content (especially multi-period)
// will fail to play correctly. The best we can do is blacklist Safari
// 8.
shaka.log.info('Blacklisting Safari 8 MSE.');
shaka.polyfill.MediaSource.blacklist_();
} else if (version.includes('Version/9')) {
shaka.log.info('Patching Safari 9 MSE bugs.');
// Safari 9 does not correctly implement abort() on SourceBuffer.
// Calling abort() causes a decoder failure, rather than resetting the
// decode timestamp as called for by the spec.
if (version <= 10) {
// Safari 8 does not implement appendWindowEnd.
// Safari 9 & 10 do not correctly implement abort() on SourceBuffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=160316
shaka.polyfill.MediaSource.stubAbort_();
} else if (version.includes('Version/10')) {
shaka.log.info('Patching Safari 10 MSE bugs.');
// Safari 10 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
shaka.polyfill.MediaSource.stubAbort_();
// Safari 10 fires spurious 'updateend' events after endOfStream().
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165336
shaka.polyfill.MediaSource.patchEndOfStreamEvents_();
} else if (version.includes('Version/11') ||
version.includes('Version/12')) {
shaka.log.info('Patching Safari 11/12 MSE bugs.');
// Safari 11 does not correctly implement abort() on SourceBuffer.

// Blacklist these very outdated versions.
shaka.log.info('Blacklisting MSE on Safari <= 10.');
shaka.polyfill.MediaSource.blacklist_();
} else if (version <= 12) {
shaka.log.info('Patching Safari 11 & 12 MSE bugs.');
// Safari 11 & 12 do not correctly implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
// If you remove up to a keyframe, Safari 11 incorrectly will also

// If you remove up to a keyframe, Safari 11 & 12 incorrectly will also
// remove that keyframe and the content up to the next.
// Offsetting the end of the removal range seems to help.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
shaka.polyfill.MediaSource.patchRemovalRange_();
} else {
shaka.log.info('Patching Safari 13 MSE bugs.');
// Safari 13 does not correctly implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
}
} else if (shaka.util.Platform.isTizen()) {
// Tizen's implementation of MSE does not work well with opus. To prevent
Expand Down Expand Up @@ -151,116 +146,6 @@ shaka.polyfill.MediaSource = class {
};
}

/**
* Patch endOfStream() to get rid of 'updateend' events that should not fire.
* These extra events confuse MediaSourceEngine, which relies on correct
* events to manage SourceBuffer state.
*
* @private
*/
static patchEndOfStreamEvents_() {
// eslint-disable-next-line no-restricted-syntax
const endOfStream = MediaSource.prototype.endOfStream;
// eslint-disable-next-line no-restricted-syntax
MediaSource.prototype.endOfStream = function(...varArgs) {
// This bug manifests only when endOfStream() results in the truncation
// of the MediaSource's duration. So first we must calculate what the
// new duration will be.
let newDuration = 0;
for (const buffer of this.sourceBuffers || []) {
const bufferEnd = buffer.buffered.end(buffer.buffered.length - 1);
newDuration = Math.max(newDuration, bufferEnd);
}

// If the duration is going to be reduced, suppress the next 'updateend'
// event on each SourceBuffer.
if (!isNaN(this.duration) &&
newDuration < this.duration) {
this.ignoreUpdateEnd_ = true;
for (const buffer of this.sourceBuffers || []) {
buffer.eventSuppressed_ = false;
}
}

// eslint-disable-next-line no-restricted-syntax
return endOfStream.apply(this, varArgs);
};

let cleanUpHandlerInstalled = false;
// eslint-disable-next-line no-restricted-syntax
const addSourceBuffer = MediaSource.prototype.addSourceBuffer;
// eslint-disable-next-line no-restricted-syntax
MediaSource.prototype.addSourceBuffer = function(...varArgs) {
// After adding a new source buffer, add an event listener to allow us to
// suppress events.
// eslint-disable-next-line no-restricted-syntax
const sourceBuffer = addSourceBuffer.apply(this, varArgs);
sourceBuffer['mediaSource_'] = this;
sourceBuffer.addEventListener('updateend',
shaka.polyfill.MediaSource.ignoreUpdateEnd_, false);

if (!cleanUpHandlerInstalled) {
// If we haven't already, install an event listener to allow us to clean
// up listeners when MediaSource is torn down.
this.addEventListener('sourceclose',
shaka.polyfill.MediaSource.cleanUpListeners_, false);
cleanUpHandlerInstalled = true;
}
return sourceBuffer;
};
}

/**
* An event listener for 'updateend' which selectively suppresses the events.
*
* @see shaka.polyfill.MediaSource.patchEndOfStreamEvents_
*
* @param {Event} event
* @private
*/
static ignoreUpdateEnd_(event) {
const sourceBuffer = event.target;
const mediaSource = sourceBuffer['mediaSource_'];

if (mediaSource.ignoreUpdateEnd_) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
sourceBuffer.eventSuppressed_ = true;

for (const buffer of mediaSource.sourceBuffers) {
if (buffer.eventSuppressed_ == false) {
// More events need to be suppressed.
return;
}
}

// All events have been suppressed, all buffers are out of 'updating'
// mode. Stop suppressing events.
mediaSource.ignoreUpdateEnd_ = false;
}
}

/**
* An event listener for 'sourceclose' which cleans up listeners for
* 'updateend' to avoid memory leaks.
*
* @see shaka.polyfill.MediaSource.patchEndOfStreamEvents_
* @see shaka.polyfill.MediaSource.ignoreUpdateEnd_
*
* @param {Event} event
* @private
*/
static cleanUpListeners_(event) {
const mediaSource = /** @type {!MediaSource} */ (event.target);
for (const buffer of mediaSource.sourceBuffers || []) {
buffer.removeEventListener('updateend',
shaka.polyfill.MediaSource.ignoreUpdateEnd_, false);
}
mediaSource.removeEventListener('sourceclose',
shaka.polyfill.MediaSource.cleanUpListeners_, false);
}

/**
* Patch isTypeSupported() to reject TS content. Used to avoid TS-related MSE
* bugs on Safari.
Expand Down

0 comments on commit 65370b4

Please sign in to comment.