UNPKG

shaka-player

Version:
215 lines (182 loc) 7.52 kB
/** * @license * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ goog.provide('shaka.polyfill.MediaSource'); goog.require('shaka.log'); goog.require('shaka.polyfill.register'); goog.require('shaka.util.MimeUtils'); goog.require('shaka.util.Platform'); /** * @namespace shaka.polyfill.MediaSource * * @summary A polyfill to patch MSE bugs. */ /** * Install the polyfill if needed. */ shaka.polyfill.MediaSource.install = function() { shaka.log.debug('MediaSource.install'); // MediaSource bugs are difficult to detect without checking for the affected // platform. SourceBuffer is not always exposed on window, for example, and // instances are only accessible after setting up MediaSource on a video // element. Because of this, we use UA detection and other platform detection // tricks to decide which patches to install. const safariVersion = shaka.util.Platform.safariVersion(); if (!window.MediaSource) { shaka.log.info('No MSE implementation available.'); } else if (window.cast && cast.__platform__ && cast.__platform__.canDisplayType) { shaka.log.info('Patching Chromecast MSE bugs.'); // Chromecast cannot make accurate determinations via isTypeSupported. shaka.polyfill.MediaSource.patchCastIsTypeSupported_(); } else if (safariVersion) { // 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 (safariVersion <= 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 & 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.isTizen2() || shaka.util.Platform.isTizen3() || shaka.util.Platform.isTizen4()) { // Tizen's implementation of MSE does not work well with opus. To prevent // the player from trying to play opus on Tizen, we will override media // source to always reject opus content. shaka.polyfill.MediaSource.rejectCodec_('opus'); } else { shaka.log.info('Using native MSE as-is.'); } }; /** * Blacklist the current browser by removing media source. A side-effect of this * will be to make |shaka.util.Platform.supportsMediaSource| return |false|. * * @private */ shaka.polyfill.MediaSource.blacklist_ = function() { window['MediaSource'] = null; }; /** * Stub out abort(). On some buggy MSE implementations, calling abort() causes * various problems. * * @private */ shaka.polyfill.MediaSource.stubAbort_ = function() { const addSourceBuffer = MediaSource.prototype.addSourceBuffer; MediaSource.prototype.addSourceBuffer = function(...varArgs) { let sourceBuffer = addSourceBuffer.apply(this, varArgs); sourceBuffer.abort = function() {}; // Stub out for buggy implementations. return sourceBuffer; }; }; /** * Patch remove(). On Safari 11, if you call remove() to remove the content up * to a keyframe, Safari will also remove the keyframe and all of the data up to * the next one. For example, if the keyframes are at 0s, 5s, and 10s, and you * tried to remove 0s-5s, it would instead remove 0s-10s. * * Offsetting the end of the range seems to be a usable workaround. * * @private */ shaka.polyfill.MediaSource.patchRemovalRange_ = function() { const originalRemove = SourceBuffer.prototype.remove; SourceBuffer.prototype.remove = function(startTime, endTime) { return originalRemove.call(this, startTime, endTime - 0.001); }; }; /** * Patch isTypeSupported() to reject TS content. Used to avoid TS-related MSE * bugs on Safari. * * @private */ shaka.polyfill.MediaSource.rejectTsContent_ = function() { const originalIsTypeSupported = MediaSource.isTypeSupported; MediaSource.isTypeSupported = function(mimeType) { // Parse the basic MIME type from its parameters. let pieces = mimeType.split(/ *; */); let basicMimeType = pieces[0]; let container = basicMimeType.split('/')[1]; if (container.toLowerCase() == 'mp2t') { return false; } return originalIsTypeSupported(mimeType); }; }; /** * Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used * when we know that we are on a platform that does not work well with a given * codec. * * @param {string} codec * @private */ shaka.polyfill.MediaSource.rejectCodec_ = function(codec) { const isTypeSupported = MediaSource.isTypeSupported; MediaSource.isTypeSupported = (mimeType) => { const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType); return actualCodec != codec && isTypeSupported(mimeType); }; }; /** * Patch isTypeSupported() to chain to a private API on the Chromecast which * can query for support of detailed content parameters. * * @private */ shaka.polyfill.MediaSource.patchCastIsTypeSupported_ = function() { const originalIsTypeSupported = MediaSource.isTypeSupported; MediaSource.isTypeSupported = function(mimeType) { // Parse the basic MIME type from its parameters. let pieces = mimeType.split(/ *; */); pieces.shift(); // Remove basic MIME type from pieces. const hasCodecs = pieces.some((piece) => piece.startsWith('codecs=')); if (!hasCodecs) { // Though the original reason for this special case was not documented, // it is presumed to be because the platform won't accept a MIME type // without codecs in canDisplayType. It is valid, however, in // isTypeSupported. return originalIsTypeSupported(mimeType); } // Only canDisplayType can check extended MIME type parameters on this // platform (such as frame rate, resolution, etc). // In previous versions of this polyfill, the MIME type parameters were // manipulated, filtered, or extended. This is no longer true, so we pass // the full MIME type to the platform as we received it. return cast.__platform__.canDisplayType(mimeType); }; }; shaka.polyfill.register(shaka.polyfill.MediaSource.install);