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

fix: assumptions in multi-drm cases where only playready is available for playback. #6946

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

cjpillsbury
Copy link
Collaborator

@cjpillsbury cjpillsbury commented Jan 6, 2025

This PR will...

Clean up some assumptions on key request logic in multi-drm scenarios, particularly around PlayReady usage.

Why is this Pull Request needed?

There were

Are there any points in the code the reviewer needs to double check?

Everything was localized to EmeController::onMediaEncrypted() and much of the core logic stayed the same, but definitely want some attention while reviewing the implementation decisions, particularly the callout questions/comments (including in github comments) before 👍.

Smoke tested multi-drm case (on demand stream with FairPlay, Widevine, and PlayReady advertised via EXT-X-KEY tags and sinf/pssh mp4 boxes) on:

  1. macOS
    1. Safari (for FairPlay usage regression testing)
    2. Chrome (for Widevine usage regression testing)
  2. Windows
    1. Edge ("Edgium") with Widevine Enabled and Widevine Disabled (See: https://learn.microsoft.com/en-us/legal/microsoft-edge/privacy#digital-rights-management-and-media-licenses for disabling flag)

Also tested using both the repo-provided demo application* and local linking to Mux Elements ("Mux Player") using the repo-provided next.js application.

NOTE: For my testing, where my dev machine is macOS but I needed to test on a Windows machine on my LAN, this required updating the server script (http-server) to use https (based on security constraints). If needed for review, see https://www.npmjs.com/package/http-server#tlsssl or reach out to me directly.

NOTE: I do not have any publicly-shareable test assets + corresponding service requests, however, if needed for review, reach out to me directly and I can provide one out of band.

Resolves issues:

fixes #6947

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable) (N/A from what I could tell with existing unit tests)
  • API or design changes are documented in API.md (N/A)

const keySystem = pssh.systemId
? keySystemIdToKeySystemDomain(pssh.systemId)
: null;
return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
})[0];
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: Here's an example where we're presumptuously assuming that the 0th key system will work in a multi-drm scenario, which may or may not be true.

}
} else if (this.getLicenseServerUrl(KeySystems.WIDEVINE)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

here's an example where we're tying ourselves explicitly to the availability of a Widevine DRM server for PSSH cases, when it may be widevine, playready, or both.

// FairPlay and only FairPlay uses sinf boxes for EME signaling, so
// if we don't have a FairPlay license server URL available, we can't
// use the sinf for EME.
if (!fairplayLicenseServerUrl) return [];
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

since this code is now a function that yields 0+ key id + key system domain pairs/duals as candidates to use for playback, we return an empty array for any case where there are no matches based on init data + config

break;
}
}
/** @TODO errors? */
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: If we early bail here, this means we didn't find any matches for playback between the intersection of our config + what was signaled from EME in the init data. We plausibly should construct an error here, and potentially earlier in the newly created getKeyIdInfos() function (if we want more context clues in our error as to what specifically failed)

);

try {
// Use the first successful key session context promise.
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: In order to strike a balance between non-blocking async behavior and not requesting keys for multiple key systems in parallel, the balance I struck in this implementation is to keep everything for a single key system roughly as it was in the old implementation, but then block on a 👍 / 👎 for that key system. If that key system fails to be usable, we proceed to the next (in cases where that applies).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Would you mind using then/catch (as we do in other parts of the code) rather than await keySessionContextPromise?

// to get any key for playback. In this case, handle these errors
// NOTE: Currently, we will not signal failed key requests if at least
// one succeeds, since this *can* happen and be valid in multi-drm scenarios.
if (errors.length >= keyIdInfos.length) {
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: See note. (tl;dr - I didn't see a clean way to signal errors that are actual errors when one but not all key systems fail without a much larger refactor)

@cjpillsbury cjpillsbury changed the title fix: assumptions in multi-drm cases where only playready is available… fix: assumptions in multi-drm cases where only playready is available for playback. Jan 6, 2025
@robwalch robwalch added this to the 1.6.0 milestone Jan 6, 2025
@robwalch
Copy link
Collaborator

robwalch commented Jan 6, 2025

After confirming the bug and verifying the fix these changes will go into v1.6.0 (beta.3). If you'd like a patch for v1.5 we can follow up after that by cherry-picking the changes in the patch/v1.5.x branch. Thanks for the detailed report and fix!

@@ -525,7 +525,7 @@ class EMEController extends Logger implements ComponentAPI {
return this.attemptKeySystemAccess(keySystemsToAttempt);
}

private onMediaEncrypted = (event: MediaEncryptedEvent) => {
private onMediaEncrypted = async (event: MediaEncryptedEvent) => {
Copy link
Collaborator

@robwalch robwalch Jan 7, 2025

Choose a reason for hiding this comment

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

Making this method async for the await at the bottom of the method adds a lot of boiler plate (regenerator runtime) to the UMD build. Would you mind using then/catch (as we do in other parts of the code) rather than await keySessionContextPromise?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than suggest changes without verifying that they work, I put together this draft:

Besides avoiding the use of async/await which adds regenerator-runtime boilerplate to our es5 output, I wanted to avoid the additional looping through found pssh and config, by resolving the selected key-system first. This is closer to how playlist keys are selected. onMediaEncrypted will always run after a playlist key has been selected unless the encrypted event is for a clear segment that signals upcoming encrypted keys ("clear-lead").

);

try {
// Use the first successful key session context promise.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would you mind using then/catch (as we do in other parts of the code) rather than await keySessionContextPromise?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PlayReady+Widevine MultiDRM error cases
2 participants