diff --git a/src/core/decrypt/__tests__/__global__/media_key_system_access.test.ts b/src/core/decrypt/__tests__/__global__/media_key_system_access.test.ts index 65d1e40150..c5fb78ae91 100644 --- a/src/core/decrypt/__tests__/__global__/media_key_system_access.test.ts +++ b/src/core/decrypt/__tests__/__global__/media_key_system_access.test.ts @@ -25,6 +25,7 @@ /* eslint-disable no-restricted-properties */ import { ICustomMediaKeySystemAccess } from "../../../../compat/eme"; +import { IKeySystemOption } from "../../../../public_types"; import { defaultKSConfig, defaultPRRecommendationKSConfig, @@ -34,16 +35,22 @@ import { } from "./utils"; export function requestMediaKeySystemAccessNoMediaKeys( - keySystem : string, - config : MediaKeySystemConfiguration[] -) : Promise { + keySystem: string, + config: MediaKeySystemConfiguration[] +): Promise { if (config.length === 0) { throw new Error("requestMediaKeySystemAccessNoMediaKeys: no config given"); } return Promise.resolve({ keySystem, - getConfiguration() { return config[0]; }, - createMediaKeys() { return new Promise(() => { /* noop */ }); }, + getConfiguration() { + return config[0]; + }, + createMediaKeys() { + return new Promise(() => { + /* noop */ + }); + }, }); } @@ -51,6 +58,22 @@ const incompatibleMKSAErrorMessage = "EncryptedMediaError (INCOMPATIBLE_KEYSYSTEMS) No key system compatible " + "with your wanted configuration has been found in the current browser."; +function removeCapabilitiesConfig( + baseConfig: MediaKeySystemConfiguration[] +): MediaKeySystemConfiguration[] { + return baseConfig.map( + (config) => + ({ + ...config, + audioCapabilities: undefined, + videoCapabilities: undefined, + // Note: TypeScript is wrong here (2024-08-07), it thinks that + // `audioCapabilities` and `videoCapabilities` cannot be set to + // `undefined`, though they definitely can. + }) as unknown as MediaKeySystemConfiguration + ); +} + /** * Check that the given `keySystemsConfigs` lead to an * `INCOMPATIBLE_KEYSYSTEMS` error. @@ -58,29 +81,33 @@ const incompatibleMKSAErrorMessage = * @returns {Promise} */ async function checkIncompatibleKeySystemsErrorMessage( - keySystemsConfigs : unknown[] -) : Promise { + keySystemsConfigs: IKeySystemOption[] +): Promise { const mediaElement = document.createElement("video"); const ContentDecryptor = jest.requireActual("../../content_decryptor").default; - const error : any = await testContentDecryptorError(ContentDecryptor, - mediaElement, - keySystemsConfigs); + const error: any = await testContentDecryptorError( + ContentDecryptor, + mediaElement, + keySystemsConfigs + ); expect(error).not.toBe(null); expect(error.message).toEqual(incompatibleMKSAErrorMessage); expect(error.name).toEqual("EncryptedMediaError"); expect(error.code).toEqual("INCOMPATIBLE_KEYSYSTEMS"); } -describe("core - decrypt - global tests - media key system access", () => { +describe("decrypt - global tests - media key system access", () => { // Used to implement every functions that should never be called. const neverCalledFn = jest.fn(); beforeEach(() => { jest.resetModules(); jest.restoreAllMocks(); - jest.mock("../../set_server_certificate", () => ({ __esModule: true as const, - default: neverCalledFn })); + jest.mock("../../set_server_certificate", () => ({ + __esModule: true as const, + default: neverCalledFn, + })); }); afterEach(() => { @@ -93,15 +120,25 @@ describe("core - decrypt - global tests - media key system access", () => { }); it("should throw if given a single incompatible keySystemsConfigs", async () => { - const mockRequestMediaKeySystemAccess = jest.fn(() => Promise.reject("nope")); + const mockRequestMediaKeySystemAccess = jest .fn(() => Promise.reject("nope")); mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); const getLicenseFn = neverCalledFn; - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: getLicenseFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "foo", getLicense: getLicenseFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should throw if given multiple incompatible keySystemsConfigs", async () => { @@ -109,17 +146,43 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - const config = [ { type: "foo", getLicense: neverCalledFn }, - { type: "bar", getLicense: neverCalledFn }, - { type: "baz", getLicense: neverCalledFn } ]; + const config = [ + { type: "foo", getLicense: neverCalledFn }, + { type: "bar", getLicense: neverCalledFn }, + { type: "baz", getLicense: neverCalledFn }, + ]; await checkIncompatibleKeySystemsErrorMessage(config); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(3); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, "foo", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "bar", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(3, "baz", defaultKSConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(6); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "bar", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "bar", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 5, + "baz", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 6, + "baz", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should throw if given a single incompatible keySystemsConfigs", async () => { @@ -127,10 +190,23 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "foo", + getLicense: neverCalledFn, + }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(defaultKSConfig) + ); }); /* eslint-disable max-len */ @@ -140,15 +216,28 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn, - persistentStateRequired: true }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "foo", + getLicense: + neverCalledFn, persistentStateRequired: true, + }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - const expectedConfig = defaultKSConfig.map(conf => { + const expectedConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map((conf) => { return { ...conf, persistentState: "required" }; }); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", expectedConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + expectedConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(expectedConfig) + ); }); /* eslint-disable max-len */ @@ -158,11 +247,28 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn, - persistentStateRequired: false }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "foo", + getLicense: neverCalledFn, + persistentStateRequired: false, + }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + + const expectedConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map((conf) => { + return { ...conf, persistentState: "optional" }; + }); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + expectedConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(expectedConfig) + ); }); /* eslint-disable max-len */ @@ -172,18 +278,28 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage( - [{ + await checkIncompatibleKeySystemsErrorMessage([ + { type: "foo", getLicense: neverCalledFn, distinctiveIdentifierRequired: true, - }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); + }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - const expectedConfig = defaultKSConfig.map(conf => { + const expectedConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map((conf) => { return { ...conf, distinctiveIdentifier: "required" }; }); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", expectedConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + expectedConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(expectedConfig) + ); }); /* eslint-disable max-len */ @@ -199,22 +315,21 @@ describe("core - decrypt - global tests - media key system access", () => { getLicense: neverCalledFn, distinctiveIdentifierRequired: false, }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); - }); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - it("should do nothing if just licenseStorage is set", async () => { - const mockRequestMediaKeySystemAccess = jest.fn(() => Promise.reject("nope")); - mockCompat({ - requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + const expectedConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map((conf) => { + return { ...conf, distinctiveIdentifier: "optional" }; }); - const licenseStorage = { save() { throw new Error("Should not save."); }, - load() { throw new Error("Should not load."); } }; - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn, - licenseStorage }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + expectedConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(expectedConfig) + ); }); /* eslint-disable max-len */ @@ -224,21 +339,37 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - const licenseStorage = { save() { throw new Error("Should not save."); }, - load() { throw new Error("Should not load."); } }; + const licenseStorage = { + save() { + throw new Error("Should not save."); + }, + load() { + throw new Error("Should not load."); + }, + }; await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", getLicense: neverCalledFn, licenseStorage, persistentLicense: true }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - const expectedConfig = defaultKSConfig.map(conf => { - return { ...conf, - persistentState: "required", - sessionTypes: ["temporary", - "persistent-license"] }; + const expectedConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; }); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", expectedConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + expectedConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(expectedConfig) + ); }); /* eslint-disable max-len */ @@ -248,17 +379,40 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn, - persistentLicense: true }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - - const expectedConfig = defaultKSConfig.map(conf => { - return { ...conf, - persistentState: "required", - sessionTypes: ["temporary", "persistent-license"] }; - }); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", expectedConfig); + await checkIncompatibleKeySystemsErrorMessage( + [{ + type: "foo", + getLicense: neverCalledFn, + persistentLicense: true, + licenseStorage: { + load() { + return []; + }, + save() { + /* noop */ + }, + }, + }] + ); + const persistentConfig: MediaKeySystemConfiguration[] = + defaultKSConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + }); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + persistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(persistentConfig) + ); }); it("should do nothing if persistentLicense is set to false", async () => { @@ -266,11 +420,24 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "foo", - getLicense: neverCalledFn, - persistentLicense: false }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith("foo", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "foo", + getLicense: neverCalledFn, + persistentLicense: false, + }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "foo", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should translate a `clearkey` keySystem", async () => { @@ -278,13 +445,30 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "clearkey", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, "webkit-org.w3.clearkey", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "org.w3.clearkey", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "clearkey", getLicense: neverCalledFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(4); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "webkit-org.w3.clearkey", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "webkit-org.w3.clearkey", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "org.w3.clearkey", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "org.w3.clearkey", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should translate a `widevine` keySystem", async () => { @@ -292,11 +476,20 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "widevine", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenCalledWith("com.widevine.alpha", defaultWidevineConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "widevine", getLicense: neverCalledFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.widevine.alpha", + defaultWidevineConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.widevine.alpha", + removeCapabilitiesConfig(defaultWidevineConfig) + ); }); it("should translate a `playready` keySystem", async () => { @@ -304,19 +497,50 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "playready", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(4); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, - "com.microsoft.playready.recommendation", - defaultPRRecommendationKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "com.microsoft.playready", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(3, "com.chromecast.playready", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(4, "com.youtube.playready", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "playready", getLicense: neverCalledFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(8); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.microsoft.playready.recommendation", + defaultPRRecommendationKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.microsoft.playready.recommendation", + removeCapabilitiesConfig(defaultPRRecommendationKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "com.microsoft.playready", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "com.microsoft.playready", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 5, + "com.chromecast.playready", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 6, + "com.chromecast.playready", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 7, + "com.youtube.playready", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 8, + "com.youtube.playready", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should translate a `fairplay` keySystem", async () => { @@ -324,11 +548,18 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "fairplay", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenCalledWith("com.apple.fps.1_0", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "fairplay", getLicense: neverCalledFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith( + "com.apple.fps.1_0", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith( + "com.apple.fps.1_0", + removeCapabilitiesConfig(defaultKSConfig) + ); }); it("should translate a multiple keySystems at the same time", async () => { @@ -336,25 +567,61 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "playready", - getLicense: neverCalledFn }, - { type: "clearkey", - getLicense: neverCalledFn }]); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(6); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, - "com.microsoft.playready.recommendation", - defaultPRRecommendationKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "com.microsoft.playready", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(3, "com.chromecast.playready", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(4, "com.youtube.playready", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(5, "webkit-org.w3.clearkey", defaultKSConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(6, "org.w3.clearkey", defaultKSConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { type: "playready", getLicense: neverCalledFn }, + { type: "clearkey", getLicense: neverCalledFn }, + ]); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(12); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.microsoft.playready.recommendation", + defaultPRRecommendationKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.microsoft.playready.recommendation", + removeCapabilitiesConfig(defaultPRRecommendationKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "com.microsoft.playready", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "com.microsoft.playready", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 5, + "com.chromecast.playready", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 6, + "com.chromecast.playready", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 9, + "webkit-org.w3.clearkey", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 10, + "webkit-org.w3.clearkey", + removeCapabilitiesConfig(defaultKSConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 11, + "org.w3.clearkey", + defaultKSConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 12, + "org.w3.clearkey", + removeCapabilitiesConfig(defaultKSConfig) + ); }); /* eslint-disable max-len */ @@ -364,68 +631,332 @@ describe("core - decrypt - global tests - media key system access", () => { mockCompat({ requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - await checkIncompatibleKeySystemsErrorMessage([{ type: "playready", - persistentLicense: true, - getLicense: neverCalledFn }, - { type: "clearkey", - distinctiveIdentifierRequired: true, - getLicense: neverCalledFn }]); - const expectedPRRecommendationPersistentConfig = - defaultPRRecommendationKSConfig.map(conf => { - return { ...conf, - persistentState: "required", - sessionTypes: ["temporary", - "persistent-license"] }; + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "playready", + persistentLicense: true, + licenseStorage: { + load() { + return []; + }, + save() { + return []; + }, + }, + getLicense: neverCalledFn, + }, + { + type: "clearkey", + distinctiveIdentifierRequired: true, + getLicense: neverCalledFn, + }, + ]); + const expectedPRRecommendationPersistentConfig: MediaKeySystemConfiguration[] = + defaultPRRecommendationKSConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + }); + const expectedPersistentConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map( + (conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + } + ); + const expectedIdentifierConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map( + (conf) => { + return { ...conf, distinctiveIdentifier: "required" }; + } + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(12); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.microsoft.playready.recommendation", + expectedPRRecommendationPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.microsoft.playready.recommendation", + removeCapabilitiesConfig(expectedPRRecommendationPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "com.microsoft.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "com.microsoft.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 5, + "com.chromecast.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 6, + "com.chromecast.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 7, + "com.youtube.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 8, + "com.youtube.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 9, + "webkit-org.w3.clearkey", + expectedIdentifierConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 10, + "webkit-org.w3.clearkey", + removeCapabilitiesConfig(expectedIdentifierConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 11, + "org.w3.clearkey", + expectedIdentifierConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 12, + "org.w3.clearkey", + removeCapabilitiesConfig(expectedIdentifierConfig) + ); + }); + + /* eslint-disable max-len */ + it("should set widevine robustnesses for a `com.widevine.alpha` keySystem", async () => { + /* eslint-enable max-len */ + const mockRequestMediaKeySystemAccess = jest.fn(() => Promise.reject("nope")); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + }); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "com.widevine.alpha", + persistentLicense: true, + licenseStorage: { + load() { + return []; + }, + save() { + return []; + }, + }, + getLicense: neverCalledFn, + }, + ]); + const expectedPersistentConfig: MediaKeySystemConfiguration[] = + defaultWidevineConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; }); - const expectedPersistentConfig = defaultKSConfig.map(conf => { - return { ...conf, - persistentState: "required", - sessionTypes: ["temporary", - "persistent-license"] }; + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.widevine.alpha", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.widevine.alpha", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + }); + + it("should set playready robustnesses for a `playready` keySystem", async () => { + const mockRequestMediaKeySystemAccess = jest.fn(() => Promise.reject("nope")); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - const expectedIdentifierConfig = defaultKSConfig.map(conf => { - return { ...conf, distinctiveIdentifier: "required" }; + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "playready", + persistentLicense: true, + licenseStorage: { + load() { + return []; + }, + save() { + return []; + }, + }, + getLicense: neverCalledFn, + }, + { + type: "clearkey", + distinctiveIdentifierRequired: true, + getLicense: neverCalledFn, + }, + ]); + const expectedPersistentConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map( + (conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + } + ); + const expectedRecoPersistentConfig: MediaKeySystemConfiguration[] = + defaultPRRecommendationKSConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + }); + const expectedIdentifierConfig: MediaKeySystemConfiguration[] = defaultKSConfig.map( + (conf) => { + return { ...conf, distinctiveIdentifier: "required" }; + } + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(12); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.microsoft.playready.recommendation", + expectedRecoPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.microsoft.playready.recommendation", + removeCapabilitiesConfig(expectedRecoPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "com.microsoft.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 4, + "com.microsoft.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 5, + "com.chromecast.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 6, + "com.chromecast.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 7, + "com.youtube.playready", + expectedPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 8, + "com.youtube.playready", + removeCapabilitiesConfig(expectedPersistentConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 9, + "webkit-org.w3.clearkey", + expectedIdentifierConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 10, + "webkit-org.w3.clearkey", + removeCapabilitiesConfig(expectedIdentifierConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 11, + "org.w3.clearkey", + expectedIdentifierConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 12, + "org.w3.clearkey", + removeCapabilitiesConfig(expectedIdentifierConfig) + ); + }); + + /* eslint-disable max-len */ + it("should set playready robustnesses for a `com.microsoft.playready.recommendation` keySystem", async () => { + /* eslint-enable max-len */ + const mockRequestMediaKeySystemAccess = jest.fn(() => Promise.reject("nope")); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, }); - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(6); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, - "com.microsoft.playready.recommendation", - expectedPRRecommendationPersistentConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "com.microsoft.playready", expectedPersistentConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(3, "com.chromecast.playready", expectedPersistentConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(4, "com.youtube.playready", expectedPersistentConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(5, "webkit-org.w3.clearkey", expectedIdentifierConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(6, "org.w3.clearkey", expectedIdentifierConfig); + await checkIncompatibleKeySystemsErrorMessage([ + { + type: "com.microsoft.playready.recommendation", + persistentLicense: true, + licenseStorage: { + load() { + return []; + }, + save() { + return []; + }, + }, + getLicense: neverCalledFn, + }, + ]); + const expectedRecoPersistentConfig: MediaKeySystemConfiguration[] = + defaultPRRecommendationKSConfig.map((conf) => { + return { + ...conf, + persistentState: "required", + sessionTypes: ["temporary", "persistent-license"], + }; + }); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.microsoft.playready.recommendation", + expectedRecoPersistentConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.microsoft.playready.recommendation", + removeCapabilitiesConfig(expectedRecoPersistentConfig) + ); }); /* eslint-disable max-len */ it("should successfully create a MediaKeySystemAccess if given the right configuration", async () => { /* eslint-enable max-len */ + const mockRequestMediaKeySystemAccess = jest.fn((keyType, conf) => { + return requestMediaKeySystemAccessNoMediaKeys(keyType, conf); + }); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + }); + const ContentDecryptor = ((jest.requireActual("../../content_decryptor"))) + .default; return new Promise((res, rej) => { - const mockRequestMediaKeySystemAccess = jest.fn((keyType, conf) => { - return requestMediaKeySystemAccessNoMediaKeys(keyType, conf); - }); - mockCompat({ - requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, - }); - const config = [{ type: "com.widevine.alpha", - getLicense: neverCalledFn }]; + const config = [{ type: "com.widevine.alpha", getLicense: neverCalledFn }]; const mediaElement = document.createElement("video"); - const ContentDecryptor = jest.requireActual("../../content_decryptor").default; const contentDecryptor = new ContentDecryptor(mediaElement, config); contentDecryptor.addEventListener("error", (error: any) => { rej(error); }); setTimeout(() => { expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenCalledWith("com.widevine.alpha", defaultWidevineConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledWith( + "com.widevine.alpha", + defaultWidevineConfig + ); res(); }, 10); }); @@ -434,59 +965,75 @@ describe("core - decrypt - global tests - media key system access", () => { /* eslint-disable max-len */ it("should successfully create a MediaKeySystemAccess if given multiple configurations where one works", async () => { /* eslint-enable max-len */ + const mockRequestMediaKeySystemAccess = jest.fn((keyType, conf) => { + if (keyType === "some-other-working-key-system") { + return requestMediaKeySystemAccessNoMediaKeys(keyType, conf); + } + return Promise.reject("nope"); + }); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + }); + const ContentDecryptor = ((jest.requireActual("../../content_decryptor"))) + .default; return new Promise((res, rej) => { - let callNb = 0; - const mockRequestMediaKeySystemAccess = jest.fn((keyType, conf) => { - if (++callNb === 2) { - return requestMediaKeySystemAccessNoMediaKeys(keyType, conf); - } - return Promise.reject("nope"); - }); - mockCompat({ - requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, - }); - const config = [{ type: "com.widevine.alpha", - getLicense: neverCalledFn }, - { type: "some-other-working-key-system", - getLicense: neverCalledFn }]; + const config = [ + { type: "com.widevine.alpha", getLicense: neverCalledFn }, + { type: "some-other-working-key-system", getLicense: neverCalledFn }, + ]; const mediaElement = document.createElement("video"); - const ContentDecryptor = jest.requireActual("../../content_decryptor").default; const contentDecryptor = new ContentDecryptor(mediaElement, config); contentDecryptor.addEventListener("error", (error: any) => { rej(error); }); setTimeout(() => { - expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(2); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, "com.widevine.alpha", defaultWidevineConfig); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(2, "some-other-working-key-system", defaultKSConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(3); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "com.widevine.alpha", + defaultWidevineConfig + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 2, + "com.widevine.alpha", + removeCapabilitiesConfig(defaultWidevineConfig) + ); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 3, + "some-other-working-key-system", + defaultKSConfig + ); res(); }, 10); }); }); - it("should not continue to check if the ContentDecryptor is disposed from", () => { - return new Promise((res, rej) => { - let contentDecryptor : any = null; - let rmksHasBeenCalled = false; - const mockRequestMediaKeySystemAccess = jest.fn(() => { - return Promise.resolve().then(() => { - rmksHasBeenCalled = true; - contentDecryptor.dispose(); - return Promise.reject("nope"); - }); - }); - mockCompat({ - requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + /* eslint-disable max-len */ + it("should not continue to check if the ContentDecryptor is disposed from", async () => { + /* eslint-enable max-len */ + let contentDecryptor: any = null; + let rmksHasBeenCalled = false; + const mockRequestMediaKeySystemAccess = jest.fn(() => { + return Promise.resolve().then(() => { + rmksHasBeenCalled = true; + contentDecryptor.dispose(); + return Promise.reject("nope"); }); + }); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + }); + const ContentDecryptor = ((jest.requireActual("../../content_decryptor"))) + .default; + return new Promise((res, rej) => { const mediaElement = document.createElement("video"); - const ContentDecryptor = jest.requireActual("../../content_decryptor").default; - const config = [ { type: "foo", getLicense: neverCalledFn }, - { type: "bar", getLicense: neverCalledFn }, - { type: "baz", getLicense: neverCalledFn } ]; + const config = [ + { type: "foo", getLicense: neverCalledFn }, + { type: "bar", getLicense: neverCalledFn }, + { type: "baz", getLicense: neverCalledFn }, + ]; contentDecryptor = new ContentDecryptor(mediaElement, config); contentDecryptor.addEventListener("error", (error: any) => { rej(error); @@ -494,25 +1041,29 @@ describe("core - decrypt - global tests - media key system access", () => { setTimeout(() => { expect(rmksHasBeenCalled).toEqual(true); expect(mockRequestMediaKeySystemAccess).toHaveBeenCalledTimes(1); - expect(mockRequestMediaKeySystemAccess) - .toHaveBeenNthCalledWith(1, "foo", defaultKSConfig); + expect(mockRequestMediaKeySystemAccess).toHaveBeenNthCalledWith( + 1, + "foo", + defaultKSConfig + ); res(); }, 10); }); }); - it("should trigger error even if requestMediaKeySystemAccess throws", () => { + it("should trigger error even if requestMediaKeySystemAccess throws", async () => { + let rmksHasBeenCalled = false; + const mockRequestMediaKeySystemAccess = jest.fn(() => { + rmksHasBeenCalled = true; + throw new Error("nope"); + }); + mockCompat({ + requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, + }); + const ContentDecryptor = ((jest.requireActual("../../content_decryptor"))) + .default; return new Promise((res, rej) => { - let rmksHasBeenCalled = false; - const mockRequestMediaKeySystemAccess = jest.fn(() => { - rmksHasBeenCalled = true; - throw new Error("nope"); - }); - mockCompat({ - requestMediaKeySystemAccess: mockRequestMediaKeySystemAccess, - }); const mediaElement = document.createElement("video"); - const ContentDecryptor = jest.requireActual("../../content_decryptor").default; const config = [{ type: "foo", getLicense: neverCalledFn }]; const contentDecryptor = new ContentDecryptor(mediaElement, config); diff --git a/src/core/decrypt/__tests__/__global__/utils.ts b/src/core/decrypt/__tests__/__global__/utils.ts index 2f43f98e5f..b5a9a3be2e 100644 --- a/src/core/decrypt/__tests__/__global__/utils.ts +++ b/src/core/decrypt/__tests__/__global__/utils.ts @@ -43,7 +43,7 @@ import { import { CancellationSignal } from "../../../../utils/task_canceller"; /** Default MediaKeySystemAccess configuration used by the RxPlayer. */ -export const defaultKSConfig = [ +export const defaultKSConfig: MediaKeySystemConfiguration[] = [ { audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, { contentType: "audio/webm;codecs=opus" } ], @@ -55,21 +55,13 @@ export const defaultKSConfig = [ { contentType: "video/mp4;codecs=\"avc1.42e01e\"" }, { contentType: "video/webm;codecs=\"vp8\"" } ], }, - { - audioCapabilities: undefined, - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: undefined, - }, ]; /** * Default "com.microsoft.playready.recommendation" MediaKeySystemAccess * configuration used by the RxPlayer. */ -export const defaultPRRecommendationKSConfig = [ +export const defaultPRRecommendationKSConfig: MediaKeySystemConfiguration[] = [ { audioCapabilities: [ { robustness: "3000", contentType: "audio/mp4;codecs=\"mp4a.40.2\"" }, @@ -96,14 +88,6 @@ export const defaultPRRecommendationKSConfig = [ { robustness: "2000", contentType: "video/webm;codecs=\"vp8\"" } ], }, - { - audioCapabilities: undefined, - distinctiveIdentifier: "optional" as const, - initDataTypes: ["cenc"] as const, - persistentState: "optional" as const, - sessionTypes: ["temporary"] as const, - videoCapabilities: undefined, - }, ]; /** Default Widevine MediaKeySystemAccess configuration used by the RxPlayer. */ @@ -129,7 +113,6 @@ export const defaultWidevineConfig = (() => { }); return [ { ...defaultKSConfig[0], audioCapabilities, videoCapabilities }, - defaultKSConfig[1], ]; })(); diff --git a/src/core/decrypt/attach_media_keys.ts b/src/core/decrypt/attach_media_keys.ts index 4aa4dc4497..af2738664a 100644 --- a/src/core/decrypt/attach_media_keys.ts +++ b/src/core/decrypt/attach_media_keys.ts @@ -60,6 +60,7 @@ export default async function attachMediaKeys( mediaElement : HTMLMediaElement, { emeImplementation, keySystemOptions, + askedConfiguration, loadedSessionsStore, mediaKeySystemAccess, mediaKeys } : IMediaKeysState, @@ -79,11 +80,14 @@ export default async function attachMediaKeys( throw cancelSignal.cancellationError; } - MediaKeysInfosStore.setState(mediaElement, { emeImplementation, - keySystemOptions, - mediaKeySystemAccess, - mediaKeys, - loadedSessionsStore }); + MediaKeysInfosStore.setState(mediaElement, { + emeImplementation, + keySystemOptions, + mediaKeySystemAccess, + mediaKeys, + loadedSessionsStore, + askedConfiguration, + }); if (mediaElement.mediaKeys === mediaKeys) { return ; } @@ -112,6 +116,11 @@ export interface IMediaKeysState { /** The MediaKeys instance to attach to the media element. */ mediaKeys : MediaKeys | ICustomMediaKeys; + /** + * The MediaKeySystemConfiguration that has been provided to the + * `requestMediaKeySystemAccess` API. + */ + askedConfiguration: MediaKeySystemConfiguration; /** * The chosen EME implementation abstraction linked to `mediaKeys`. * Different EME implementation might for example be used while debugging or diff --git a/src/core/decrypt/content_decryptor.ts b/src/core/decrypt/content_decryptor.ts index 128ad252e5..d04552f932 100644 --- a/src/core/decrypt/content_decryptor.ts +++ b/src/core/decrypt/content_decryptor.ts @@ -247,7 +247,8 @@ export default class ContentDecryptor extends EventEmitter} keySystems + * @param {Object} askedConfiguration * @param {MediaKeySystemAccess} currentKeySystemAccess * @param {Object} currentKeySystemOptions * @returns {null|Object} */ function checkCachedMediaKeySystemAccess( keySystems: IKeySystemOption[], + askedConfiguration: MediaKeySystemConfiguration, currentKeySystemAccess: MediaKeySystemAccess|ICustomMediaKeySystemAccess, currentKeySystemOptions: IKeySystemOption ) : null | { keySystemOptions: IKeySystemOption; + askedConfiguration: MediaKeySystemConfiguration; keySystemAccess: MediaKeySystemAccess|ICustomMediaKeySystemAccess; } { const mksConfiguration = currentKeySystemAccess.getConfiguration(); @@ -109,8 +121,11 @@ function checkCachedMediaKeySystemAccess( })[0]; if (firstCompatibleOption != null) { - return { keySystemOptions: firstCompatibleOption, - keySystemAccess: currentKeySystemAccess }; + return { + keySystemOptions: firstCompatibleOption, + keySystemAccess: currentKeySystemAccess, + askedConfiguration, + }; } return null; } @@ -240,6 +255,14 @@ function buildKeySystemConfigurations( sessionTypes, }; + if ( + !isNullOrUndefined(keySystem.audioRobustnesses) || + !isNullOrUndefined(keySystem.videoRobustnesses) + ) { + // If the user specifically asked for robustnesses, we don't want to try without them + return [wantedMediaKeySystemConfiguration]; + } + return [ wantedMediaKeySystemConfiguration, @@ -282,14 +305,18 @@ export default function getMediaKeySystemAccess( // one as exactly the same compatibility options. const cachedKeySystemAccess = checkCachedMediaKeySystemAccess(keySystemsConfigs, + currentState.askedConfiguration, currentState.mediaKeySystemAccess, currentState.keySystemOptions); if (cachedKeySystemAccess !== null) { log.info("DRM: Found cached compatible keySystem"); return Promise.resolve({ type: "reuse-media-key-system-access" as const, - value: { mediaKeySystemAccess: cachedKeySystemAccess.keySystemAccess, - options: cachedKeySystemAccess.keySystemOptions }, + value: { + mediaKeySystemAccess: cachedKeySystemAccess.keySystemAccess, + askedConfiguration: cachedKeySystemAccess.askedConfiguration, + options: cachedKeySystemAccess.keySystemOptions, + }, }); } } @@ -360,22 +387,33 @@ export default function getMediaKeySystemAccess( keyType, keySystemOptions); - log.debug(`DRM: Request keysystem access ${keyType},` + - `${index + 1} of ${keySystemsType.length}`); - - try { - const keySystemAccess = await testKeySystem(keyType, keySystemConfigurations); - log.info("DRM: Found compatible keysystem", keyType, index + 1); - return { type: "create-media-key-system-access" as const, - value: { options: keySystemOptions, - mediaKeySystemAccess: keySystemAccess } }; - } catch (_) { - log.debug("DRM: Rejected access to keysystem", keyType, index + 1); - if (cancelSignal.cancellationError !== null) { - throw cancelSignal.cancellationError; + log.debug( + `DRM: Request keysystem access ${keyType},` + + `${index + 1} of ${keySystemsType.length}` + ); + + let keySystemAccess; + for (let configIdx = 0; configIdx < keySystemConfigurations.length; configIdx++) { + const keySystemConfiguration = keySystemConfigurations[configIdx]; + try { + keySystemAccess = await testKeySystem(keyType, [keySystemConfiguration]); + log.info("DRM: Found compatible keysystem", keyType, index + 1); + return { + type: "create-media-key-system-access" as const, + value: { + options: keySystemOptions, + askedConfiguration: keySystemConfiguration, + mediaKeySystemAccess: keySystemAccess, + }, + }; + } catch (_) { + log.debug("DRM: Rejected access to keysystem", keyType, index + 1, configIdx); + if (cancelSignal.cancellationError !== null) { + throw cancelSignal.cancellationError; + } } - return recursivelyTestKeySystems(index + 1); } + return recursivelyTestKeySystems(index + 1); } } diff --git a/src/core/decrypt/get_media_keys.ts b/src/core/decrypt/get_media_keys.ts index 3214f664bc..7a3171b33b 100644 --- a/src/core/decrypt/get_media_keys.ts +++ b/src/core/decrypt/get_media_keys.ts @@ -58,6 +58,11 @@ export interface IMediaKeysInfos { /** The MediaKeySystemAccess which allowed to create the MediaKeys instance. */ mediaKeySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess; + /** + * The MediaKeySystemConfiguration that has been provided to the + * `requestMediaKeySystemAccess` API. + */ + askedConfiguration: MediaKeySystemConfiguration; /** The MediaKeys instance. */ mediaKeys : MediaKeys | ICustomMediaKeys; @@ -92,7 +97,7 @@ export default async function getMediaKeysInfos( throw cancelSignal.cancellationError; } - const { options, mediaKeySystemAccess } = evt.value; + const { options, mediaKeySystemAccess, askedConfiguration } = evt.value; const currentState = MediaKeysInfosStore.getState(mediaElement); const persistentSessionsStore = createPersistentSessionsStorage(options); @@ -109,10 +114,13 @@ export default async function getMediaKeysInfos( (!isNullOrUndefined(options.serverCertificate) && ServerCertificateStore.has(mediaKeys, options.serverCertificate))) { - return { mediaKeys, - mediaKeySystemAccess, - stores: { loadedSessionsStore, persistentSessionsStore }, - options }; + return { + mediaKeys, + mediaKeySystemAccess, + askedConfiguration, + stores: { loadedSessionsStore, persistentSessionsStore }, + options, + }; } } @@ -120,10 +128,13 @@ export default async function getMediaKeysInfos( const mediaKeys = await createMediaKeys(mediaKeySystemAccess); log.info("DRM: MediaKeys created with success"); const loadedSessionsStore = new LoadedSessionsStore(mediaKeys); - return { mediaKeys, - mediaKeySystemAccess, - stores: { loadedSessionsStore, persistentSessionsStore }, - options }; + return { + mediaKeys, + mediaKeySystemAccess, + askedConfiguration, + stores: { loadedSessionsStore, persistentSessionsStore }, + options, + }; } /** diff --git a/src/core/decrypt/utils/media_keys_infos_store.ts b/src/core/decrypt/utils/media_keys_infos_store.ts index 862a864c6b..8901c159b5 100644 --- a/src/core/decrypt/utils/media_keys_infos_store.ts +++ b/src/core/decrypt/utils/media_keys_infos_store.ts @@ -29,6 +29,12 @@ export interface IMediaElementMediaKeysInfos { /** Last keySystemOptions used with that HTMLMediaElement. */ keySystemOptions : IKeySystemOption; + /** + * The actual MediaKeySystemConfiguration asked to the + * `requestMediaKeySystemAccess` API. + */ + askedConfiguration: MediaKeySystemConfiguration; + /** * Last MediaKeySystemAccess used to create a MediaKeys bound to that * HTMLMediaElement.