Skip to content

Commit

Permalink
Add DRM integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
peaBerberian committed Jul 9, 2024
1 parent 340b67c commit 7743e95
Show file tree
Hide file tree
Showing 36 changed files with 494 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tests/contents/DASH_DRM_static_SegmentTemplate/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import manifestInfos from "./infos.mjs";

export { manifestInfos };
31 changes: 31 additions & 0 deletions tests/contents/DASH_DRM_static_SegmentTemplate/infos.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const BASE_URL =
"http://" +
/* eslint-disable no-undef */
__TEST_CONTENT_SERVER__.URL +
":" +
__TEST_CONTENT_SERVER__.PORT +
/* eslint-enable no-undef */
"/DASH_DRM_static_SegmentTemplate/media/";

export default {
url: BASE_URL + "encrypted_multiple_keys_number.mpd",
transport: "dash",
isDynamic: false,
isLive: false,
duration: 734,
minimumPosition: 0,
maximumPosition: 734,
availabilityStartTime: 0,
periods: [
{
start: 0,
duration: 734,
// TODO?
adaptations: {
audio: [],
video: [],
text: [],
},
},
],
};
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

138 changes: 138 additions & 0 deletions tests/contents/DASH_DRM_static_SegmentTemplate/urls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* eslint-env node */

import * as path from "path";
import patchSegmentWithTimeOffset from "../utils/patchSegmentWithTimeOffset.mjs";
import { fileURLToPath } from "url";

const currentDirectory = path.dirname(fileURLToPath(import.meta.url));

const baseURL = "/DASH_DRM_static_SegmentTemplate/media/";

const segments = [];

// video
[
"8-80399bf5",
"9-80399bf5",
"10-80399bf5",
"11-90953e09",
"12-90953e09",
"1-80399bf5",
"2-80399bf5",
"3-80399bf5",
"4-90953e09",
"5-90953e09",
].forEach((dir) => {
segments.push({
url: baseURL + dir + "/init.mp4",
path: path.join(currentDirectory, `./media/${dir}/init.mp4`),
content: "video/mp4",
});
const duration = [
"1-80399bf5",
"2-80399bf5",
"3-80399bf5",
"4-90953e09",
"5-90953e09",
].includes(dir)
? 96
: 4799983;
for (let i = 1; i <= 184; i++) {
const nb = String(i).padStart(4, "0");
segments.push({
url: baseURL + dir + `/${nb}.m4s`,
path: path.join(currentDirectory, `./media/${dir}/0001.m4s`),
postProcess: (buffer) => patchMp4(buffer, (i - 1) * duration),
content: "video/mp4",
});
}
});

[
// audio
"15-585f233f",
"16-4222bd78",
"17",
].forEach((dir) => {
segments.push({
url: baseURL + dir + "/init.mp4",
path: path.join(currentDirectory, `./media/${dir}/init.mp4`),
content: "audio/mp4",
});
const duration = 95232;
for (let i = 1; i <= 185; i++) {
const nb = String(i).padStart(4, "0");
segments.push({
url: baseURL + dir + `/${nb}.m4s`,
path: path.join(currentDirectory, `./media/${dir}/0001.m4s`),
postProcess: (buffer) => patchMp4(buffer, (i - 1) * duration),
content: "audio/mp4",
});
}
});

[
// subtitles
"19",
"27",
].forEach((dir) => {
segments.push({
url: baseURL + dir + "/init.mp4",
path: path.join(currentDirectory, `./media/${dir}/init.mp4`),
content: "application/mp4",
});
const duration = 4000;
for (let i = 1; i <= 184; i++) {
const nb = String(i).padStart(4, "0");
segments.push({
url: baseURL + dir + `/${nb}.m4s`,
path: path.join(currentDirectory, `./media/${dir}/0001.m4s`),
postProcess: (buffer) => patchMp4(buffer, (i - 1) * duration),
content: "application/mp4",
});
}
});

export default [
// Manifest
{
url: baseURL + "encrypted_multiple_keys_number.mpd",
path: path.join(currentDirectory, "./media/encrypted_multiple_keys_number.mpd"),
contentType: "application/dash+xml",
},
...segments,
];

/**
* Translate groups of 4 big-endian bytes to Integer.
* @param {Uint8Array} bytes
* @param {Number} offset - The offset (from the start of the given array)
* @returns {Number}
*/
function be4toi(bytes, offset) {
return (
bytes[offset + 0] * 0x1000000 +
bytes[offset + 1] * 0x0010000 +
bytes[offset + 2] * 0x0000100 +
bytes[offset + 3]
);
}

function patchMp4(buffer, startTime) {
const bufferu8 = new Uint8Array(buffer);
let ret = bufferu8;

// If it just finishes with "mdat", fill in the rest with 0
if (
bufferu8[bufferu8.length - 4] === 0x6d &&
bufferu8[bufferu8.length - 3] === 0x64 &&
bufferu8[bufferu8.length - 2] === 0x61 &&
bufferu8[bufferu8.length - 1] === 0x74
) {
const mdatLen = be4toi(bufferu8, bufferu8.length - 8);
const remainingLength = mdatLen - 8;
ret = new Uint8Array(bufferu8.length + remainingLength);
ret.set(bufferu8, 0);
}
return patchSegmentWithTimeOffset(ret, startTime).buffer;
}
2 changes: 2 additions & 0 deletions tests/contents/urls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import urls8 from "./directfile_webm/urls.mjs";
import urls9 from "./DASH_dynamic_SegmentTemplate_Multi_Periods/urls.mjs";
import urls10 from "./DASH_static_broken_cenc_in_MPD/urls.mjs";
import urls11 from "./DASH_static_number_based_SegmentTimeline/urls.mjs";
import urls12 from "./DASH_DRM_static_SegmentTemplate/urls.mjs";

