Skip to content

Commit

Permalink
Call generateRequest BEFORE pushing any segment with PlayReady
Browse files Browse the repository at this point in the history
Another day, another PlayReady-specific issue :/

A partner signalled to us that they weren't able to play a mix of
unencrypted and encrypted content on any PlayReady devices.

After investigation, it seems that calling `generateRequest` for the
first time after clear segments are already present on a MSE
`SourceBuffer` associated to the MediaSource linked to the
corresponding media element immediately triggered an HTML5
`MEDIA_ERR_DECODE` error.

We tried A LOT of work-arounds:

  - patching clear segments with a `tenc` box with a `0x0` key id to
    incite the CDM to understand that encrypted contents may be pushed
    in the future

  - Rewriting the pssh sent through the EME `generateRequest` API so
    that it is barebone to limit weird PlayReady edge cases.

  - Replacing those stream clear segments' with those in our demo page,
    just to check that the clear segments were not at fault here

  - Waiting more time between the association of a MediaKeys to the
    media element and pushing the first segments.

None of those actions had an effect.

However, what had an effect, was to call the `generateRequest` API
BEFORE buffering any segment yet AFTER attaching the MediaKeys (and
perhaps MediaSource) to the media element.

So this commit does just that, communicating dummy initialization data
for a session that will be closed directly after.

Note that we already do a fake `generateRequest` on Edge Chromium with
Playready since #1434, yet this test was not sufficient, seemingly
because it is performed BEFORE MediaKeys attachment.

Note that this commit fixes the clear -> encrypted issues our partner
were having, but we're unsure yet of if it fixes the encrypted -> clear
issues (and I have good reasons to think it does not).

So, uh, yeah, PlayReady seems to keep being hard-at-work giving us
challenges and head-scratchers.
  • Loading branch information
peaBerberian committed Jul 23, 2024
1 parent a624fd7 commit 16cb4ae
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 5 deletions.
18 changes: 18 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,18 @@
/**
* (2024-07-23) We noticed issues with most devices relying on PlayReady when
* playing some contents with mix encrypted and clear contents (not with
* Canal+ own contents weirdly enough, yet with multiple other contents
* encoded/packaged differently).
*
* Due to this, we
* @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;
}
24 changes: 19 additions & 5 deletions src/main_thread/decrypt/content_decryptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/

import type { ICustomMediaKeys, ICustomMediaKeySystemAccess } from "../../compat/eme";
import eme, { getInitData } from "../../compat/eme";
import { DUMMY_PLAY_READY_HEADER, generatePlayReadyInitData } from "../../compat/generate_init_data";
import eme, { closeSession, getInitData } from "../../compat/eme";
import {
DUMMY_PLAY_READY_HEADER,
generatePlayReadyInitData,
} from "../../compat/generate_init_data";
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 @@ -292,9 +296,19 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
return;
}

const session = mediaKeys.createSession();
const initData = generatePlayReadyInitData(DUMMY_PLAY_READY_HEADER);
await session.generateRequest("cenc", initData);
if (
shouldCallGenerateRequestBeforeBufferingMedia(mediaKeySystemAccess.keySystem)
) {
try {
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 fully perform fake generateRequest call", error);
}
}

if (this._isStopped()) {
// We might be stopped since then
Expand Down

0 comments on commit 16cb4ae

Please sign in to comment.