Skip to content

Commit

Permalink
feat(DRM): Add preferred key systems config
Browse files Browse the repository at this point in the history
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 shaka-project#3002

Change-Id: Idb881ef4921259bb3e1879cd8ec2bb6966d6580d
  • Loading branch information
michellezhuogg committed May 3, 2021
1 parent 9b4502c commit 6449b30
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 6 deletions.
6 changes: 5 additions & 1 deletion externs/shaka/player.js
Expand Up @@ -574,7 +574,8 @@ shaka.extern.AdvancedDrmConfiguration;
* ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
* undefined),
* logLicenseExchange: boolean,
* updateExpirationTime: number
* updateExpirationTime: number,
* preferredKeySystems: !Array.<string>
* }}
*
* @property {shaka.extern.RetryParameters} retryParameters
Expand Down Expand Up @@ -612,6 +613,9 @@ shaka.extern.AdvancedDrmConfiguration;
* @property {number} updateExpirationTime
* <i>Defaults to 1.</i> <br>
* The frequency in seconds with which to check the expiration of a session.
* @property {!Array.<string>} preferredKeySystems
* <i>Defaults to an empty array. </i> <br>
* Specifies the priorties of available DRM key systems.
*
* @exportDoc
*/
Expand Down
43 changes: 38 additions & 5 deletions lib/media/drm_engine.js
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/util/player_configuration.js
Expand Up @@ -75,6 +75,7 @@ shaka.util.PlayerConfiguration = class {
initDataTransform: shaka.media.DrmEngine.defaultInitDataTransform,
logLicenseExchange: false,
updateExpirationTime: 1,
preferredKeySystems: [],
};

const manifest = {
Expand Down
24 changes: 24 additions & 0 deletions test/media/drm_engine_unit.js
Expand Up @@ -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']);
Expand Down

0 comments on commit 6449b30

Please sign in to comment.