Skip to content

chore: Move eme-encryption-scheme-polyfill to the Shaka Player repo #8818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions build/types/polyfill
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Polyfills used to emulate missing browsers features.

+../../node_modules/eme-encryption-scheme-polyfill/index.js
+../../lib/polyfill/encryption_scheme.js
+../../lib/polyfill/eme_encryption_scheme.js
+../../lib/polyfill/encryption_scheme_media_key_system_access.js
+../../lib/polyfill/encryption_scheme_utils.js
+../../lib/polyfill/mcap_encryption_scheme.js
+../../lib/polyfill/mediasource.js
+../../lib/polyfill/media_capabilities.js
+../../lib/polyfill/patchedmediakeys_cert.js
Expand Down
2 changes: 0 additions & 2 deletions demo/cast_receiver/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
// Bootstrap the Shaka Player library through the Closure library.
'../../node_modules/google-closure-library/closure/goog/base.js',
'../../dist/deps.js',
// Compiled into Shaka Player, but outside of the Closure deps system.
'../../node_modules/eme-encryption-scheme-polyfill/index.js',
// This file contains goog.require calls for all exported library classes.
'../../shaka-player.uncompiled.js',
// These are the individual parts of the receiver app.
Expand Down
2 changes: 0 additions & 2 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@
'../dist/deps.js',
// This is required for goog.asserts.
'../lib/debug/asserts.js',
// Compiled into Shaka Player, but outside of the Closure deps system.
'../node_modules/eme-encryption-scheme-polyfill/index.js',
// This file contains goog.require calls for all exported library classes.
'../shaka-player.uncompiled.js',
// Enable less, the CSS pre-processor.
Expand Down
8 changes: 8 additions & 0 deletions externs/polyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@

/** @type {string} */
window.shakaMediaKeysPolyfill;


/** @type {boolean} */
navigator.emeEncryptionSchemePolyfilled;


/** @type {boolean} */
navigator.mediaCapabilitiesEncryptionSchemePolyfilled;
8 changes: 0 additions & 8 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,6 @@ module.exports = (config) => {
},
'node_modules/lcevc_dec.js/dist/lcevc_dec.min.js',

// EME encryption scheme polyfill, compiled into Shaka Player, but outside
// of the Closure deps system, so not in shaka-player.uncompiled.js. This
// is specifically the compiled, minified, cross-browser build of it. It
// is necessary to use the compiled version to avoid problems on older
// TVs.
// eslint-disable-next-line @stylistic/max-len
'node_modules/eme-encryption-scheme-polyfill/dist/eme-encryption-scheme-polyfill.js',

// load closure base, the deps tree, and the uncompiled library
'test/test/closure-boot.js',
'node_modules/google-closure-library/closure/goog/base.js',
Expand Down
7 changes: 7 additions & 0 deletions lib/device/abstract_device.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,13 @@ shaka.device.AbstractDevice = class {
return false;
}

/**
* @override
*/
supportsCbcsWithoutEncryptionSchemeSupport() {
return false;
}

/**
* @override
*/
Expand Down
7 changes: 7 additions & 0 deletions lib/device/chromecast.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ shaka.device.Chromecast = class extends shaka.device.AbstractDevice {
return false;
}

/**
* @override
*/
supportsCbcsWithoutEncryptionSchemeSupport() {
return true;
}

/**
* Check if the current platform is Vizio TV.
* @return {boolean}
Expand Down
12 changes: 12 additions & 0 deletions lib/device/default_browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ shaka.device.DefaultBrowser = class extends shaka.device.AbstractDevice {
return this.isWindows_.value() &&
this.getBrowserEngine() === shaka.device.IDevice.BrowserEngine.GECKO;
}

/**
* @override
*/
supportsCbcsWithoutEncryptionSchemeSupport() {
if (this.getBrowserEngine() === shaka.device.IDevice.BrowserEngine.GECKO) {
const version = this.getVersion();
return version !== null ?
version >= 100 : super.supportsCbcsWithoutEncryptionSchemeSupport();
}
return super.supportsCbcsWithoutEncryptionSchemeSupport();
}
};

