Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Call generateRequest BEFORE pushing any segment with PlayReady #1486

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/compat/should_call_generate_request_before_buffering_media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* (2024-07-23) We noticed issues with most devices relying on PlayReady when
* playing some contents with a mix of encrypted and clear segments (not with
* Canal+ own contents weirdly enough, yet with multiple others both encoded
* and packaged differently).
* The issue fixed itself when we called the
* `MediaKeySession.prototype.generateRequest` EME API **BEFORE** any segment
* was buffered.
*
* So this function returns `true` when calling `generateRequest` should
* probably be performed before buffering any segment.
*
* @param {string} keySystem - The key system in use.
* @returns {boolean}
*/
export default function shouldCallGenerateRequestBeforeBufferingMedia(
keySystem: string,
): boolean {
if (keySystem.indexOf("playready") !== -1) {
return true;
}
return false;
}
18 changes: 18 additions & 0 deletions src/main_thread/decrypt/content_decryptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import type { ICustomMediaKeys, ICustomMediaKeySystemAccess } from "../../compat/eme";
import eme, { getInitData } from "../../compat/eme";
import shouldCallGenerateRequestBeforeBufferingMedia from "../../compat/should_call_generate_request_before_buffering_media";
import config from "../../config";
import { EncryptedMediaError, OtherError } from "../../errors";
import log from "../../log";
Expand Down Expand Up @@ -54,6 +55,7 @@ import {
areSomeKeyIdsContainedIn,
} from "./utils/key_id_comparison";
import type KeySessionRecord from "./utils/key_session_record";
import performFakeGenerateRequest from "./utils/perform_fake_generate_request";

/**
* Module communicating with the Content Decryption Module (or CDM) to be able
Expand Down Expand Up @@ -291,6 +293,22 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
return;
}

if (
shouldCallGenerateRequestBeforeBufferingMedia(mediaKeySystemAccess.keySystem)
) {
try {
await performFakeGenerateRequest(mediaKeys);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: maybe this does not need to be done if MediaKeySessions have already been created (and their generateRequest method called)?

To check, it could save us some time when playing multiple contents

} catch (err) {
const error = err instanceof Error ? err : new Error("Unknown Error");
log.warn("DRM: unable to fully perform fake generateRequest call", error);
}
}

if (this._isStopped()) {
// We might be stopped since then
return;
}

const prevState = this._stateData.state;
this._stateData = {
state: ContentDecryptorState.ReadyForContent,
Expand Down
9 changes: 2 additions & 7 deletions src/main_thread/decrypt/find_key_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
import { canRelyOnRequestMediaKeySystemAccess } from "../../compat/can_rely_on_request_media_key_system_access";
import type { ICustomMediaKeySystemAccess } from "../../compat/eme";
import eme from "../../compat/eme";
import {
generatePlayReadyInitData,
DUMMY_PLAY_READY_HEADER,
} from "../../compat/generate_init_data";
import shouldRenewMediaKeySystemAccess from "../../compat/should_renew_media_key_system_access";
import config from "../../config";
import { EncryptedMediaError } from "../../errors";
Expand All @@ -31,6 +27,7 @@ import flatMap from "../../utils/flat_map";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
import type { CancellationSignal } from "../../utils/task_canceller";
import MediaKeysInfosStore from "./utils/media_keys_infos_store";
import performFakeGenerateRequest from "./utils/perform_fake_generate_request";

type MediaKeysRequirement = "optional" | "required" | "not-allowed";

Expand Down Expand Up @@ -418,9 +415,7 @@ export async function testKeySystem(
if (!canRelyOnRequestMediaKeySystemAccess(keyType)) {
try {
const mediaKeys = await keySystemAccess.createMediaKeys();
const session = mediaKeys.createSession();
const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
await session.generateRequest("cenc", initData);
await performFakeGenerateRequest(mediaKeys);
} catch (err) {
log.debug("DRM: KeySystemAccess was granted but it is not usable");
throw err;
Expand Down
33 changes: 33 additions & 0 deletions src/main_thread/decrypt/utils/perform_fake_generate_request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { closeSession } from "../../../compat/eme";
import type { ICustomMediaKeys } from "../../../compat/eme";
import {
DUMMY_PLAY_READY_HEADER,
generatePlayReadyInitData,
} from "../../../compat/generate_init_data";
import log from "../../../log";

/**
* The EME API is badly implemented on many devices, leading us toward the need
* to perform some heavy work-arounds.
*
* A frequent one is to call the `MediaKeySession.prototype.generateRequest` API
* at some point for dummy data an see if it fails (or not, sometimes just
* calling it is important).
*
* This method does just that, resolving the returned Promise if the
* `generateRequest` call could be performed and succeeded or rejecting in other
* cases.
* @param {MediaKeys} mediaKeys
* @returns {Promise}
*/
export default async function performFakeGenerateRequest(
mediaKeys: MediaKeys | ICustomMediaKeys,
): Promise<void> {
const session = mediaKeys.createSession();
const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
await session.generateRequest("cenc", initData);
closeSession(session).catch((err) => {
const error = err instanceof Error ? err : new Error("Unknown Error");
log.warn("DRM: unable to close fake MediaKeySession", error);
});
}
Loading