diff --git a/demo/config.js b/demo/config.js index fe01b6271c..c476b2e4b0 100644 --- a/demo/config.js +++ b/demo/config.js @@ -139,7 +139,8 @@ shakaDemo.Config = class { .addBoolInput_('Ignore duplicate init data', 'drm.ignoreDuplicateInitData'); const advanced = shakaDemoMain.getConfiguration().drm.advanced || {}; - const addDRMAdvancedField = (name, valueName, suggestions) => { + const addDRMAdvancedField = (name, valueName, suggestions, + arrayString = false) => { // All advanced fields of a given type are set at once. this.addDatalistInput_(name, suggestions, (input) => { // Add in any common drmSystem not currently in advanced. @@ -150,7 +151,9 @@ shakaDemo.Config = class { } // Set the robustness. for (const drmSystem in advanced) { - advanced[drmSystem][valueName] = input.value; + advanced[drmSystem][valueName] = arrayString ? + input.value.split(',').filter(Boolean) : + input.value; } shakaDemoMain.configure('drm.advanced', advanced); shakaDemoMain.remakeHash(); @@ -176,9 +179,11 @@ shakaDemo.Config = class { const sessionTypeSuggestions = ['temporary', 'persistent-license']; addDRMAdvancedField( - 'Video Robustness', 'videoRobustness', robustnessSuggestions); + 'Video Robustness', 'videoRobustness', robustnessSuggestions, + /* arrayString= */ true); addDRMAdvancedField( - 'Audio Robustness', 'audioRobustness', robustnessSuggestions); + 'Audio Robustness', 'audioRobustness', robustnessSuggestions, + /* arrayString= */ true); addDRMAdvancedField('Session Type', 'sessionType', sessionTypeSuggestions); this.addRetrySection_('drm', 'DRM Retry Parameters'); diff --git a/demo/main.js b/demo/main.js index fc4831d02d..17727c27a0 100644 --- a/demo/main.js +++ b/demo/main.js @@ -949,12 +949,18 @@ shakaDemo.Main = class { advanced[drmSystem] = shakaDemo.Main.defaultAdvancedDrmConfig(); } if ('videoRobustness' in params) { - advanced[drmSystem].videoRobustness = params['videoRobustness']; + advanced[drmSystem].videoRobustness = + params['videoRobustness'].split(','); } if ('audioRobustness' in params) { - advanced[drmSystem].audioRobustness = params['audioRobustness']; + advanced[drmSystem].audioRobustness = + params['audioRobustness'].split(','); } } + + if ('audioRobustness' in params || 'videoRobustness' in params) { + this.configure('drm.advanced', advanced); + } } } if ('lang' in params) { @@ -1499,11 +1505,15 @@ shakaDemo.Main = class { for (const drmSystem of shakaDemo.Main.commonDrmSystems) { const advancedFor = advanced[drmSystem]; if (advancedFor) { - if (advancedFor.videoRobustness) { - params.push('videoRobustness=' + advancedFor.videoRobustness); + if (advancedFor.videoRobustness && + advancedFor.videoRobustness.length) { + params.push('videoRobustness=' + + advancedFor.videoRobustness.join()); } - if (advancedFor.audioRobustness) { - params.push('audioRobustness=' + advancedFor.audioRobustness); + if (advancedFor.audioRobustness && + advancedFor.audioRobustness.length) { + params.push('audioRobustness=' + + advancedFor.audioRobustness.join()); } break; } @@ -1933,8 +1943,8 @@ shakaDemo.Main = class { return { distinctiveIdentifierRequired: false, persistentStateRequired: false, - videoRobustness: '', - audioRobustness: '', + videoRobustness: [], + audioRobustness: [], sessionType: '', serverCertificate: new Uint8Array(0), serverCertificateUri: '', diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 7ba8a76ccd..fb3d88fd6f 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -116,6 +116,7 @@ application: `panicThreshold`. (deprecated in v4.10.0) - `useSafariBehaviorForLive` has been removed. - `parsePrftBox` has been removed. + - `videoRobustness` and `audioRobustness` are now only an array of strings. (deprecated in v4.13.0) - Plugin changes: - `TextDisplayer` plugins must implement the `configure()` method. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 4d5e44febf..27fe901328 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -808,8 +808,8 @@ shaka.extern.ProducerReferenceTime; * @typedef {{ * distinctiveIdentifierRequired: boolean, * persistentStateRequired: boolean, - * videoRobustness: string, - * audioRobustness: string, + * videoRobustness: Array., + * audioRobustness: Array., * serverCertificate: Uint8Array, * serverCertificateUri: string, * individualizationServer: string, @@ -827,16 +827,18 @@ shaka.extern.ProducerReferenceTime; * state, e.g., for persistent license storage. *
* Defaults to false. - * @property {string} videoRobustness - * A key-system-specific string that specifies a required security level for - * video. - *
- * Defaults to '', i.e., no specific robustness required. - * @property {string} audioRobustness - * A key-system-specific string that specifies a required security level for - * audio. - *
- * Defaults to '', i.e., no specific robustness required. + * @property {Array.} videoRobustness + * A key-system-specific Array of strings that specifies a required security + * level for video. For multiple robustness levels, list items in priority + * order. + *
+ * Defaults to [], i.e., no specific robustness required. + * @property {Array.} audioRobustness + * A key-system-specific Array of strings that specifies a required security + * level for audio. For multiple robustness levels, list items in priority + * order. + *
+ * Defaults to [], i.e., no specific robustness required. * @property {Uint8Array} serverCertificate * An empty certificate (byteLength==0) will be treated as * null.
diff --git a/lib/drm/drm_engine.js b/lib/drm/drm_engine.js index 4a07ae11e2..4d5864b8b4 100644 --- a/lib/drm/drm_engine.js +++ b/lib/drm/drm_engine.js @@ -413,6 +413,57 @@ shaka.drm.DrmEngine = class { /** @type {!Map.} */ let configsByKeySystem; + /** + * Expand robustness into multiple drm infos if multiple video robustness + * levels were provided. + * + * robustness can be either a single item as a string or multiple items as + * an array of strings. + */ + const expandRobustness = (drmInfos, robustnessType) => { + const newDrmInfos = []; + for (const drmInfo of drmInfos) { + let items = drmInfo[robustnessType] || + (this.config_.advanced && + this.config_.advanced[drmInfo.keySystem] && + this.config_.advanced[drmInfo.keySystem][robustnessType]) || ''; + if (typeof items === 'string') { + // if drmInfo's robustness has already been expanded, + // use the drmInfo directly. + newDrmInfos.push(drmInfo); + } else if (Array.isArray(items)) { + if (items.length === 0) { + items = ['']; + } + for (const item of items) { + newDrmInfos.push( + Object.assign({}, drmInfo, {[robustnessType]: item}), + ); + } + } + } + return newDrmInfos; + }; + + for (const variant of variants) { + if (variant.video) { + variant.video.drmInfos = + expandRobustness(variant.video.drmInfos, + 'videoRobustness'); + variant.video.drmInfos = + expandRobustness(variant.video.drmInfos, + 'audioRobustness'); + } + if (variant.audio) { + variant.audio.drmInfos = + expandRobustness(variant.audio.drmInfos, + 'videoRobustness'); + variant.audio.drmInfos = + expandRobustness(variant.audio.drmInfos, + 'audioRobustness'); + } + } + // 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, @@ -2505,13 +2556,8 @@ shaka.drm.DrmEngine = class { advancedConfig.persistentStateRequired; } - if (!drmInfo.videoRobustness) { - drmInfo.videoRobustness = advancedConfig.videoRobustness; - } - - if (!drmInfo.audioRobustness) { - drmInfo.audioRobustness = advancedConfig.audioRobustness; - } + // robustness will be filled in with defaults, if needed, in + // expandRobustness if (!drmInfo.serverCertificate) { drmInfo.serverCertificate = advancedConfig.serverCertificate; diff --git a/lib/player.js b/lib/player.js index 1c150905e8..5a60411481 100644 --- a/lib/player.js +++ b/lib/player.js @@ -4047,6 +4047,38 @@ shaka.Player = class extends shaka.util.FakeEventTarget { config['streaming']['autoLowLatencyMode']; delete config['streaming']['autoLowLatencyMode']; } + + // Deprecate AdvancedDrmConfiguration's videoRobustness and audioRobustness + // as a string. It's now an array of strings. + if (config['drm'] && config['drm']['advanced']) { + let fixedUp = false; + for (const keySystem in config['drm']['advanced']) { + const {videoRobustness, audioRobustness} = + config['drm']['advanced'][keySystem]; + if ('videoRobustness' in config['drm']['advanced'][keySystem] && + !Array.isArray( + config['drm']['advanced'][keySystem]['videoRobustness'])) { + config['drm']['advanced'][keySystem]['videoRobustness'] = + [videoRobustness]; + fixedUp = true; + } + if ('audioRobustness' in config['drm']['advanced'][keySystem] && + !Array.isArray( + config['drm']['advanced'][keySystem]['audioRobustness'])) { + config['drm']['advanced'][keySystem]['audioRobustness'] = + [audioRobustness]; + fixedUp = true; + } + } + + if (fixedUp) { + shaka.Deprecate.deprecateFeature(5, + 'AdvancedDrmConfiguration\'s videoRobustness and audioRobustness', + 'These properties are no longer strings but array of strings, ' + + 'please update your usage of these properties.'); + } + } + const ret = shaka.util.PlayerConfiguration.mergeConfigObjects( this.config_, config, this.defaultConfig_()); diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index ee2f8c1658..c42311baa0 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -540,8 +540,8 @@ shaka.util.PlayerConfiguration = class { '.drm.advanced': { distinctiveIdentifierRequired: false, persistentStateRequired: false, - videoRobustness: '', - audioRobustness: '', + videoRobustness: [], + audioRobustness: [], sessionType: '', serverCertificate: new Uint8Array(0), serverCertificateUri: '', diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index aa865a224d..e9d3c3cd11 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -977,101 +977,117 @@ shaka.util.StreamUtils = class { usePersistentLicenses ? ['persistent-license'] : ['temporary']; for (const keySystem of drmInfoByKeySystems.keys()) { - const modifiedMediaDecodingConfigBatch = []; - for (const base of mediaDecodingConfigBatch) { - // Create a copy of the mediaDecodingConfig. - const config = /** @type {!MediaDecodingConfiguration} */ - (Object.assign({}, base)); - - const drmInfos = drmInfoByKeySystems.get(keySystem); - - /** @type {!MediaCapabilitiesKeySystemConfiguration} */ - const keySystemConfig = { - keySystem: keySystem, - initDataType: 'cenc', - persistentState: persistentState, - distinctiveIdentifier: 'optional', - sessionTypes: sessionTypes, - }; + const drmInfos = drmInfoByKeySystems.get(keySystem); + + // Get all the robustness info so that we can avoid using nested + // loops when we just need the robustness. + const drmInfosByRobustness = new Map(); + for (const info of drmInfos) { + const keyName = `${info.videoRobustness},${info.audioRobustness}`; + if (!drmInfosByRobustness.get(keyName)) { + drmInfosByRobustness.set(keyName, []); + } + drmInfosByRobustness.get(keyName).push(info); + } - for (const info of drmInfos) { - if (info.initData && info.initData.length) { - const initDataTypes = new Set(); - for (const initData of info.initData) { - initDataTypes.add(initData.initDataType); - } - if (initDataTypes.size > 1) { - shaka.log.v2('DrmInfo contains more than one initDataType,', - 'and we use the initDataType of the first initData.', - info); + for (const drmInfosRobustness of drmInfosByRobustness.values()) { + const modifiedMediaDecodingConfigBatch = []; + for (const base of mediaDecodingConfigBatch) { + // Create a copy of the mediaDecodingConfig. + const config = /** @type {!MediaDecodingConfiguration} */ + (Object.assign({}, base)); + + + /** @type {!MediaCapabilitiesKeySystemConfiguration} */ + const keySystemConfig = { + keySystem: keySystem, + initDataType: 'cenc', + persistentState: persistentState, + distinctiveIdentifier: 'optional', + sessionTypes: sessionTypes, + }; + + for (const info of drmInfosRobustness) { + if (info.initData && info.initData.length) { + const initDataTypes = new Set(); + for (const initData of info.initData) { + initDataTypes.add(initData.initDataType); + } + if (initDataTypes.size > 1) { + shaka.log.v2('DrmInfo contains more than one initDataType,', + 'and we use the initDataType of the first initData.', + info); + } + keySystemConfig.initDataType = info.initData[0].initDataType; } - keySystemConfig.initDataType = info.initData[0].initDataType; - } - if (info.distinctiveIdentifierRequired) { - keySystemConfig.distinctiveIdentifier = 'required'; - } - if (info.persistentStateRequired) { - keySystemConfig.persistentState = 'required'; - } - if (info.sessionType) { - keySystemConfig.sessionTypes = [info.sessionType]; - } + if (info.distinctiveIdentifierRequired) { + keySystemConfig.distinctiveIdentifier = 'required'; + } + if (info.persistentStateRequired) { + keySystemConfig.persistentState = 'required'; + } + if (info.sessionType) { + keySystemConfig.sessionTypes = [info.sessionType]; + } - if (audio) { - if (!keySystemConfig.audio) { - // KeySystemTrackConfiguration - keySystemConfig.audio = { - robustness: info.audioRobustness, - }; - if (info.encryptionScheme) { - keySystemConfig.audio.encryptionScheme = info.encryptionScheme; + if (audio) { + if (!keySystemConfig.audio) { + // KeySystemTrackConfiguration + keySystemConfig.audio = { + robustness: info.audioRobustness, + }; + if (info.encryptionScheme) { + keySystemConfig.audio.encryptionScheme = + info.encryptionScheme; + } + } else { + if (info.encryptionScheme) { + keySystemConfig.audio.encryptionScheme = + keySystemConfig.audio.encryptionScheme || + info.encryptionScheme; + } + keySystemConfig.audio.robustness = + keySystemConfig.audio.robustness || + info.audioRobustness; } - } else { - if (info.encryptionScheme) { - keySystemConfig.audio.encryptionScheme = - keySystemConfig.audio.encryptionScheme || - info.encryptionScheme; + // See: https://github.com/shaka-project/shaka-player/issues/4659 + if (keySystemConfig.audio.robustness == '') { + delete keySystemConfig.audio.robustness; } - keySystemConfig.audio.robustness = - keySystemConfig.audio.robustness || - info.audioRobustness; - } - // See: https://github.com/shaka-project/shaka-player/issues/4659 - if (keySystemConfig.audio.robustness == '') { - delete keySystemConfig.audio.robustness; } - } - if (video) { - if (!keySystemConfig.video) { - // KeySystemTrackConfiguration - keySystemConfig.video = { - robustness: info.videoRobustness, - }; - if (info.encryptionScheme) { - keySystemConfig.video.encryptionScheme = info.encryptionScheme; + if (video) { + if (!keySystemConfig.video) { + // KeySystemTrackConfiguration + keySystemConfig.video = { + robustness: info.videoRobustness, + }; + if (info.encryptionScheme) { + keySystemConfig.video.encryptionScheme = + info.encryptionScheme; + } + } else { + if (info.encryptionScheme) { + keySystemConfig.video.encryptionScheme = + keySystemConfig.video.encryptionScheme || + info.encryptionScheme; + } + keySystemConfig.video.robustness = + keySystemConfig.video.robustness || + info.videoRobustness; } - } else { - if (info.encryptionScheme) { - keySystemConfig.video.encryptionScheme = - keySystemConfig.video.encryptionScheme || - info.encryptionScheme; + // See: https://github.com/shaka-project/shaka-player/issues/4659 + if (keySystemConfig.video.robustness == '') { + delete keySystemConfig.video.robustness; } - keySystemConfig.video.robustness = - keySystemConfig.video.robustness || - info.videoRobustness; - } - // See: https://github.com/shaka-project/shaka-player/issues/4659 - if (keySystemConfig.video.robustness == '') { - delete keySystemConfig.video.robustness; } } + config.keySystemConfiguration = keySystemConfig; + modifiedMediaDecodingConfigBatch.push(config); } - config.keySystemConfiguration = keySystemConfig; - modifiedMediaDecodingConfigBatch.push(config); + configs.push(modifiedMediaDecodingConfigBatch); } - configs.push(modifiedMediaDecodingConfigBatch); } return configs; } diff --git a/test/drm/drm_engine_unit.js b/test/drm/drm_engine_unit.js index fdbdff1144..a4c0f65737 100644 --- a/test/drm/drm_engine_unit.js +++ b/test/drm/drm_engine_unit.js @@ -509,59 +509,294 @@ describe('DrmEngine', () => { expect(variants[0].decodingInfos.length).toBe(1); }); - it('uses advanced config to fill in DrmInfo', async () => { - // Leave only one drmInfo - manifest = shaka.test.ManifestGenerator.generate((manifest) => { - manifest.addVariant(0, (variant) => { - variant.addVideo(1, (stream) => { - stream.encrypted = true; - stream.addDrmInfo('drm.abc'); + it('uses advanced config to fill in DrmInfo, single robustness', + async () => { + // Leave only one drmInfo + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + variant.addAudio(2, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + }); }); - variant.addAudio(2, (stream) => { - stream.encrypted = true; - stream.addDrmInfo('drm.abc'); + + setDecodingInfoSpy([]); + + config.advanced['drm.abc'] = { + audioRobustness: ['good'], + videoRobustness: ['really_really_ridiculously_good'], + serverCertificate: null, + serverCertificateUri: '', + sessionType: 'persistent-license', + individualizationServer: '', + distinctiveIdentifierRequired: true, + persistentStateRequired: true, + headers: {}, + }; + drmEngine.configure(config); + + const variants = manifest.variants; + await expectAsync( + drmEngine.initForPlayback(variants, manifest.offlineSessionIds)) + .toBeRejected(); + + expect(drmEngine.initialized()).toBe(false); + expect(decodingInfoSpy).toHaveBeenCalledTimes(1); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'good', + }), + video: containing({ + robustness: 'really_really_ridiculously_good', + }), + }), + })); + }); + + it('uses advanced config to fill in DrmInfo, single robustness, default', + async () => { + // Leave only one drmInfo + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + variant.addAudio(2, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + }); }); + + setDecodingInfoSpy([]); + + config.advanced['drm.abc'] = { + audioRobustness: [], + videoRobustness: [], + serverCertificate: null, + serverCertificateUri: '', + sessionType: 'persistent-license', + individualizationServer: '', + distinctiveIdentifierRequired: true, + persistentStateRequired: true, + headers: {}, + }; + drmEngine.configure(config); + + const variants = manifest.variants; + await expectAsync( + drmEngine.initForPlayback(variants, manifest.offlineSessionIds)) + .toBeRejected(); + + expect(drmEngine.initialized()).toBe(false); + expect(decodingInfoSpy).toHaveBeenCalledTimes(1); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + }), + })); + expect(decodingInfoSpy).not.toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + audio: containing({ + robustness: '', + }), + video: containing({ + robustness: '', + }), + }), + })); }); - }); - setDecodingInfoSpy([]); + it('uses advanced config to fill in DrmInfo, multiple video robustness', + async () => { + // Leave only one drmInfo + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + variant.addAudio(2, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + }); + }); - config.advanced['drm.abc'] = { - audioRobustness: 'good', - videoRobustness: 'really_really_ridiculously_good', - serverCertificate: null, - serverCertificateUri: '', - sessionType: 'persistent-license', - individualizationServer: '', - distinctiveIdentifierRequired: true, - persistentStateRequired: true, - headers: {}, - }; - drmEngine.configure(config); + setDecodingInfoSpy([]); - const variants = manifest.variants; - await expectAsync( - drmEngine.initForPlayback(variants, manifest.offlineSessionIds)) - .toBeRejected(); + config.advanced['drm.abc'] = { + audioRobustness: ['good'], + videoRobustness: [ + 'really_ridiculously_good', 'a_mid_one', 'another_worse_one', + ], + serverCertificate: null, + serverCertificateUri: '', + sessionType: 'persistent-license', + individualizationServer: '', + distinctiveIdentifierRequired: true, + persistentStateRequired: true, + headers: {}, + }; + drmEngine.configure(config); - expect(drmEngine.initialized()).toBe(false); - expect(decodingInfoSpy).toHaveBeenCalledTimes(1); - expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ - keySystemConfiguration: containing({ - keySystem: 'drm.abc', - distinctiveIdentifier: 'required', - persistentState: 'required', - sessionTypes: ['persistent-license'], - initDataType: 'cenc', - audio: containing({ - robustness: 'good', - }), - video: containing({ - robustness: 'really_really_ridiculously_good', - }), - }), - })); - }); + const variants = manifest.variants; + await expectAsync( + drmEngine.initForPlayback(variants, manifest.offlineSessionIds)) + .toBeRejected(); + + expect(drmEngine.initialized()).toBe(false); + expect(decodingInfoSpy).toHaveBeenCalledTimes(3); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'good', + }), + video: containing({ + robustness: 'really_ridiculously_good', + }), + }), + })); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'good', + }), + video: containing({ + robustness: 'a_mid_one', + }), + }), + })); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'good', + }), + video: containing({ + robustness: 'another_worse_one', + }), + }), + })); + }); + + it('uses advanced config to fill in DrmInfo, multiple audio robustness', + async () => { + // Leave only one drmInfo + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(0, (variant) => { + variant.addVideo(1, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + variant.addAudio(2, (stream) => { + stream.encrypted = true; + stream.addDrmInfo('drm.abc'); + }); + }); + }); + + setDecodingInfoSpy([]); + + config.advanced['drm.abc'] = { + audioRobustness: [ + 'really_ridiculously_good', 'a_mid_one', 'another_worse_one', + ], + videoRobustness: ['good'], + serverCertificate: null, + serverCertificateUri: '', + sessionType: 'persistent-license', + individualizationServer: '', + distinctiveIdentifierRequired: true, + persistentStateRequired: true, + headers: {}, + }; + drmEngine.configure(config); + + const variants = manifest.variants; + await expectAsync( + drmEngine.initForPlayback(variants, manifest.offlineSessionIds)) + .toBeRejected(); + + expect(drmEngine.initialized()).toBe(false); + expect(decodingInfoSpy).toHaveBeenCalledTimes(3); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'really_ridiculously_good', + }), + video: containing({ + robustness: 'good', + }), + }), + })); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'a_mid_one', + }), + video: containing({ + robustness: 'good', + }), + }), + })); + expect(decodingInfoSpy).toHaveBeenCalledWith(containing({ + keySystemConfiguration: containing({ + keySystem: 'drm.abc', + distinctiveIdentifier: 'required', + persistentState: 'required', + sessionTypes: ['persistent-license'], + initDataType: 'cenc', + audio: containing({ + robustness: 'another_worse_one', + }), + video: containing({ + robustness: 'good', + }), + }), + })); + }); it('prefers advanced config from manifest if present', async () => { // Leave only one drmInfo @@ -590,8 +825,8 @@ describe('DrmEngine', () => { }); config.advanced['drm.abc'] = { - audioRobustness: 'bad', - videoRobustness: 'so_bad_it_hurts', + audioRobustness: ['bad'], + videoRobustness: ['so_bad_it_hurts'], serverCertificate: null, serverCertificateUri: '', sessionType: '', @@ -2191,8 +2426,8 @@ describe('DrmEngine', () => { }); config.advanced['drm.abc'] = { - audioRobustness: 'good', - videoRobustness: 'really_really_ridiculously_good', + audioRobustness: ['good'], + videoRobustness: ['really_really_ridiculously_good'], distinctiveIdentifierRequired: true, serverCertificate: null, serverCertificateUri: '', @@ -2560,14 +2795,14 @@ describe('DrmEngine', () => { */ function createAdvancedConfig(serverCert) { return { - audioRobustness: '', + audioRobustness: [], distinctiveIdentifierRequired: false, persistentStateRequired: false, serverCertificate: serverCert, serverCertificateUri: '', individualizationServer: '', sessionType: '', - videoRobustness: '', + videoRobustness: [], headers: {}, }; }