From 6d75d89fbb13a32de135a6c1ab6a7a3e55fcb3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Tyczy=C5=84ski?= Date: Sat, 8 Jul 2023 13:36:32 +0200 Subject: [PATCH] feat(DRM): use preferredKeySystems to reduce requestMediaKeySystemAccess() calls (#5391) Propagate `preferredKeySystems` config to `getDecodingInfosForVariants()` method. By doing that, shaka can only ask for `MediaKeySystemAccess` objects that will be used during playback. If any preferred key system is available, player will stop requesting for MKSA. If none of preferred key systems is available, player will try to get MKSA for any existing configuration, as usual. --- lib/media/drm_engine.js | 3 ++- lib/util/stream_utils.js | 45 +++++++++++++++++++++++++++++--- test/media/drm_engine_unit.js | 4 ++- test/util/stream_utils_unit.js | 47 ++++++++++++++++++++++++++++++---- 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 5aa47052d2..1f4b52b784 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -396,7 +396,8 @@ shaka.media.DrmEngine = class { // We should get the decodingInfo results for the variants after we filling // in the drm infos, and before queryMediaKeys_(). await shaka.util.StreamUtils.getDecodingInfosForVariants(variants, - this.usePersistentLicenses_, this.srcEquals_); + this.usePersistentLicenses_, this.srcEquals_, + this.config_.preferredKeySystems); const hasDrmInfo = hadDrmInfo || Object.keys(this.config_.servers).length; // An unencrypted content is initialized. diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 3c5974e858..2aa0338863 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -428,7 +428,8 @@ shaka.util.StreamUtils = class { 'MediaCapabilities should be valid.'); await shaka.util.StreamUtils.getDecodingInfosForVariants( - manifest.variants, usePersistentLicenses, /* srcEquals= */ false); + manifest.variants, usePersistentLicenses, /* srcEquals= */ false, + /** preferredKeySystems= */ []); manifest.variants = manifest.variants.filter((variant) => { // See: https://github.com/shaka-project/shaka-player/issues/3860 const video = variant.video; @@ -575,10 +576,11 @@ shaka.util.StreamUtils = class { * @param {!Array.} variants * @param {boolean} usePersistentLicenses * @param {boolean} srcEquals + * @param {!Array} preferredKeySystems * @exportDoc */ static async getDecodingInfosForVariants(variants, usePersistentLicenses, - srcEquals) { + srcEquals, preferredKeySystems) { const gotDecodingInfo = variants.some((variant) => variant.decodingInfos.length); if (gotDecodingInfo) { @@ -586,10 +588,47 @@ shaka.util.StreamUtils = class { return; } + // Try to get preferred key systems first to avoid unneeded calls to CDM. + for (const preferredKeySystem of preferredKeySystems) { + let keySystemSatisfied = false; + for (const variant of variants) { + /** @type {!Array.} */ + const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_( + variant, usePersistentLicenses, srcEquals) + .filter((config) => { + const keySystem = config.keySystemConfiguration && + config.keySystemConfiguration.keySystem; + return keySystem === preferredKeySystem; + }); + + // The reason we are performing this await in a loop rather than + // batching into a `promise.all` is performance related. + // https://github.com/shaka-project/shaka-player/pull/4708#discussion_r1022581178 + for (const config of decodingConfigs) { + // eslint-disable-next-line no-await-in-loop + await shaka.util.StreamUtils.getDecodingInfosForVariant_( + variant, config); + } + if (variant.decodingInfos.length) { + keySystemSatisfied = true; + } + } // for (const variant of variants) + if (keySystemSatisfied) { + // Return if any preferred key system is already satisfied. + return; + } + } // for (const preferredKeySystem of preferredKeySystems) + for (const variant of variants) { /** @type {!Array.} */ const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_( - variant, usePersistentLicenses, srcEquals); + variant, usePersistentLicenses, srcEquals) + .filter((config) => { + const keySystem = config.keySystemConfiguration && + config.keySystemConfiguration.keySystem; + // Avoid checking preferred systems twice. + return !keySystem || !preferredKeySystems.includes(keySystem); + }); // The reason we are performing this await in a loop rather than // batching into a `promise.all` is performance related. diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js index e6320f4638..a5355eea07 100644 --- a/test/media/drm_engine_unit.js +++ b/test/media/drm_engine_unit.js @@ -214,7 +214,9 @@ describe('DrmEngine', () => { const variants = manifest.variants; await drmEngine.initForPlayback(variants, manifest.offlineSessionIds); - expect(variants[0].decodingInfos.length).toBe(2); + // should be only one variant, as preferredKeySystems is propagated + // to getDecodingInfos + expect(variants[0].decodingInfos.length).toBe(1); expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo())) .toBe('drm.def'); }); diff --git a/test/util/stream_utils_unit.js b/test/util/stream_utils_unit.js index 527b9d6d30..45f53da3fa 100644 --- a/test/util/stream_utils_unit.js +++ b/test/util/stream_utils_unit.js @@ -483,7 +483,8 @@ describe('StreamUtils', () => { }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, - /* usePersistentLicenses= */false, /* srcEquals= */ false); + /* usePersistentLicenses= */false, /* srcEquals= */ false, + /* preferredKeySystems= */ []); expect(manifest.variants.length).toBeTruthy(); expect(manifest.variants[0].decodingInfos.length).toBe(1); expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy(); @@ -499,7 +500,8 @@ describe('StreamUtils', () => { }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, - /* usePersistentLicenses= */false, /* srcEquals= */ true); + /* usePersistentLicenses= */false, /* srcEquals= */ true, + /* preferredKeySystems= */ []); expect(manifest.variants.length).toBeTruthy(); expect(manifest.variants[0].decodingInfos.length).toBe(1); expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy(); @@ -528,7 +530,8 @@ describe('StreamUtils', () => { }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, - /* usePersistentLicenses= */false, /* srcEquals= */ false); + /* usePersistentLicenses= */false, /* srcEquals= */ false, + /* preferredKeySystems= */ []); expect(manifest.variants.length).toBe(1); expect(manifest.variants[0].decodingInfos.length).toBe(0); }); @@ -562,7 +565,8 @@ describe('StreamUtils', () => { }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, - /* usePersistentLicenses= */ false, /* srcEquals= */ false); + /* usePersistentLicenses= */ false, /* srcEquals= */ false, + /* preferredKeySystems= */ []); expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction) .toBe('srgb'); expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction) @@ -573,6 +577,38 @@ describe('StreamUtils', () => { navigator.mediaCapabilities.decodingInfo = originalDecodingInfo; } }); + + it('includes streams only with preferred key system', async () => { + const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo; + + try { + navigator.mediaCapabilities.decodingInfo = + shaka.test.Util.spyFunc(decodingInfoSpy); + + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.mime('video/mp4', 'avc1.4d400d'); + stream.encrypted = true; + stream.addDrmInfo('com.widevine.alpha'); + stream.addDrmInfo('com.microsoft.playready'); + }); + }); + }); + + await StreamUtils.getDecodingInfosForVariants(manifest.variants, + /* usePersistentLicenses= */ false, /* srcEquals= */ false, + /* preferredKeySystems= */ ['com.microsoft.playready']); + + // if preferred key system satisfies us, we shouldn't check other ones. + expect(decodingInfoSpy).toHaveBeenCalledTimes(1); + expect(decodingInfoSpy.calls.argsFor(0)[0].keySystemConfiguration + .keySystem) + .toBe('com.microsoft.playready'); + } finally { + navigator.mediaCapabilities.decodingInfo = originalDecodingInfo; + } + }); }); describe('filterManifest', () => { @@ -906,7 +942,8 @@ describe('StreamUtils', () => { }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, - /* usePersistentLicenses= */false, /* srcEquals= */ false); + /* usePersistentLicenses= */false, /* srcEquals= */ false, + /* preferredKeySystems= */ []); shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, /* preferredVideoCodecs= */[],