rx-player
Version:
Canal+ HTML5 Video Player
339 lines (309 loc) • 13 kB
text/typescript
import { describe, beforeEach, it, expect, vi, afterEach } from "vitest";
import getEmeApiImplementation from "../../../../compat/eme";
import type { IKeySystemOption } from "../../../../public_types";
import assert from "../../../../utils/assert";
import ContentDecryptor from "../../content_decryptor";
import { ContentDecryptorState } from "../../types";
import { MediaKeysImpl, MediaKeySystemAccessImpl, mockCompat } from "./utils";
const mocks = vi.hoisted(() => {
return {
shouldRenewMediaKeySystemAccess: vi.fn(() => false),
canReuseMediaKeys: vi.fn(() => true),
onEncrypted: vi.fn(),
requestMediaKeySystemAccess: vi.fn(),
setMediaKeys: vi.fn(),
getInitData: vi.fn(),
generateKeyRequest: vi.fn(),
};
});
vi.mock("../../../../compat/should_renew_media_key_system_access", () => ({
default: mocks.shouldRenewMediaKeySystemAccess,
}));
vi.mock("../../../../compat/can_reuse_media_keys", () => ({
default: mocks.canReuseMediaKeys,
}));
vi.mock("../../../../compat/eme", () => ({
default: () => ({
onEncrypted: mocks.onEncrypted,
requestMediaKeySystemAccess: mocks.requestMediaKeySystemAccess,
setMediaKeys: mocks.setMediaKeys,
}),
getInitData: mocks.getInitData,
generateKeyRequest: mocks.generateKeyRequest,
closeSession: vi.fn(),
loadSession: vi.fn(),
}));
describe("decrypt - global tests - server certificate", () => {
const mockGetLicense = vi.fn(() => {
return new Promise<BufferSource>(() => {
/* noop */
});
});
const serverCertificate = new Uint8Array([1, 2, 3]);
/** Default keySystems configuration used in our tests. */
const ksConfigCert: IKeySystemOption[] = [
{
type: "com.widevine.alpha",
getLicense: mockGetLicense,
serverCertificate,
},
];
beforeEach(() => {
vi.resetModules();
vi.restoreAllMocks();
});
afterEach(() => {
mocks.shouldRenewMediaKeySystemAccess.mockReset();
mocks.canReuseMediaKeys.mockReset();
mocks.onEncrypted.mockReset();
mocks.requestMediaKeySystemAccess.mockReset();
mocks.setMediaKeys.mockReset();
mocks.getInitData.mockReset();
mocks.generateKeyRequest.mockReset();
});
it("should set the serverCertificate only after the MediaKeys is attached", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
mocks.setMediaKeys.mockImplementation(() => {
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
return Promise.resolve();
});
const mockCreateSession = vi.spyOn(MediaKeysImpl.prototype, "createSession");
const mockSetServerCertificate = vi
.spyOn(MediaKeysImpl.prototype, "setServerCertificate")
.mockImplementation((_serverCertificate: BufferSource): Promise<true> => {
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockCreateSession).not.toHaveBeenCalled();
return Promise.resolve(true);
});
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
return new Promise<void>((res) => {
contentDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
contentDecryptor.removeEventListener("stateChange");
setTimeout(() => {
expect(mocks.setMediaKeys).not.toHaveBeenCalled();
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
contentDecryptor.attach();
}, 5);
setTimeout(() => {
contentDecryptor.dispose(undefined);
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockSetServerCertificate).toHaveBeenCalledTimes(1);
expect(mockCreateSession).not.toHaveBeenCalled();
mockSetServerCertificate.mockRestore();
mockCreateSession.mockRestore();
res();
}, 10);
}
});
});
});
it("should not call serverCertificate multiple times on init data", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
mocks.setMediaKeys.mockImplementation(() => {
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
return Promise.resolve();
});
const mockCreateSession = vi.spyOn(MediaKeysImpl.prototype, "createSession");
const mockSetServerCertificate = vi
.spyOn(MediaKeysImpl.prototype, "setServerCertificate")
.mockImplementation((_serverCertificate: BufferSource): Promise<true> => {
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockCreateSession).not.toHaveBeenCalled();
return Promise.resolve(true);
});
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
contentDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
contentDecryptor.removeEventListener("stateChange");
setTimeout(() => {
expect(mocks.setMediaKeys).not.toHaveBeenCalled();
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
const initData = new Uint8Array([54, 55, 75]);
contentDecryptor.onInitializationData({
type: "cenc2",
values: [{ systemId: "15", data: initData }],
});
contentDecryptor.attach();
}, 5);
}
});
return new Promise<void>((res) => {
setTimeout(() => {
contentDecryptor.dispose(undefined);
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockSetServerCertificate).toHaveBeenCalledTimes(1);
expect(mockCreateSession).toHaveBeenCalledTimes(1);
res();
}, 100);
});
});
it("should emit warning if serverCertificate call rejects but still continue", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
const mockCreateSession = vi.spyOn(MediaKeysImpl.prototype, "createSession");
const mockSetServerCertificate = vi
.spyOn(MediaKeysImpl.prototype, "setServerCertificate")
.mockImplementation((_serverCertificate: BufferSource) => {
throw new Error("some error");
});
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
contentDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
contentDecryptor.removeEventListener("stateChange");
contentDecryptor.attach();
}
});
let warningsReceived = 0;
contentDecryptor.addEventListener("warning", (w) => {
expect(w.code).toEqual("LICENSE_SERVER_CERTIFICATE_ERROR");
expect(w.type).toEqual("ENCRYPTED_MEDIA_ERROR");
warningsReceived++;
});
return new Promise<void>((res) => {
setTimeout(() => {
contentDecryptor.dispose(undefined);
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockSetServerCertificate).toHaveBeenCalledTimes(1);
expect(mockCreateSession).not.toHaveBeenCalled();
expect(warningsReceived).toEqual(1);
res();
}, 10);
});
});
it("should emit warning if serverCertificate call throws but still continue", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
const mockCreateSession = vi.spyOn(MediaKeysImpl.prototype, "createSession");
const mockSetServerCertificate = vi
.spyOn(MediaKeysImpl.prototype, "setServerCertificate")
.mockImplementation((_serverCertificate: BufferSource) => {
return Promise.reject(new Error("some error"));
});
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
contentDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
contentDecryptor.removeEventListener("stateChange");
contentDecryptor.attach();
}
});
let warningsReceived = 0;
contentDecryptor.addEventListener("warning", (w) => {
expect(w.code).toEqual("LICENSE_SERVER_CERTIFICATE_ERROR");
expect(w.type).toEqual("ENCRYPTED_MEDIA_ERROR");
warningsReceived++;
});
return new Promise<void>((res) => {
setTimeout(() => {
contentDecryptor.dispose(undefined);
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockSetServerCertificate).toHaveBeenCalledTimes(1);
expect(mockCreateSession).not.toHaveBeenCalled();
expect(warningsReceived).toEqual(1);
res();
}, 10);
});
});
it("should just continue if setServerCertificate is undefined", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
vi.spyOn(MediaKeySystemAccessImpl.prototype, "createMediaKeys").mockImplementation(
() => {
const mediaKeys = new MediaKeysImpl();
(mediaKeys as { setServerCertificate?: unknown }).setServerCertificate =
undefined;
return Promise.resolve(mediaKeys);
},
);
mocks.setMediaKeys.mockImplementation(() => {
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
return Promise.resolve();
});
const mockCreateSession = vi.spyOn(MediaKeysImpl.prototype, "createSession");
const mockSetServerCertificate = vi.spyOn(
MediaKeysImpl.prototype,
"setServerCertificate",
);
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
contentDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
contentDecryptor.removeEventListener("stateChange");
setTimeout(() => {
expect(mocks.setMediaKeys).not.toHaveBeenCalled();
expect(mockCreateSession).not.toHaveBeenCalled();
expect(mockSetServerCertificate).not.toHaveBeenCalled();
const initData = new Uint8Array([54, 55, 75]);
contentDecryptor.onInitializationData({
type: "cenc2",
values: [{ systemId: "15", data: initData }],
});
contentDecryptor.attach();
}, 5);
}
});
return new Promise<void>((res) => {
setTimeout(() => {
contentDecryptor.dispose(undefined);
expect(mocks.setMediaKeys).toHaveBeenCalledTimes(1);
expect(mockSetServerCertificate).not.toHaveBeenCalled();
expect(mockCreateSession).toHaveBeenCalledTimes(1);
res();
}, 10);
});
});
it("should continue if setServerCertificate resolves to false and retry later", () => {
const videoElt = document.createElement("video");
mockCompat(mocks);
const mockSetServerCertificate = vi
.spyOn(MediaKeysImpl.prototype, "setServerCertificate")
.mockImplementationOnce((_serverCertificate: BufferSource): Promise<false> => {
return Promise.resolve(false);
})
.mockImplementationOnce((_serverCertificate: BufferSource): Promise<true> => {
return Promise.resolve(true);
});
const eme = getEmeApiImplementation("auto");
assert(eme !== null, "Expected to have an EME implementation");
const firstDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
return new Promise<void>((res) => {
firstDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
firstDecryptor.removeEventListener("stateChange");
firstDecryptor.attach();
}
});
setTimeout(() => {
firstDecryptor.dispose(undefined);
const secondDecryptor = new ContentDecryptor(eme, videoElt, ksConfigCert);
secondDecryptor.addEventListener("stateChange", (state) => {
if (state === ContentDecryptorState.WaitingForAttachment) {
secondDecryptor.removeEventListener("stateChange");
secondDecryptor.attach();
}
});
setTimeout(() => {
secondDecryptor.dispose(undefined);
expect(mockSetServerCertificate).toHaveBeenCalledTimes(2);
res();
}, 10);
}, 10);
});
});
});