shaka.device.DeviceFactory.registerDefaultDeviceFactory(
Expand Down
5 changes: 5 additions & 0 deletions lib/device/i_device.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ shaka.device.IDevice = class {
* @return {boolean}
*/
disableHEVCSupport() {}

/**
* @return {boolean}
*/
supportsCbcsWithoutEncryptionSchemeSupport() {}
};

/**
Expand Down
9 changes: 9 additions & 0 deletions lib/device/webos.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ shaka.device.WebOS = class extends shaka.device.AbstractDevice {
return false;
}

/**
* @override
*/
supportsCbcsWithoutEncryptionSchemeSupport() {
const version = this.getVersion();
return version !== null ?
version >= 6 : super.supportsCbcsWithoutEncryptionSchemeSupport();
}

/**
* @return {boolean}
* @private
Expand Down
263 changes: 263 additions & 0 deletions lib/polyfill/eme_encryption_scheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.polyfill.EmeEncryptionScheme');

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 EME.
*
* 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.EmeEncryptionScheme = class {
/**
* Installs the polyfill. To avoid the possibility of extra user prompts,
* this will shim EME so long as it exists, without checking support for
* encryptionScheme upfront. The support check will happen on-demand the
* first time EME is used.
*
* @export
*/
static install() {
// Skip polyfill for PlayStation 4 and SkyQ devices due to known crashes
// caused by unsupported encryptionScheme handling. These platforms do not
// require the polyfill, and forcing encryptionScheme processing can result
// in playback crashes.
const device = shaka.device.DeviceFactory.getDevice();
if (!device.supportsEncryptionSchemePolyfill()) {
return;
}

const logPrefix = 'EmeEncryptionSchemePolyfill:';

if (shaka.polyfill.EmeEncryptionScheme.originalRMKSA_ ||
navigator.emeEncryptionSchemePolyfilled) {
shaka.log.debug(logPrefix, 'Already installed.');
return;
}
if (!navigator.requestMediaKeySystemAccess ||
// eslint-disable-next-line no-restricted-syntax
!MediaKeySystemAccess.prototype.getConfiguration) {
shaka.log.debug(logPrefix, 'EME not found');
// No EME.
return;
}

// Save the original.
shaka.polyfill.EmeEncryptionScheme.originalRMKSA_ =
navigator.requestMediaKeySystemAccess;

// Patch in a method which will check for support on the first call.
shaka.log.debug(logPrefix, 'Waiting to detect encryptionScheme support.');
navigator.requestMediaKeySystemAccess =
shaka.polyfill.EmeEncryptionScheme.probeRMKSA_;

// Mark EME as polyfilled. This keeps us from running into conflicts
// between multiple versions of this (compiled Shaka lib vs
// uncompiled source).
navigator.emeEncryptionSchemePolyfilled = true;
}

/**
* A shim for navigator.requestMediaKeySystemAccess to check for
* encryptionScheme support. Only used until we know if the browser has
* native support for the encryptionScheme field.
*
* @this {Navigator}
* @param {string} keySystem The key system ID.
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations An
* array of supported configurations the application can use.
* @return {!Promise<!MediaKeySystemAccess>} A Promise to a
* MediaKeySystemAccess instance.
* @private
*/
static async probeRMKSA_(keySystem, supportedConfigurations) {
const logPrefix = 'EmeEncryptionSchemePolyfill:';

goog.asserts.assert(this == navigator,
'bad "this" for requestMediaKeySystemAccess');

// Call the original version. If the call succeeds, we look at the result
// to decide if the encryptionScheme field is supported or not.
const mediaKeySystemAccess =
// eslint-disable-next-line no-restricted-syntax
await shaka.polyfill.EmeEncryptionScheme.originalRMKSA_.call(
this, keySystem, supportedConfigurations);

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.requestMediaKeySystemAccess =
shaka.polyfill.EmeEncryptionScheme.originalRMKSA_;
// Return the results, which are completely valid.
return mediaKeySystemAccess;
}

// If we land here, the browser does _not_ support the encryptionScheme
// field. So we install another patch to check the encryptionScheme field
// in future calls.
shaka.log.debug(logPrefix, 'No native encryptionScheme support found. '+
'Patching encryptionScheme support.');

navigator.requestMediaKeySystemAccess =
shaka.polyfill.EmeEncryptionScheme.polyfillRMKSA_;

// The results we have may not be valid. Run the query again through our
// polyfill.
// eslint-disable-next-line no-restricted-syntax
return shaka.polyfill.EmeEncryptionScheme.polyfillRMKSA_.call(
this, keySystem, supportedConfigurations);
}

