From 6449b30faa76fc5a6e48814c08d6dbfe8c612fc7 Mon Sep 17 00:00:00 2001 From: Michelle Zhuo Date: Fri, 5 Feb 2021 09:11:27 -0800 Subject: [PATCH] feat(DRM): Add preferred key systems config We'll allow users to configure key system priorities through the 'drm.preferredKeySystems' configuration. Edge now supports both Widevine and PlayReady, and users should be able to configure the priorities of the key systems. If no key system is preferred, or no preferred key systems is valid, we'll fall back to the original behavior of choosing the first key system with configured license server url in the manifest. Example: player.configure('drm.preferredKeySystems', [ 'com.widevine.alpha', 'com.microsoft.playready', ]); If both Widevine and Playready have a license server and are supported, the config sets Widevine as the first choice, and PlayReady as the second. Issue #3002 Change-Id: Idb881ef4921259bb3e1879cd8ec2bb6966d6580d --- externs/shaka/player.js | 6 ++++- lib/media/drm_engine.js | 43 ++++++++++++++++++++++++++++---- lib/util/player_configuration.js | 1 + test/media/drm_engine_unit.js | 24 ++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 0be58f147a..a0ea3f28ac 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -574,7 +574,8 @@ shaka.extern.AdvancedDrmConfiguration; * ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)| * undefined), * logLicenseExchange: boolean, - * updateExpirationTime: number + * updateExpirationTime: number, + * preferredKeySystems: !Array. * }} * * @property {shaka.extern.RetryParameters} retryParameters @@ -612,6 +613,9 @@ shaka.extern.AdvancedDrmConfiguration; * @property {number} updateExpirationTime * Defaults to 1.
* The frequency in seconds with which to check the expiration of a session. + * @property {!Array.} preferredKeySystems + * Defaults to an empty array.
+ * Specifies the priorties of available DRM key systems. * * @exportDoc */ diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 7f3424f708..e34314749b 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -1016,6 +1016,22 @@ shaka.media.DrmEngine = class { shaka.util.Error.Category.DRM, shaka.util.Error.Code.NO_RECOGNIZED_KEY_SYSTEMS); } + + // If we have configured preferredKeySystems, choose a preferred keySystem + // if available. + for (const preferredKeySystem of this.config_.preferredKeySystems) { + for (const variant of variants) { + const decodingInfo = variant.decodingInfos.find((decodingInfo) => { + return decodingInfo.supported && + decodingInfo.keySystemAccess != null && + decodingInfo.keySystemAccess.keySystem == preferredKeySystem; + }); + if (decodingInfo) { + return decodingInfo.keySystemAccess; + } + } + } + // Try key systems with configured license servers first. We only have to // try key systems without configured license servers for diagnostic // reasons, so that we can differentiate between "none of these key @@ -1071,6 +1087,24 @@ shaka.media.DrmEngine = class { } } + // If we have configured preferredKeySystems, choose the preferred one if + // available. + for (const keySystem of this.config_.preferredKeySystems) { + if (configsByKeySystem.has(keySystem)) { + const config = configsByKeySystem.get(keySystem); + try { + mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop + await navigator.requestMediaKeySystemAccess(keySystem, [config]); + return mediaKeySystemAccess; + } catch (error) { + // Suppress errors. + shaka.log.v2( + 'Requesting', keySystem, 'failed with config', config, error); + } + this.destroyer_.ensureNotDestroyed(); + } + } + // Try key systems with configured license servers first. We only have to // try key systems without configured license servers for diagnostic // reasons, so that we can differentiate between "none of these key @@ -1092,14 +1126,13 @@ shaka.media.DrmEngine = class { try { mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop - await navigator.requestMediaKeySystemAccess( - keySystem, [config]); + await navigator.requestMediaKeySystemAccess(keySystem, [config]); return mediaKeySystemAccess; } catch (error) { + // Suppress errors. shaka.log.v2( - 'Requesting', keySystem, 'failed with config', - config, error); - } // Suppress errors. + 'Requesting', keySystem, 'failed with config', config, error); + } this.destroyer_.ensureNotDestroyed(); } } diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 5cd3ebad2e..03fe4e2c54 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -75,6 +75,7 @@ shaka.util.PlayerConfiguration = class { initDataTransform: shaka.media.DrmEngine.defaultInitDataTransform, logLicenseExchange: false, updateExpirationTime: 1, + preferredKeySystems: [], }; const manifest = { diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js index 8b276d450d..a4dbc65217 100644 --- a/test/media/drm_engine_unit.js +++ b/test/media/drm_engine_unit.js @@ -257,6 +257,30 @@ function testDrmEngine(useMediaCapabilities) { } }); + it('chooses systems by configured preferredKeySystems', async () => { + // Accept both drm.abc and drm.def. Only one can be chosen. + setRequestMediaKeySystemAccessSpy(['drm.abc', 'drm.def']); + config.preferredKeySystems = ['drm.def']; + drmEngine.configure(config); + logErrorSpy.and.stub(); + + const variants = manifest.variants; + await drmEngine.initForPlayback(variants, manifest.offlineSessionIds, + useMediaCapabilities); + + if (useMediaCapabilities) { + expect(variants[0].decodingInfos.length).toBe(2); + } else { + expect(requestMediaKeySystemAccessSpy).toHaveBeenCalledTimes(1); + // Although drm.def appears second in the manifest, it is queried first + // and also selected because it has a server configured. + const calls = requestMediaKeySystemAccessSpy.calls; + expect(calls.argsFor(0)[0]).toBe('drm.def'); + } + expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo())) + .toBe('drm.def'); + }); + it('chooses systems with configured license servers', async () => { // Accept both drm.abc and drm.def. Only one can be chosen. setRequestMediaKeySystemAccessSpy(['drm.abc', 'drm.def']);