Skip to content

Commit

Permalink
feat(MediaCap): Use mediaKeySystemAccess from decodingInfo in DrmEngine
Browse files Browse the repository at this point in the history
In DrmEngine, previously we created MediaKeySystemConfiguration for the
variants, and called navigator.requestMediaKeySystemAccess() to get the
mediaKeySystemAccess, and set up MediaKeys.

Now we can use the mediaKeySystemAccess from the decodingInfo results of
the variants directly to set up MediaKeys.

Issue #1391

Change-Id: Id93a5e2fed7f6827317ae11644967185fc0cffbd
  • Loading branch information
michellezhuogg committed Apr 7, 2021
1 parent 34c3133 commit b63a64e
Show file tree
Hide file tree
Showing 8 changed files with 553 additions and 245 deletions.
1 change: 1 addition & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ shaka.extern.OfflineConfiguration;
* @property {boolean} useMediaCapabilities
* If true, use MediaCapabilities.decodingInfo() to filter the manifest, and
* get MediaKeys information for encrypted content. Default to false.
* Shaka Player's integration with MediaCapabilities is now in BETA.
* @exportDoc
*/
shaka.extern.PlayerConfiguration;
Expand Down
115 changes: 83 additions & 32 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,10 @@ shaka.media.DrmEngine = class {
* @param {boolean} usePersistentLicenses
* Whether or not persistent licenses should be requested and stored for
* |manifest|.
* @param {boolean=} useMediaCapabilities
* @return {!Promise}
*/
initForStorage(variants, usePersistentLicenses) {
initForStorage(variants, usePersistentLicenses, useMediaCapabilities) {
this.initializedForStorage_ = true;
// There are two cases for this call:
// 1. We are about to store a manifest - in that case, there are no offline
Expand All @@ -218,7 +219,7 @@ shaka.media.DrmEngine = class {
// persistent licenses.
this.usePersistentLicenses_ = usePersistentLicenses;

return this.init_(variants, /* useMediaCapabilities= */ false);
return this.init_(variants, !!useMediaCapabilities);
}

/**
Expand Down Expand Up @@ -278,8 +279,9 @@ shaka.media.DrmEngine = class {
}];

configsByKeySystem.set(keySystem, config);

return this.queryMediaKeys_(configsByKeySystem);
return this.queryMediaKeys_(configsByKeySystem,
/* variants= */ [],
/* useMediaCapabilities= */ false);
}

/**
Expand Down Expand Up @@ -368,7 +370,8 @@ shaka.media.DrmEngine = class {
return Promise.resolve();
}

const p = this.queryMediaKeys_(configsByKeySystem);
const p = this.queryMediaKeys_(configsByKeySystem, variants,
useMediaCapabilities);

// TODO(vaage): Look into the assertion below. If we do not have any drm
// info, we create drm info so that content can play if it has drm info
Expand Down Expand Up @@ -859,10 +862,12 @@ shaka.media.DrmEngine = class {
* @param {!Map.<string, MediaKeySystemConfiguration>} configsByKeySystem
* A dictionary of configs, indexed by key system, with an iteration order
* (insertion order) that reflects the preference for the application.
* @param {!Array.<shaka.extern.Variant>} variants
* @param {boolean} useMediaCapabilities
* @return {!Promise} Resolved if/when a key system has been chosen.
* @private
*/
async queryMediaKeys_(configsByKeySystem) {
async queryMediaKeys_(configsByKeySystem, variants, useMediaCapabilities) {
if (configsByKeySystem.size == 1 && configsByKeySystem.has('')) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
Expand All @@ -881,39 +886,85 @@ shaka.media.DrmEngine = class {
}
}

/** @type {MediaKeySystemAccess} */
let mediaKeySystemAccess;

// 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 systems
// are available" and "some are available, but you did not configure them
// properly." The former takes precedence.
for (const shouldHaveLicenseServer of [true, false]) {
for (const keySystem of configsByKeySystem.keys()) {
const config = configsByKeySystem.get(keySystem);
// TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
const hasLicenseServer = config['drmInfos'].some((info) => {
return !!info.licenseServerUri;
});
if (hasLicenseServer != shouldHaveLicenseServer) {
continue;
}
if (!useMediaCapabilities) {
// 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
// systemsare available" and "some are available, but you did not
// configure them properly." The former takes precedence.
// TODO: once MediaCap implementation is complete, this part can be
// simplified or removed.
for (const shouldHaveLicenseServer of [true, false]) {
for (const keySystem of configsByKeySystem.keys()) {
const config = configsByKeySystem.get(keySystem);
// TODO: refactor, don't stick drmInfos onto
// MediaKeySystemConfiguration
const hasLicenseServer = config['drmInfos'].some((info) => {
return !!info.licenseServerUri;
});
if (hasLicenseServer != shouldHaveLicenseServer) {
continue;
}

try {
mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop
await navigator.requestMediaKeySystemAccess(keySystem, [config]);
try {
mediaKeySystemAccess = // eslint-disable-next-line no-await-in-loop
await navigator.requestMediaKeySystemAccess(
keySystem, [config]);
break;
} catch (error) {
shaka.log.v2(
'Requesting', keySystem, 'failed with config',
config, error);
} // Suppress errors.
this.destroyer_.ensureNotDestroyed();
}
if (mediaKeySystemAccess) {
break;
} catch (error) {
shaka.log.v2(
'Requesting', keySystem, 'failed with config',
config, error);
} // Suppress errors.
}
this.destroyer_.ensureNotDestroyed();
}
if (mediaKeySystemAccess) {
break;
} else {
// 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
// systems are available" and "some are available, but you did not
// configure them properly." The former takes precedence.
for (const shouldHaveLicenseServer of [true, false]) {
for (const variant of variants) {
// Get all the key systems in the variant that
// shouldHaveLicenseServer.
const keySystemSet = new Set();
const videoDrmInfos = variant.video ? variant.video.drmInfos : [];
const audioDrmInfos = variant.audio ? variant.audio.drmInfos : [];
const drmInfos = videoDrmInfos.concat(audioDrmInfos);
for (const info of drmInfos) {
shaka.log.info('licenseServerUri', info.licenseServerUri);
if (!!info.licenseServerUri == shouldHaveLicenseServer) {
keySystemSet.add(info.keySystem);
}
}

for (const decodingInfo of variant.decodingInfos) {
if (decodingInfo.supported &&
decodingInfo.keySystemAccess &&
keySystemSet.has(decodingInfo.keySystemAccess.keySystem)) {
mediaKeySystemAccess =
(decodingInfo.keySystemAccess);
break;
}
}
if (mediaKeySystemAccess) {
break;
}
}
if (mediaKeySystemAccess) {
break;
}
}
}
} // end of else

if (!mediaKeySystemAccess) {
throw new shaka.util.Error(
Expand Down
3 changes: 2 additions & 1 deletion lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,8 @@ shaka.offline.Storage = class {

drmEngine.configure(config.drm);
await drmEngine.initForStorage(
manifest.variants, config.offline.usePersistentLicense);
manifest.variants, config.offline.usePersistentLicense,
/* useMediaCapabilities= */ config.useMediaCapabilities);
await drmEngine.setServerCertificate();
await drmEngine.createOrLoad();

Expand Down
5 changes: 5 additions & 0 deletions lib/polyfill/media_capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ shaka.polyfill.MediaCapabilities = class {
shaka.log.debug(
'MediaCapabilities: Native mediaCapabilities support found.');
return;
} else if (!window.MediaSource) {
shaka.log.debug(
'MediaSource must be available to install mediaCapabilities ',
'polyfill.');
return;
}

navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
Expand Down
3 changes: 3 additions & 0 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,9 @@ shaka.util.StreamUtils = class {
if (info.persistentStateRequired) {
keySystemConfig.persistentState = 'required';
}
if (info.sessionType) {
keySystemConfig.sessionTypes = [info.sessionType];
}

if (audio) {
if (!keySystemConfig.audio) {
Expand Down
16 changes: 13 additions & 3 deletions test/media/drm_engine_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,16 @@ describe('DrmEngine', () => {
return support['com.widevine.alpha'] || support['com.microsoft.playready'];
}

filterDescribe('basic flow', checkSupport, () => {
for (const useMediaCapabilities of [true, false]) {
const isEnabled = useMediaCapabilities ? 'enabled' : 'disabled';
filterDescribe('basic flow with MediaCapabilities ' + isEnabled,
checkSupport, () => {
testBasicFlow(useMediaCapabilities);
});
}


function testBasicFlow(useMediaCapabilities) {
drmIt('gets a license and can play encrypted segments', async () => {
// The error callback should not be invoked.
onErrorSpy.and.callFake(fail);
Expand Down Expand Up @@ -217,7 +226,8 @@ describe('DrmEngine', () => {

const variants = manifest.variants;

await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds,
useMediaCapabilities);
await drmEngine.attach(video);
await mediaSourceEngine.appendBuffer(
ContentType.VIDEO, videoInitSegment, null, null,
Expand Down Expand Up @@ -273,5 +283,5 @@ describe('DrmEngine', () => {
expect(video.readyState).toBeGreaterThan(1);
expect(video.currentTime).toBeGreaterThan(0);
});
}); // describe('basic flow')
} // describe('basic flow')
});
Loading

0 comments on commit b63a64e

Please sign in to comment.