UNPKG

shaka-player

Version:
319 lines (277 loc) 12.1 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.polyfill.MCapEncryptionScheme'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.device.DeviceFactory'); goog.require('shaka.polyfill'); goog.require('shaka.polyfill.EmeEncryptionSchemePolyfillMediaKeySystemAccess'); goog.require('shaka.polyfill.EncryptionSchemeUtils'); /** * A polyfill to add support for EncryptionScheme queries in MediaCapabilities. * * Because this polyfill can't know what schemes the UA or CDM actually support, * it assumes support for the historically-supported schemes of each well-known * key system. * * @see https://wicg.github.io/encrypted-media-encryption-scheme/ * @see https://github.com/w3c/encrypted-media/pull/457 * @export */ shaka.polyfill.MCapEncryptionScheme = class { /** * Installs the polyfill. To avoid the possibility of extra user prompts, * this will shim MC so long as it exists, without checking support for * encryptionScheme upfront. The support check will happen on-demand the * first time MC is used. * * @export */ static install() { const device = shaka.device.DeviceFactory.getDevice(); if (!device.supportsEncryptionSchemePolyfill()) { return; } const logPrefix = 'McEncryptionSchemePolyfill:'; if (shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_ || navigator.mediaCapabilitiesEncryptionSchemePolyfilled) { shaka.log.debug(logPrefix, 'Already installed.'); return; } if (!navigator.mediaCapabilities) { shaka.log.debug(logPrefix, 'MediaCapabilities not found'); // No MediaCapabilities. return; } // Save the original. shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_ = navigator.mediaCapabilities.decodingInfo; // Patch in a method which will check for support on the first call. shaka.log.debug(logPrefix, 'Waiting to detect encryptionScheme support.'); navigator.mediaCapabilities.decodingInfo = shaka.polyfill.MCapEncryptionScheme.probeDecodingInfo_; // Mark MediaCapabilities as polyfilled. This keeps us from running into // conflicts between multiple versions of this (compiled Shaka lib vs // uncompiled source). navigator.mediaCapabilitiesEncryptionSchemePolyfilled = true; } /** * A shim for mediaCapabilities.decodingInfo to check for encryptionScheme * support. Only used until we know if the browser has native support for the * encryptionScheme field. * * @this {MediaCapabilities} * @param {!MediaDecodingConfiguration} requestedConfiguration The requested * decoding configuration. * @return {!Promise<!MediaCapabilitiesDecodingInfo>} A Promise to a result * describing the capabilities of the browser in the request configuration. * @private */ static async probeDecodingInfo_(requestedConfiguration) { const logPrefix = 'McEncryptionSchemePolyfill:'; goog.asserts.assert(this == navigator.mediaCapabilities, 'bad "this" for decodingInfo'); // Call the original version. If the call succeeds, we look at the result // to decide if the encryptionScheme field is supported or not. const capabilities = // eslint-disable-next-line no-restricted-syntax await shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_.call( this, requestedConfiguration); // If the config is not supported, we don't need to try anything else. if (!capabilities.supported) { return capabilities; } if (!requestedConfiguration.keySystemConfiguration) { // This was not a query regarding encrypted content. The results are // valid, but won't tell us anything about native support for // encryptionScheme. Just return the results. return capabilities; } const mediaKeySystemAccess = capabilities.keySystemAccess; const hasEncryptionScheme = shaka.polyfill.EncryptionSchemeUtils .hasEncryptionScheme(mediaKeySystemAccess); if (hasEncryptionScheme) { // The browser supports the encryptionScheme field! // No need for a patch. Revert back to the original implementation. shaka.log.debug(logPrefix, 'Native encryptionScheme support found.'); navigator.mediaCapabilities.decodingInfo = shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_; // Return the results, which are completely valid. return capabilities; } // If we land here, either the browser does not support the // encryptionScheme field, or the browser does not support EME-related // fields in MCap _at all_. // First, install a patch to check the mediaKeySystemAccess or // encryptionScheme field in future calls. shaka.log.debug(logPrefix, 'No native encryptionScheme support found. '+ 'Patching encryptionScheme support.'); navigator.mediaCapabilities.decodingInfo = shaka.polyfill.MCapEncryptionScheme.polyfillDecodingInfo_; // Second, if _none_ of the EME-related fields of MCap are supported, fill // them in now before returning the results. if (!mediaKeySystemAccess) { capabilities.keySystemAccess = await shaka.polyfill.MCapEncryptionScheme.getMediaKeySystemAccess_( requestedConfiguration); return capabilities; } // If we land here, it's only the encryption scheme field that is missing. // The results we have may not be valid, since they didn't account for // encryption scheme. Run the query again through our polyfill. // eslint-disable-next-line no-restricted-syntax return shaka.polyfill.MCapEncryptionScheme.polyfillDecodingInfo_.call( this, requestedConfiguration); } /** * A polyfill for mediaCapabilities.decodingInfo to handle the * encryptionScheme field in browsers that don't support it. It uses the * user-agent string to guess what encryption schemes are supported, then * those guesses are used to reject unsupported schemes. * * @this {MediaCapabilities} * @param {!MediaDecodingConfiguration} requestedConfiguration The requested * decoding configuration. * @return {!Promise<!MediaCapabilitiesDecodingInfo>} A Promise to a result * describing the capabilities of the browser in the request configuration. * @private */ static async polyfillDecodingInfo_(requestedConfiguration) { goog.asserts.assert(this == navigator.mediaCapabilities, 'bad "this" for decodingInfo'); let videoScheme = null; let audioScheme = null; if (requestedConfiguration.keySystemConfiguration) { const keySystemConfig = requestedConfiguration.keySystemConfiguration; const keySystem = keySystemConfig.keySystem; audioScheme = keySystemConfig.audio && keySystemConfig.audio.encryptionScheme; videoScheme = keySystemConfig.video && keySystemConfig.video.encryptionScheme; const supportedScheme = shaka.polyfill.EncryptionSchemeUtils.guessSupportedScheme(keySystem); const notSupportedResult = { powerEfficient: false, smooth: false, supported: false, keySystemAccess: null, configuration: requestedConfiguration, }; if (!shaka.polyfill.EncryptionSchemeUtils.checkSupportedScheme( audioScheme, supportedScheme)) { return notSupportedResult; } if (!shaka.polyfill.EncryptionSchemeUtils.checkSupportedScheme( videoScheme, supportedScheme)) { return notSupportedResult; } } // At this point, either it's unencrypted or we assume the encryption scheme // is supported. So delegate to the original decodingInfo() method. const capabilities = // eslint-disable-next-line no-restricted-syntax await shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_.call( this, requestedConfiguration); if (capabilities.keySystemAccess) { // If the result is supported and encrypted, this will be a // MediaKeySystemAccess instance. Wrap the MKSA object in ours to provide // the missing field in the returned configuration. capabilities.keySystemAccess = new shaka.polyfill.EmeEncryptionSchemePolyfillMediaKeySystemAccess( capabilities.keySystemAccess, videoScheme, audioScheme); } else if (requestedConfiguration.keySystemConfiguration) { // If the result is supported and the content is encrypted, we should have // a MediaKeySystemAccess instance as part of the result. If we land // here, the browser doesn't support the EME-related fields of MCap. capabilities.keySystemAccess = await shaka.polyfill.MCapEncryptionScheme.getMediaKeySystemAccess_( requestedConfiguration); } return capabilities; } /** * Call navigator.requestMediaKeySystemAccess to get the MediaKeySystemAccess * information. * * @param {!MediaDecodingConfiguration} requestedConfiguration The requested * decoding configuration. * @return {!Promise<!MediaKeySystemAccess>} A Promise to a * MediaKeySystemAccess instance. * @private */ static async getMediaKeySystemAccess_(requestedConfiguration) { const mediaKeySystemConfig = shaka.polyfill.MCapEncryptionScheme.convertToMediaKeySystemConfig_( requestedConfiguration); const keySystemAccess = await navigator.requestMediaKeySystemAccess( requestedConfiguration.keySystemConfiguration.keySystem, [mediaKeySystemConfig]); return keySystemAccess; } /** * Convert the MediaDecodingConfiguration object to a * MediaKeySystemConfiguration object. * * @param {!MediaDecodingConfiguration} decodingConfig The decoding * configuration. * @return {!MediaKeySystemConfiguration} The converted MediaKeys * configuration. * @private */ static convertToMediaKeySystemConfig_(decodingConfig) { const mediaCapKeySystemConfig = decodingConfig.keySystemConfiguration; const audioCapabilities = []; const videoCapabilities = []; if (mediaCapKeySystemConfig.audio) { const capability = { robustness: mediaCapKeySystemConfig.audio.robustness || '', contentType: decodingConfig.audio.contentType, encryptionScheme: mediaCapKeySystemConfig.audio.encryptionScheme, }; audioCapabilities.push(capability); } if (mediaCapKeySystemConfig.video) { const capability = { robustness: mediaCapKeySystemConfig.video.robustness || '', contentType: decodingConfig.video.contentType, encryptionScheme: mediaCapKeySystemConfig.video.encryptionScheme, }; videoCapabilities.push(capability); } const initDataTypes = mediaCapKeySystemConfig.initDataType ? [mediaCapKeySystemConfig.initDataType] : []; /** @type {!MediaKeySystemConfiguration} */ const mediaKeySystemConfig = { initDataTypes: initDataTypes, distinctiveIdentifier: mediaCapKeySystemConfig.distinctiveIdentifier, persistentState: mediaCapKeySystemConfig.persistentState, sessionTypes: mediaCapKeySystemConfig.sessionTypes, }; // Only add the audio video capabilities if they have valid data. // Otherwise the query will fail. if (audioCapabilities.length) { mediaKeySystemConfig.audioCapabilities = audioCapabilities; } if (videoCapabilities.length) { mediaKeySystemConfig.videoCapabilities = videoCapabilities; } return mediaKeySystemConfig; } }; /** * The original decodingInfo, before we patched it. * * @type { * function(this:MediaCapabilities, * !MediaDecodingConfiguration * ):!Promise<!MediaCapabilitiesDecodingInfo> * } * @private */ shaka.polyfill.MCapEncryptionScheme.originalDecodingInfo_; // Install at a low priority so that other EME polyfills go first. shaka.polyfill.register(shaka.polyfill.MCapEncryptionScheme.install, -2);