export default [
...urls1,
Expand All @@ -26,4 +27,5 @@ export default [
...urls9,
...urls10,
...urls11,
...urls12,
];
193 changes: 193 additions & 0 deletions tests/integration/scenarios/drm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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 { lockLowestBitrates } from "../../utils/bitrates";

describe("DRM", function () {
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
let player;
const oldMediaSourceSupported = MediaSource.isTypeSupported;

beforeEach(() => {
MediaSource.isTypeSupported = () => true;
const dummy = new DummyMediaElement();
player = new RxPlayer({ videoElement: dummy });
});

afterEach(async () => {
MediaSource.isTypeSupported = oldMediaSourceSupported;
player.dispose();
});

const { url, transport } = manifestInfos;

it("should trigger error if no key system option is provided", async function () {
lockLowestBitrates(player);
player.setWantedBufferAhead(10);
player.loadVideo({
url,
transport,
autoPlay: false,
textTrackMode: "html",
textTrackElement: document.createElement("div"),
});
await waitForPlayerState(player, "STOPPED", ["LOADING"]);
const error = player.getError();
expect(error).not.toBeNull();
expect(error.code).to.equal("MEDIA_IS_ENCRYPTED_ERROR");
expect(error.name).to.equal("EncryptedMediaError");
expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR");
});

it("should load the content if licenses are returned", async function () {
lockLowestBitrates(player);
player.setWantedBufferAhead(10);
const expectedKeyIds = [
"80399bf58a2140148053e27e748e98c1",
"585f233f307246f19fa46dc22c66a014",
];
const keyIdsAsked = [];
player.loadVideo({
url,
transport,
autoPlay: false,
textTrackMode: "html",
textTrackElement: document.createElement("div"),
keySystems: [
{
type: "com.microsoft.playready",
getLicense(challenge) {
const challengeStr = textDecoder.decode(challenge);
const challengeObj = JSON.parse(challengeStr);
const keys = {};
challengeObj.keyIds.forEach((kid) => {
expect(expectedKeyIds).toContain(kid);
keyIdsAsked.push(kid);
keys[kid] = {
policyLevel: 0,
};
});
const license = {
type: "license",
persistent: false,
keys,
};
const licenseU8 = textEncoder.encode(JSON.stringify(license));
return Promise.resolve(licenseU8.buffer);
},
},
],
});
await waitForLoadedStateAfterLoadVideo(player);
expect(player.getVideoRepresentation().id).toEqual("8-80399bf5");
expect(player.getAudioRepresentation().id).toEqual("15-585f233f");
expect(player.getError()).toBeNull();
expect(keyIdsAsked.length).toEqual(expectedKeyIds.length);
});

it("should trigger specific error if the license request fails", async function () {
lockLowestBitrates(player);
player.setWantedBufferAhead(10);
player.loadVideo({
url,
transport,
autoPlay: false,
textTrackMode: "html",
textTrackElement: document.createElement("div"),
keySystems: [
{
type: "com.microsoft.playready",
getLicense() {
throw new Error("I do not work");
},
},
],
});
await waitForPlayerState(player, "STOPPED", ["LOADING"]);
const error = player.getError();
expect(error).not.toBeNull();
expect(error.code).to.equal("KEY_LOAD_ERROR");
expect(error.name).to.equal("EncryptedMediaError");
expect(error.type).to.equal("ENCRYPTED_MEDIA_ERROR");
});

it("should fallback from license request error with a `fallbackOnLastTry` toggle on", async function () {
player.setWantedBufferAhead(10);
const notReturnedKeyIds = ["90953e096cb249a3a2607a5fefead499"];
const expectedKeyIds = [
"90953e096cb249a3a2607a5fefead499",
"585f233f307246f19fa46dc22c66a014",
"80399bf58a2140148053e27e748e98c1",
];
const keyIdsAsked = [];
player.loadVideo({
url,
transport,
autoPlay: false,
textTrackMode: "html",
textTrackElement: document.createElement("div"),
keySystems: [
{
type: "com.microsoft.playready",
getLicense(challenge) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const challengeStr = textDecoder.decode(challenge);
const challengeObj = JSON.parse(challengeStr);
const keys = {};
challengeObj.keyIds.forEach((kid) => {
expect(expectedKeyIds).toContain(kid);
keyIdsAsked.push(kid);
if (notReturnedKeyIds.includes(kid)) {
const error = new Error("Should fallback!");
error.noRetry = true;
error.fallbackOnLastTry = true;
reject(error);
}
keys[kid] = {
policyLevel: 0,
};
});
const license = {
type: "license",
persistent: false,
keys,
};
const licenseU8 = textEncoder.encode(JSON.stringify(license));
resolve(licenseU8.buffer);
} catch (e) {
reject(e);
}
}, 100);
});
},
},
],
});
let brokenVideoLock = 0;
player.addEventListener("newAvailablePeriods", (p) => {
player.lockVideoRepresentations({
periodId: p[0].id,
representations: ["11-90953e09", "12-90953e09"],
});
});
player.addEventListener("brokenRepresentationsLock", (lock) => {
if (lock.trackType === "video") {
brokenVideoLock++;
}
});
await waitForLoadedStateAfterLoadVideo(player);
expect(brokenVideoLock).toEqual(1);
expect(["8-80399bf5", "9-80399bf5", "10-80399bf5"]).toContain(
player.getVideoRepresentation().id,
);
expect(player.getAudioRepresentation().id).toEqual("15-585f233f");
expect(player.getError()).toBeNull();
});
});

0 comments on commit 7743e95

Please sign in to comment.