/**
* A polyfill for navigator.requestMediaKeySystemAccess 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 filter videoCapabilities and audioCapabilities
* and reject unsupported schemes.
*
* @this {Navigator}
* @param {string} keySystem The key system ID.
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations An
* array of supported configurations the application can use.
* @return {!Promise<!MediaKeySystemAccess>} A Promise to a
* MediaKeySystemAccess instance.
* @private
*/
static async polyfillRMKSA_(keySystem, supportedConfigurations) {
goog.asserts.assert(this == navigator,
'bad "this" for requestMediaKeySystemAccess');

const supportedScheme =
shaka.polyfill.EncryptionSchemeUtils.guessSupportedScheme(keySystem);

// Filter the application's configurations based on our guess of what
// encryption scheme is supported.
const filteredSupportedConfigurations = [];
for (const configuration of supportedConfigurations) {
const filteredVideoCapabilities =
shaka.polyfill.EmeEncryptionScheme.filterCapabilities_(
configuration.videoCapabilities, supportedScheme);
const filteredAudioCapabilities =
shaka.polyfill.EmeEncryptionScheme.filterCapabilities_(
configuration.audioCapabilities, supportedScheme);

if (configuration.videoCapabilities &&
configuration.videoCapabilities.length &&
!filteredVideoCapabilities.length) {
// We eliminated all of the video capabilities, so this configuration
// is unusable.
} else if (configuration.audioCapabilities &&
configuration.audioCapabilities.length &&
!filteredAudioCapabilities.length) {
// We eliminated all of the audio capabilities, so this configuration
// is unusable.
} else {
// Recreate a clone of the configuration and modify that. This way, we
// don't modify the application-provided config objects.
/** @type {!MediaKeySystemConfiguration} */
const clonedConfiguration = Object.assign({}, configuration);
clonedConfiguration.videoCapabilities = filteredVideoCapabilities;
clonedConfiguration.audioCapabilities = filteredAudioCapabilities;
filteredSupportedConfigurations.push(clonedConfiguration);
}
}

if (!filteredSupportedConfigurations.length) {
// None of the application's configurations passed our encryptionScheme
// filters, so this request fails.

// As spec'd, this should be a DOMException, but there is not a public
// constructor for this in all browsers. This should be close enough for
// most applications.
const unsupportedError = new Error(
'Unsupported keySystem or supportedConfigurations.');
unsupportedError.name = 'NotSupportedError';
unsupportedError['code'] = DOMException.NOT_SUPPORTED_ERR;
throw unsupportedError;
}

// At this point, we have some filtered configurations that we think could
// work. Pass this subset to the native version of RMKSA.
const mediaKeySystemAccess =
// eslint-disable-next-line no-restricted-syntax
await shaka.polyfill.EmeEncryptionScheme.originalRMKSA_.call(
this, keySystem, filteredSupportedConfigurations);

// Wrap the MKSA object in ours to provide the missing field in the
// returned configuration.
let videoScheme = null;
let audioScheme = null;
if (filteredSupportedConfigurations[0]) {
if (filteredSupportedConfigurations[0].videoCapabilities) {
videoScheme = filteredSupportedConfigurations[0]
.videoCapabilities[0].encryptionScheme;
}
if (filteredSupportedConfigurations[0].audioCapabilities) {
audioScheme = filteredSupportedConfigurations[0]
.audioCapabilities[0].encryptionScheme;
}
}
return new shaka.polyfill.EmeEncryptionSchemePolyfillMediaKeySystemAccess(
mediaKeySystemAccess, videoScheme, audioScheme);
}

/**
* Filters out capabilities that don't match the supported encryption scheme.
*
* @param {!Array<!MediaKeySystemMediaCapability> | undefined} capabilities
* An array of capabilities, or null or undefined.
* @param {?string} supportedScheme The encryption scheme that we think is
* supported by the key system.
* @return {!Array<!MediaKeySystemMediaCapability> | undefined} A filtered
* array of capabilities based on |supportedScheme|. May be undefined if
* the input was undefined.
* @private
*/
static filterCapabilities_(capabilities, supportedScheme) {
if (!capabilities) {
return capabilities;
}

return capabilities.filter((capability) => {
return shaka.polyfill.EncryptionSchemeUtils.checkSupportedScheme(
capability['encryptionScheme'], supportedScheme);
});
}
};

/**
* The original requestMediaKeySystemAccess, before we patched it.
*
* @type {
* function(this:Navigator,
* string,
* !Array<!MediaKeySystemConfiguration>
* ):!Promise<!MediaKeySystemAccess>
* }
* @private
*/
shaka.polyfill.EmeEncryptionScheme.originalRMKSA_;


shaka.polyfill.register(shaka.polyfill.EmeEncryptionScheme.install);
Loading