diff --git a/src/compat/eme/eme-api-implementation.ts b/src/compat/eme/eme-api-implementation.ts index e4f5fa8ffb..0ec72a3a7d 100644 --- a/src/compat/eme/eme-api-implementation.ts +++ b/src/compat/eme/eme-api-implementation.ts @@ -42,7 +42,7 @@ export interface IEmeApiImplementation { requestMediaKeySystemAccess: ( keyType: string, config: MediaKeySystemConfiguration[], - ) => Promise; + ) => Promise; /** * API allowing to listen for `"encrypted"` events, presumably sent by the diff --git a/src/experimental/tools/DummyMediaElement/eme.ts b/src/experimental/tools/DummyMediaElement/eme.ts index e9b2d6288d..1599248350 100644 --- a/src/experimental/tools/DummyMediaElement/eme.ts +++ b/src/experimental/tools/DummyMediaElement/eme.ts @@ -22,46 +22,94 @@ import { utf8ToStr, } from "../../../utils/string_parsing"; -const keySystemBlacklist: string[] = []; -const keySystemWhitelist: string[] = []; +export interface IRequestMediaKeySystemAccessConfig { + /** + * For the given arguments of the `navigator.requestMediaKeySystemAccess` + * API, returns the resulting `MediaKeySystemConfiguration`, or `null` if + * that configuration should be anounced as not supported. + * + * If not set, the first configuration will be anounced as supported for a + * supported key system. + * + * @param {string} keySystem + * @param {Array.} configs + * @returns {Object} config + */ + getMediaKeySystemConfiguration?: + | (( + keySystem: string, + configs: MediaKeySystemConfiguration[], + ) => MediaKeySystemConfiguration | null) + | undefined; + + /** + * For the given key system, returns `true` if it should be anounced as + * supported or `false` if it should be anounced as unsupported. + * + * If not set, all keySystems are supported. + * @param {string} keySystem + * @returns {boolean} + */ + isKeySystemSupported: ((keySystem: string) => boolean) | undefined; +} /** - * Re-implementation of the EME `navigator.requestMediaKeySystemAccess` API. - * @param {string} keySystem - * @param {Array.} supportedConfigurations - * @returns {Promise.} + * Return a configured re-implementation of the EME + * `navigator.requestMediaKeySystemAccess` API. + * @param {Object|undefined} [config] + * @returns {Function} */ -export function requestMediaKeySystemAccess( - keySystem: string, - supportedConfigurations: MediaKeySystemConfiguration[], -): Promise { - if (keySystem === "") { - return Promise.reject( - new TypeError("`requestMediaKeySystemAccess` error: empty string"), - ); - } - if (supportedConfigurations.length === 0) { - return Promise.reject( - new TypeError("`requestMediaKeySystemAccess` error: no given configuration."), +export function createRequestMediaKeySystemAccess( + config?: IRequestMediaKeySystemAccessConfig | undefined, +) { + /** + * Re-implementation of the EME `navigator.requestMediaKeySystemAccess` API. + * @param {string} keySystem + * @param {Array.} supportedConfigurations + * @returns {Promise.} + */ + return function requestMediaKeySystemAccess( + keySystem: string, + supportedConfigurations: MediaKeySystemConfiguration[], + ): Promise { + if (keySystem === "") { + return Promise.reject( + new TypeError("`requestMediaKeySystemAccess` error: empty string"), + ); + } + if (supportedConfigurations.length === 0) { + return Promise.reject( + new TypeError("`requestMediaKeySystemAccess` error: no given configuration."), + ); + } + if ( + typeof config?.isKeySystemSupported === "function" && + !config.isKeySystemSupported(keySystem) + ) { + const error = new Error(`"${keySystem}" is not a supported keySystem`); + error.name = "NotSupportedError"; + return Promise.reject(error); + } + let supportedConfiguration: MediaKeySystemConfiguration | null = + supportedConfigurations[0] ?? null; + + if (typeof config?.getMediaKeySystemConfiguration === "function") { + supportedConfiguration = config.getMediaKeySystemConfiguration( + keySystem, + supportedConfigurations, + ); + } + if (supportedConfiguration === null) { + const error = new Error( + "`requestMediaKeySystemAccess` error: No configuration supported.", + ); + error.name = "NotSupportedError"; + return Promise.reject(error); + } + return Promise.resolve( + new DummyMediaKeySystemAccess(keySystem, supportedConfiguration), ); - } - if ( - (keySystemWhitelist.length > 0 && !arrayIncludes(keySystemWhitelist, keySystem)) || - (keySystemBlacklist.length > 0 && arrayIncludes(keySystemBlacklist, keySystem)) - ) { - const error = new Error(`"${keySystem}" is not a supported keySystem`); - error.name = "NotSupportedError"; - return Promise.reject(error); - } - for (const config of supportedConfigurations) { - // TODO configurable configuration validation. Callback? - return Promise.resolve(new DummyMediaKeySystemAccess(keySystem, config)); - } - const error = new Error( - "`requestMediaKeySystemAccess` error: No configuration supported.", - ); - error.name = "NotSupportedError"; - return Promise.reject(error); + }; } /** diff --git a/src/experimental/tools/DummyMediaElement/html5.ts b/src/experimental/tools/DummyMediaElement/html5.ts index d4cb71de17..5e5420c342 100644 --- a/src/experimental/tools/DummyMediaElement/html5.ts +++ b/src/experimental/tools/DummyMediaElement/html5.ts @@ -14,12 +14,20 @@ import { keepRangeIntersection, } from "../../../utils/ranges"; import TaskCanceller from "../../../utils/task_canceller"; -import { DummyMediaKeys, requestMediaKeySystemAccess } from "./eme"; +import { DummyMediaKeys, createRequestMediaKeySystemAccess } from "./eme"; +import type { IRequestMediaKeySystemAccessConfig } from "./eme"; import { DummyMediaSource } from "./mse"; import TimeRangesWithMetadata, { EventScheduler } from "./utils"; export interface IDummyMediaElementOptions { nodeName?: "AUDIO" | "VIDEO" | undefined | null; + drmOptions?: + | { + requestMediaKeySystemAccessConfig?: + | IRequestMediaKeySystemAccessConfig + | undefined; + } + | undefined; } export class DummyMediaElement @@ -152,7 +160,9 @@ export class DummyMediaElement this.FORCED_MEDIA_SOURCE = DummyMediaSource; this.FORCED_EME_API = { - requestMediaKeySystemAccess, + requestMediaKeySystemAccess: createRequestMediaKeySystemAccess( + opts.drmOptions?.requestMediaKeySystemAccessConfig, + ), onEncrypted: createCompatibleEventListener(["encrypted"]), setMediaKeys, implementation: "standard", diff --git a/src/utils/are_codecs_compatible.ts b/src/utils/are_codecs_compatible.ts index fd9959a675..e58aa39ce4 100644 --- a/src/utils/are_codecs_compatible.ts +++ b/src/utils/are_codecs_compatible.ts @@ -35,7 +35,11 @@ function areCodecsCompatible(a: string, b: string): boolean { if (codecsA === "" || codecsB === "") { return false; } - if (codecsA.split(".")[0] !== codecsB.split(".")[0]) { + let initialPartA = codecsA.split(".")[0]; + initialPartA = initialPartA === "hev1" ? "hvc1" : initialPartA; + let initialPartB = codecsB.split(".")[0]; + initialPartB = initialPartB === "hev1" ? "hvc1" : initialPartB; + if (initialPartA !== initialPartB) { return false; } return true; diff --git a/tests/contents/DASH_DRM_static_SegmentTemplate/media/encrypted_multiple_keys_number.mpd b/tests/contents/DASH_DRM_static_SegmentTemplate/media/encrypted_multiple_keys_number.mpd index aa84cec211..8b9f182380 100644 --- a/tests/contents/DASH_DRM_static_SegmentTemplate/media/encrypted_multiple_keys_number.mpd +++ b/tests/contents/DASH_DRM_static_SegmentTemplate/media/encrypted_multiple_keys_number.mpd @@ -11,117 +11,117 @@ MediaInfoLib - v0.7.87 For more info about this video, see https://github.com/Axinom/dash-test-vectors --> - - + + - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA5AFoAcwA1AGcAQwBHAEsARgBFAEMAQQBVACsASgArAGQASQA2AFkAdwBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA5AFoAcwA1AGcAQwBHAEsARgBFAEMAQQBVACsASgArAGQASQA2AFkAdwBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AOQBaAHMANQBnAEMARwBLAEYARQBDAEEAVQArAEoAKwBkAEkANgBZAHcAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQgDmb9YohQBSAU+J+dI6YwQ== - - - - - - - - + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AOQBaAHMANQBnAEMARwBLAEYARQBDAEEAVQArAEoAKwBkAEkANgBZAHcAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQgDmb9YohQBSAU+J+dI6YwQ== + + + + + + + + - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBDAFQANgBWAGsATABKAHMAbwAwAG0AaQBZAEgAcABmADcAKwByAFUAbQBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQwBUADYAVgBrAEwASgBzAG8AMABtAGkAWQBIAHAAZgA3ACsAcgBVAG0AUQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQkJU+CWyySaOiYHpf7+rUmQ== - - - - - - - + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBDAFQANgBWAGsATABKAHMAbwAwAG0AaQBZAEgAcABmADcAKwByAFUAbQBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQwBUADYAVgBrAEwASgBzAG8AMABtAGkAWQBIAHAAZgA3ACsAcgBVAG0AUQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQkJU+CWyySaOiYHpf7+rUmQ== + + + + + + + - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA5AFoAcwA1AGcAQwBHAEsARgBFAEMAQQBVACsASgArAGQASQA2AFkAdwBBAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AOQBaAHMANQBnAEMARwBLAEYARQBDAEEAVQArAEoAKwBkAEkANgBZAHcAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQgDmb9YohQBSAU+J+dI6YwA== - - - - - - - - + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA5AFoAcwA1AGcAQwBHAEsARgBFAEMAQQBVACsASgArAGQASQA2AFkAdwBBAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AOQBaAHMANQBnAEMARwBLAEYARQBDAEEAVQArAEoAKwBkAEkANgBZAHcAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQgDmb9YohQBSAU+J+dI6YwA== + + + + + + + + - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBDAFQANgBWAGsATABKAHMAbwAwAG0AaQBZAEgAcABmADcAKwByAFUAbQBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQwBUADYAVgBrAEwASgBzAG8AMABtAGkAWQBIAHAAZgA3ACsAcgBVAG0AUQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQkJU+CWyySaOiYHpf7+rUmQ== - - - - - - - - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBQAHkATgBmAFcASABJAHcAOABVAGEAZgBwAEcAMwBDAEwARwBhAGcARgBBAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AUAB5AE4AZgBXAEgASQB3ADgAVQBhAGYAcABHADMAQwBMAEcAYQBnAEYAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQWF8jPzByRvGfpG3CLGagFA== - - - - - - - - - - - AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBlAEwAMABpAFEAawBXADgAdgAwAEcAMgBQAG0AKwBCAFQAYwBPAFIAMwB3AD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== - xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AZQBMADAAaQBRAGsAVwA4AHYAMABHADIAUABtACsAQgBUAGMATwBSADMAdwA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= - - - AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQQiK9eLxFQb+2Pm+BTcOR3w== - - - - - - - - - - - - - - - - - - - - - - - - + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBDAFQANgBWAGsATABKAHMAbwAwAG0AaQBZAEgAcABmADcAKwByAFUAbQBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQwBUADYAVgBrAEwASgBzAG8AMABtAGkAWQBIAHAAZgA3ACsAcgBVAG0AUQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQkJU+CWyySaOiYHpf7+rUmQ== + + + + + + + + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBQAHkATgBmAFcASABJAHcAOABVAGEAZgBwAEcAMwBDAEwARwBhAGcARgBBAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AUAB5AE4AZgBXAEgASQB3ADgAVQBhAGYAcABHADMAQwBMAEcAYQBnAEYAQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQWF8jPzByRvGfpG3CLGagFA== + + + + + + + + + + + AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBlAEwAMABpAFEAawBXADgAdgAwAEcAMgBQAG0AKwBCAFQAYwBPAFIAMwB3AD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AZQBMADAAaQBRAGsAVwA4AHYAMABHADIAUABtACsAQgBUAGMATwBSADMAdwA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQQiK9eLxFQb+2Pm+BTcOR3w== + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-trigger-error-if-no-key-system-provided-is-supported-1.png b/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-trigger-error-if-no-key-system-provided-is-supported-1.png new file mode 100644 index 0000000000..a32e6b8d35 Binary files /dev/null and b/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-trigger-error-if-no-key-system-provided-is-supported-1.png differ diff --git a/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-try-to-create-key-system-with-no-audio-or-video-capabilities-if-adding-them-fails-1.png b/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-try-to-create-key-system-with-no-audio-or-video-capabilities-if-adding-them-fails-1.png new file mode 100644 index 0000000000..a32e6b8d35 Binary files /dev/null and b/tests/integration/scenarios/__screenshots__/drm_requestMediaKeySystemAccess.js/DRM--Basic-use-cases-should-try-to-create-key-system-with-no-audio-or-video-capabilities-if-adding-them-fails-1.png differ diff --git a/tests/integration/scenarios/drm_base.js b/tests/integration/scenarios/drm_base.js index eb39c424a8..19366ce989 100644 --- a/tests/integration/scenarios/drm_base.js +++ b/tests/integration/scenarios/drm_base.js @@ -7,9 +7,7 @@ import waitForPlayerState, { } from "../../utils/waitForPlayerState"; import { lockLowestBitrates } from "../../utils/bitrates"; import sleep from "../../utils/sleep"; - -const textDecoder = new TextDecoder(); -const textEncoder = new TextEncoder(); +import { generateGetLicenseForFakeLicense } from "../utils/drm_utils"; describe("DRM: Basic use cases", function () { const { url, transport } = manifestInfos; @@ -62,7 +60,7 @@ describe("DRM: Basic use cases", function () { keySystems: [ { type: "com.microsoft.playready", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, }), @@ -96,7 +94,7 @@ describe("DRM: Basic use cases", function () { keySystems: [ { type: "com.microsoft.playready", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, }), @@ -158,7 +156,7 @@ describe("DRM: Basic use cases", function () { keySystems: [ { type: "com.microsoft.playready", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, failingKeyIds, @@ -205,7 +203,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "fallback", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, highPolicyLevelKeyIds, @@ -252,7 +250,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "continue", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, highPolicyLevelKeyIds, @@ -301,7 +299,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "error", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, highPolicyLevelKeyIds, @@ -348,7 +346,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "fallback", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, mediumPolicyLevelKeyIds, @@ -406,7 +404,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "error", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, mediumPolicyLevelKeyIds, @@ -463,7 +461,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "fallback", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, mediumPolicyLevelKeyIds, @@ -537,7 +535,7 @@ describe("DRM: Basic use cases", function () { { type: "com.microsoft.playready", onKeyOutputRestricted: "fallback", - getLicense: generateGetLicense({ + getLicense: generateGetLicenseForFakeLicense({ expectedKeyIds, askedKeyIds, mediumPolicyLevelKeyIds, @@ -593,63 +591,4 @@ describe("DRM: Basic use cases", function () { expect(player.getAudioRepresentation().id).toEqual("15-585f233f"); expect(player.getError()).toBeNull(); }); - - function generateGetLicense({ - expectedKeyIds, - askedKeyIds, - highPolicyLevelKeyIds, - mediumPolicyLevelKeyIds, - failingKeyIds, - }) { - return function getLicense(challenge, messageType) { - return new Promise((resolve, reject) => { - setTimeout(() => { - try { - expect(messageType).toEqual("license-request"); - const challengeStr = textDecoder.decode(challenge); - const challengeObj = JSON.parse(challengeStr); - const keys = {}; - challengeObj.keyIds.forEach((kid) => { - if (Array.isArray(expectedKeyIds)) { - expect(expectedKeyIds).toContain(kid); - } - if (Array.isArray(askedKeyIds)) { - askedKeyIds.push(kid); - } - if (Array.isArray(failingKeyIds) && failingKeyIds.includes(kid)) { - const error = new Error("Should fallback!"); - error.noRetry = true; - error.fallbackOnLastTry = true; - reject(error); - } - let policyLevel = 0; - if ( - Array.isArray(highPolicyLevelKeyIds) && - highPolicyLevelKeyIds.includes(kid) - ) { - policyLevel = 200; - } else if ( - Array.isArray(mediumPolicyLevelKeyIds) && - mediumPolicyLevelKeyIds.includes(kid) - ) { - policyLevel = 50; - } - keys[kid] = { - policyLevel, - }; - }); - const license = { - type: "license", - persistent: false, - keys, - }; - const licenseU8 = textEncoder.encode(JSON.stringify(license)); - resolve(licenseU8.buffer); - } catch (e) { - reject(e); - } - }, 50); - }); - }; - } }); diff --git a/tests/integration/scenarios/drm_requestMediaKeySystemAccess.js b/tests/integration/scenarios/drm_requestMediaKeySystemAccess.js new file mode 100644 index 0000000000..01eb20c879 --- /dev/null +++ b/tests/integration/scenarios/drm_requestMediaKeySystemAccess.js @@ -0,0 +1,1345 @@ +import { describe, beforeEach, afterEach, it, expect } from "vitest"; +import { manifestInfos } from "../../contents/DASH_DRM_static_SegmentTemplate"; +import DummyMediaElement from "../../../dist/es2017/experimental/tools/DummyMediaElement"; +import RxPlayer from "../../../dist/es2017"; +import waitForPlayerState, { + waitForLoadedStateAfterLoadVideo, +} from "../../utils/waitForPlayerState"; +import sleep from "../../utils/sleep"; +import { lockLowestBitrates } from "../../utils/bitrates"; +import { generateGetLicenseForFakeLicense } from "../utils/drm_utils"; + +describe("DRM: requestMediaKeySystemAcces use cases", function () { + const { url, transport } = manifestInfos; + let player; + const oldMediaSourceSupported = MediaSource.isTypeSupported; + + let dummy; + beforeEach(() => { + MediaSource.isTypeSupported = () => true; + }); + + afterEach(async () => { + MediaSource.isTypeSupported = oldMediaSourceSupported; + player?.dispose(); + }); + + it("should trigger error if none of the key system provided is supported", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: () => false, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + }); + + it('should ask for the following keySystems if just "playready" is set', async () => { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + case 1: + expect(keySystem).toEqual("com.microsoft.playready.recommendation"); + break; + case 2: + case 3: + expect(keySystem).toEqual("com.microsoft.playready"); + break; + case 4: + case 5: + expect(keySystem).toEqual("com.chromecast.playready"); + break; + case 6: + case 7: + expect(keySystem).toEqual("com.youtube.playready"); + break; + default: + throw new Error("Too many playready checks"); + } + } catch (e) { + keySystemCheckError = e; + return true; + } + return false; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "playready", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + }); + + it('should ask for the following keySystems if just "widevine" is set', async () => { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + case 1: + expect(keySystem).toEqual("com.widevine.alpha"); + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + return true; + } + return false; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "widevine", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + }); + + it("should load the content with a supported key system", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => keySystem === "com.widevine.alpha", + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + const expectedKeyIds = [ + "80399bf58a2140148053e27e748e98c1", + "585f233f307246f19fa46dc22c66a014", + ]; + const askedKeyIds = []; + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: () => { + throw new Error("Wrong key system"); + }, + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + }), + }, + ], + }); + await waitForLoadedStateAfterLoadVideo(player); + expect(player.getError()).toBeNull(); + expect(askedKeyIds.length).toEqual(expectedKeyIds.length); + const ksConfig = player.getKeySystemConfiguration(); + expect(ksConfig).not.toBeNull(); + expect(ksConfig.keySystem).toEqual("com.widevine.alpha"); + }); + + it("should try to create key system with no audio or video capabilities if adding them fails", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => keySystem === "com.widevine.alpha", + getMediaKeySystemConfiguration: (_keySystem, configs) => { + return ( + configs.find( + (config) => + (config.audioCapabilities === null || + config.audioCapabilities === undefined || + config.audioCapabilities.length === 0) && + (config.videoCapabilities === null || + config.videoCapabilities === undefined || + config.videoCapabilities.length === 0), + ) ?? null + ); + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + const expectedKeyIds = [ + "80399bf58a2140148053e27e748e98c1", + "585f233f307246f19fa46dc22c66a014", + ]; + const askedKeyIds = []; + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: () => { + throw new Error("Wrong key system"); + }, + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + }), + }, + ], + }); + await waitForLoadedStateAfterLoadVideo(player); + expect(player.getError()).toBeNull(); + expect(askedKeyIds.length).toEqual(expectedKeyIds.length); + const ksConfig = player.getKeySystemConfiguration(); + expect(ksConfig).not.toBeNull(); + expect(ksConfig.keySystem).toEqual("com.widevine.alpha"); + expect(ksConfig.configuration.videoCapabilities).toEqual(undefined); + expect(ksConfig.configuration.audioCapabilities).toEqual(undefined); + }); + + it("should ask for the right robustnesses when a widevine keySystem is set", async () => { + for (const keySystem of ["widevine", "com.widevine.alpha"]) { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + for (const prop of ["audioCapabilities", "videoCapabilities"]) { + let previousRobustness = null; + for (const capability of configs[0][prop]) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("HW_SECURE_ALL"); + } else if (previousRobustness === "HW_SECURE_ALL") { + if (robustness !== "HW_SECURE_ALL") { + expect(robustness).toEqual("HW_SECURE_DECODE"); + } + } else if (previousRobustness === "HW_SECURE_DECODE") { + if (robustness !== "HW_SECURE_DECODE") { + expect(robustness).toEqual("HW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "HW_SECURE_CRYPTO") { + if (robustness !== "HW_SECURE_CRYPTO") { + expect(robustness).toEqual("SW_SECURE_DECODE"); + } + } else if (previousRobustness === "SW_SECURE_DECODE") { + if (robustness !== "SW_SECURE_DECODE") { + expect(robustness).toEqual("SW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "SW_SECURE_CRYPTO") { + if (robustness !== "SW_SECURE_CRYPTO") { + throw new Error("Unexpected robustness: " + robustness); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "SW_SECURE_CRYPTO") { + throw new Error("Robustness in improper order"); + } + } + break; + case 1: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).toBeUndefined(); + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: keySystem, + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + player.dispose(); + await sleep(10); + expect(checkNumber).toEqual(2); + } + }); + + it("should ask for the right robustnesses when a com.microsoft.playready.recommendation keySystem is set", async () => { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.microsoft.playready.recommendation"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + for (const prop of ["audioCapabilities", "videoCapabilities"]) { + let previousRobustness = null; + for (const capability of configs[0][prop]) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("3000"); + } else if (previousRobustness === "3000") { + if (robustness !== "3000") { + expect(robustness).toEqual("2000"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "2000") { + throw new Error("Robustness in improper order"); + } + } + break; + case 1: + expect(keySystem).toEqual("com.microsoft.playready.recommendation"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).toBeUndefined(); + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready.recommendation", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + expect(checkNumber).toEqual(2); + player.dispose(); + }); + + it("should only ask for the right robustnesses with a specific key system when a playready keySystem is set", async () => { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.microsoft.playready.recommendation"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + for (const prop of ["audioCapabilities", "videoCapabilities"]) { + let previousRobustness = null; + for (const capability of configs[0][prop]) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("3000"); + } else if (previousRobustness === "3000") { + if (robustness !== "3000") { + expect(robustness).toEqual("2000"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "2000") { + throw new Error("Robustness in improper order"); + } + } + break; + case 1: + expect(keySystem).toEqual("com.microsoft.playready.recommendation"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).toBeUndefined(); + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + case 2: + case 4: + case 6: + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + expect(configs[0].videoCapabilities).not.toBeUndefined(); + expect(configs[0].audioCapabilities).not.toBeUndefined(); + for (const prop of ["audioCapabilities", "videoCapabilities"]) { + for (const capability of configs[0][prop]) { + expect(capability.robustness).toEqual(undefined); + } + } + break; + case 3: + case 5: + case 7: + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).toBeUndefined(); + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "playready", + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + expect(checkNumber).toEqual(8); + player.dispose(); + }); + + it("should not ask for any robustnesses when other playready keySystems are set", async () => { + for (const keySystem of [ + "com.microsoft.playready", + "com.playready.hardware", + "com.playready.software", + "com.chromecast.playready", + "com.youtube.playready", + ]) { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (_keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + expect(configs[0].videoCapabilities).not.toBeUndefined(); + expect(configs[0].audioCapabilities).not.toBeUndefined(); + for (const prop of ["audioCapabilities", "videoCapabilities"]) { + for (const capability of configs[0][prop]) { + expect(capability.robustness).toEqual(undefined); + } + } + break; + case 1: + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).toBeUndefined(); + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: keySystem, + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + player.dispose(); + await sleep(10); + expect(checkNumber).toEqual(2); + } + }); + + it("should not play hevc if not anounced as supported", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => keySystem === "com.widevine.alpha", + getMediaKeySystemConfiguration: (_keySystem, configs) => { + for (const config of configs) { + if ( + !Array.isArray(config.videoCapabilities) || + config.videoCapabilities === undefined + ) { + return config; + } + const filtered = config.videoCapabilities.filter((v) => { + return ( + v.contentType === undefined || + (v.contentType.indexOf("hev") === -1 && + v.contentType.indexOf("hvc") === -1) + ); + }); + if (filtered.length !== 0) { + return { + ...config, + videoCapabilities: filtered, + }; + } + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + const expectedKeyIds = [ + "80399bf58a2140148053e27e748e98c1", + "80399bf58a2140148053e27e748e98c0", + "585f233f307246f19fa46dc22c66a014", + ]; + const askedKeyIds = []; + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: () => { + throw new Error("Wrong key system"); + }, + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + }), + }, + ], + }); + try { + await waitForLoadedStateAfterLoadVideo(player); + } catch (err) { + if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + expect(player.getError()).toBeNull(); + expect(askedKeyIds).toContain("80399bf58a2140148053e27e748e98c0"); + const availableVideoTracks = player.getAvailableVideoTracks(); + expect(availableVideoTracks).toHaveLength(1); + expect( + availableVideoTracks[0].representations.every( + (r) => r.codec !== undefined && r.codec.indexOf("avc1") === 0, + ), + ).toBeTruthy(); + }); + + it("should not play avc if not anounced as supported", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => keySystem === "com.widevine.alpha", + getMediaKeySystemConfiguration: (_keySystem, configs) => { + for (const config of configs) { + if ( + !Array.isArray(config.videoCapabilities) || + config.videoCapabilities === undefined + ) { + return config; + } + const filtered = config.videoCapabilities.filter((v) => { + return v.contentType === undefined || v.contentType.indexOf("avc") === -1; + }); + if (filtered.length !== 0) { + return { + ...config, + videoCapabilities: filtered, + }; + } + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + const expectedKeyIds = [ + "80399bf58a2140148053e27e748e98c1", + "80399bf58a2140148053e27e748e98c0", + "585f233f307246f19fa46dc22c66a014", + ]; + const askedKeyIds = []; + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: () => { + throw new Error("Wrong key system"); + }, + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + }), + }, + ], + }); + try { + await waitForLoadedStateAfterLoadVideo(player); + } catch (err) { + if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + expect(player.getError()).toBeNull(); + expect(askedKeyIds).not.toContain("80399bf58a2140148053e27e748e98c0"); + const availableVideoTracks = player.getAvailableVideoTracks(); + expect(availableVideoTracks).toHaveLength(1); + expect( + availableVideoTracks[0].representations.every( + (r) => + r.codec !== undefined && + (r.codec.indexOf("hev1") === 0 || r.codec.indexOf("hvc1") === 0), + ), + ).toBeTruthy(); + }); + + it("should have both avc and hevc if both are anounced as supported", async function () { + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + isKeySystemSupported: (keySystem) => keySystem === "com.widevine.alpha", + getMediaKeySystemConfiguration: (_keySystem, configs) => { + return configs[0]; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + const expectedKeyIds = [ + "80399bf58a2140148053e27e748e98c1", + "80399bf58a2140148053e27e748e98c0", + "585f233f307246f19fa46dc22c66a014", + ]; + const askedKeyIds = []; + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: "com.microsoft.playready", + getLicense: () => { + throw new Error("Wrong key system"); + }, + }, + { + type: "com.widevine.alpha", + getLicense: generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + }), + }, + ], + }); + try { + await waitForLoadedStateAfterLoadVideo(player); + } catch (err) { + if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + expect(player.getError()).toBeNull(); + const availableVideoTracks = player.getAvailableVideoTracks(); + expect(availableVideoTracks).toHaveLength(2); + expect( + availableVideoTracks[0].representations.every( + (r) => + r.codec !== undefined && + (r.codec.indexOf("hev1") === 0 || r.codec.indexOf("hvc1") === 0), + ), + ).toBeTruthy(); + expect( + availableVideoTracks[1].representations.every( + (r) => r.codec !== undefined && r.codec.indexOf("avc1") === 0, + ), + ).toBeTruthy(); + }); + + it('should ask for specific video robustnesses if videoCapabilitiesConfig is set to "robustness"', async () => { + for (const keySystem of ["widevine", "com.widevine.alpha"]) { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + + // videoCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].videoCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("foo"); + } else if (previousRobustness === "foo") { + if (robustness !== "foo") { + expect(robustness).toEqual("bar"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "bar") { + throw new Error("Robustness in improper order"); + } + } + + // audioCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].audioCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("HW_SECURE_ALL"); + } else if (previousRobustness === "HW_SECURE_ALL") { + if (robustness !== "HW_SECURE_ALL") { + expect(robustness).toEqual("HW_SECURE_DECODE"); + } + } else if (previousRobustness === "HW_SECURE_DECODE") { + if (robustness !== "HW_SECURE_DECODE") { + expect(robustness).toEqual("HW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "HW_SECURE_CRYPTO") { + if (robustness !== "HW_SECURE_CRYPTO") { + expect(robustness).toEqual("SW_SECURE_DECODE"); + } + } else if (previousRobustness === "SW_SECURE_DECODE") { + if (robustness !== "SW_SECURE_DECODE") { + expect(robustness).toEqual("SW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "SW_SECURE_CRYPTO") { + if (robustness !== "SW_SECURE_CRYPTO") { + throw new Error("Unexpected robustness: " + robustness); + } + } + previousRobustness = robustness; + } + } + break; + case 1: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + // videoCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].videoCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("foo"); + } else if (previousRobustness === "foo") { + if (robustness !== "foo") { + expect(robustness).toEqual("bar"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "bar") { + throw new Error("Robustness in improper order"); + } + } + expect(configs[0].audioCapabilities).toBeUndefined(); + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: keySystem, + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + videoCapabilitiesConfig: { + type: "robustness", + value: ["foo", "bar"], + }, + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + player.dispose(); + await sleep(10); + expect(checkNumber).toEqual(2); + } + }); + + it('should ask for specific audio robustnesses if audioCapabilitiesConfig is set to "robustness"', async () => { + for (const keySystem of ["widevine", "com.widevine.alpha"]) { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + + // audioCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].audioCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("foo"); + } else if (previousRobustness === "foo") { + if (robustness !== "foo") { + expect(robustness).toEqual("bar"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "bar") { + throw new Error("Robustness in improper order"); + } + } + + // videoCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].videoCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("HW_SECURE_ALL"); + } else if (previousRobustness === "HW_SECURE_ALL") { + if (robustness !== "HW_SECURE_ALL") { + expect(robustness).toEqual("HW_SECURE_DECODE"); + } + } else if (previousRobustness === "HW_SECURE_DECODE") { + if (robustness !== "HW_SECURE_DECODE") { + expect(robustness).toEqual("HW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "HW_SECURE_CRYPTO") { + if (robustness !== "HW_SECURE_CRYPTO") { + expect(robustness).toEqual("SW_SECURE_DECODE"); + } + } else if (previousRobustness === "SW_SECURE_DECODE") { + if (robustness !== "SW_SECURE_DECODE") { + expect(robustness).toEqual("SW_SECURE_CRYPTO"); + } + } else if (previousRobustness === "SW_SECURE_CRYPTO") { + if (robustness !== "SW_SECURE_CRYPTO") { + throw new Error("Unexpected robustness: " + robustness); + } + } + previousRobustness = robustness; + } + } + break; + case 1: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + // audioCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].audioCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("foo"); + } else if (previousRobustness === "foo") { + if (robustness !== "foo") { + expect(robustness).toEqual("bar"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "bar") { + throw new Error("Robustness in improper order"); + } + } + expect(configs[0].videoCapabilities).toBeUndefined(); + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: keySystem, + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + audioCapabilitiesConfig: { + type: "robustness", + value: ["foo", "bar"], + }, + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + player.dispose(); + await sleep(10); + expect(checkNumber).toEqual(2); + } + }); + + it('should ask for specific audio and video robustnesses if audioCapabilitiesConfig and videoCapabilities are set to "robustness"', async () => { + for (const keySystem of ["widevine", "com.widevine.alpha"]) { + let checkNumber = 0; + let keySystemCheckError = null; + dummy = new DummyMediaElement({ + drmOptions: { + requestMediaKeySystemAccessConfig: { + getMediaKeySystemConfiguration: (keySystem, configs) => { + // NOTE: this method should always return `false` for the test to pass + try { + switch (checkNumber++) { + case 0: + expect(keySystem).toEqual("com.widevine.alpha"); + expect(configs).toHaveLength(1); + expect(configs[0].audioCapabilities).not.toHaveLength(0); + expect(configs[0].videoCapabilities).not.toHaveLength(0); + + // audioCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].audioCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("foo"); + } else if (previousRobustness === "foo") { + if (robustness !== "foo") { + expect(robustness).toEqual("bar"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "bar") { + throw new Error( + "audio robustness in improper order 1: " + previousRobustness, + ); + } + } + + // videoCapabilities + { + let previousRobustness = null; + for (const capability of configs[0].videoCapabilities) { + const robustness = capability.robustness; + if (previousRobustness === null) { + expect(robustness).toEqual("Waka"); + } else if (previousRobustness === "Waka") { + if (robustness !== "Waka") { + expect(robustness).toEqual("Flocka"); + } + } + previousRobustness = robustness; + } + if (previousRobustness === null) { + throw new Error("No robustness communicated"); + } else if (previousRobustness !== "Flocka") { + throw new Error( + "video robustness in improper order 1: " + previousRobustness, + ); + } + } + break; + default: + throw new Error("Too many widevine checks"); + } + } catch (e) { + keySystemCheckError = e; + } + return null; + }, + }, + }, + }); + player = new RxPlayer({ videoElement: dummy }); + lockLowestBitrates(player); + player.loadVideo({ + url, + transport, + autoPlay: false, + textTrackMode: "html", + textTrackElement: document.createElement("div"), + keySystems: [ + { + type: keySystem, + getLicense: generateGetLicenseForFakeLicense({ + expectedkeyids: [], + askedKeyIds: [], + }), + audioCapabilitiesConfig: { + type: "robustness", + value: ["foo", "bar"], + }, + videoCapabilitiesConfig: { + type: "robustness", + value: ["Waka", "Flocka"], + }, + }, + ], + }); + try { + await waitForPlayerState(player, "STOPPED", ["LOADING"]); + } catch (err) { + if (keySystemCheckError !== null) { + throw keySystemCheckError; + } else if (player.getError() !== null) { + throw player.getError(); + } + throw err; + } + const error = player.getError(); + expect(error).not.toBeNull(); + expect(error.code).to.equal("INCOMPATIBLE_KEYSYSTEMS"); + expect(error.name).to.equal("EncryptedMediaError"); + expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR"); + expect(keySystemCheckError).to.equal(null); + player.dispose(); + await sleep(10); + expect(checkNumber).toEqual(1); + } + }); +}); diff --git a/tests/integration/utils/drm_utils.js b/tests/integration/utils/drm_utils.js new file mode 100644 index 0000000000..e186974255 --- /dev/null +++ b/tests/integration/utils/drm_utils.js @@ -0,0 +1,63 @@ +import { expect } from "vitest"; + +const textDecoder = new TextDecoder(); +const textEncoder = new TextEncoder(); + +export function generateGetLicenseForFakeLicense({ + expectedKeyIds, + askedKeyIds, + highPolicyLevelKeyIds, + mediumPolicyLevelKeyIds, + failingKeyIds, +}) { + return function getLicense(challenge, messageType) { + return new Promise((resolve, reject) => { + setTimeout(() => { + try { + expect(messageType).toEqual("license-request"); + const challengeStr = textDecoder.decode(challenge); + const challengeObj = JSON.parse(challengeStr); + const keys = {}; + challengeObj.keyIds.forEach((kid) => { + if (Array.isArray(expectedKeyIds)) { + expect(expectedKeyIds).toContain(kid); + } + if (Array.isArray(askedKeyIds)) { + askedKeyIds.push(kid); + } + if (Array.isArray(failingKeyIds) && failingKeyIds.includes(kid)) { + const error = new Error("Should fallback!"); + error.noRetry = true; + error.fallbackOnLastTry = true; + reject(error); + } + let policyLevel = 0; + if ( + Array.isArray(highPolicyLevelKeyIds) && + highPolicyLevelKeyIds.includes(kid) + ) { + policyLevel = 200; + } else if ( + Array.isArray(mediumPolicyLevelKeyIds) && + mediumPolicyLevelKeyIds.includes(kid) + ) { + policyLevel = 50; + } + keys[kid] = { + policyLevel, + }; + }); + const license = { + type: "license", + persistent: false, + keys, + }; + const licenseU8 = textEncoder.encode(JSON.stringify(license)); + resolve(licenseU8.buffer); + } catch (e) { + reject(e); + } + }, 50); + }); + }; +} diff --git a/vitest.config.mjs b/vitest.config.mjs index 9ae7598dd2..2314832449 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -26,6 +26,7 @@ function getBrowserConfig(browser) { name: "chrome", provider: "webdriverio", headless: true, + screenshotFailures: false, providerOptions: { capabilities: { "goog:chromeOptions": { @@ -45,6 +46,7 @@ function getBrowserConfig(browser) { name: "firefox", provider: "webdriverio", headless: true, + screenshotFailures: false, providerOptions: { capabilities: { "moz:firefoxOptions": {