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

Only use selected key-system in EME "mediaencrypted" event handler #6948

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

Conversation

robwalch
Copy link
Collaborator

@robwalch robwalch commented Jan 8, 2025

Resolves #6947 / Suggested changes for #6946

CC @cjpillsbury. Thanks for reporting #6947 and submitting #6946. I was able to reproduce the issue. The only issue with the PR was that adding async/await functions introduces regenerator-runtime boilerplate (about 300 lines) to hls.js. To avoid this I wanted to see if we could simplify the fix. Resolving the selected key-system up front (as it always should be based on playlist keys/config) simplifies the rest.

Comment on lines +578 to +582
const psshInfo = psshResults.filter(
(pssh): pssh is PsshData =>
!!pssh.systemId &&
keySystemIdToKeySystemDomain(pssh.systemId) === keySystem,
)[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.

If you wanted to iterate through these to find the first key that matched any in mediaKeySessions that would work too. I don't expect we would have multiple pssh with the same key system.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is probably fine. Small improvement might be to save off the filter, always use the 0th psshInfo, but log/warn if psshInfos.length > 1

Comment on lines +538 to +551
if (!this.keyFormatPromise) {
let keySystems = Object.keys(
this.keySystemAccessPromises,
) as KeySystems[];
if (!keySystems.length) {
keySystems = getKeySystemsForConfig(this.config);
}
const keyFormats = keySystems
.map(keySystemToKeySystemFormat)
.filter((k) => !!k) as KeySystemFormats[];
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
}

const psshInfo = psshResults.filter((pssh): pssh is PsshData => {
const keySystem = pssh.systemId
? keySystemIdToKeySystemDomain(pssh.systemId)
: null;
return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
})[0];
this.keyFormatPromise.then((keySystemFormat) => {
Copy link
Collaborator Author

@robwalch robwalch Jan 8, 2025

Choose a reason for hiding this comment

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

keyFormatPromise = this.getKeyFormatPromise now could happen in three places:

  1. onManifestLoaded for EXT-X-SESSION-KEY session keys
  2. selectKeySystemFormat(frag: Fragment) for HLS media playlist keys (assigned to a fragment being loaded)
  3. (new with this change) onMediaEncrypted at this point we would expect an encrypted fragment to signal its key format(s), but if it hasn't we can check the configured key-systems. It may be good to limit this to the intersection of configured key-systems and init data type and formats. IMO it would be incorrect to configure for a key system supported the device but not present in the media so I don't think that is important to cover.

Copy link
Collaborator Author

@robwalch robwalch Jan 8, 2025

Choose a reason for hiding this comment

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

If the key format promise returned all the results of it's subroutine getKeySystemSelectionPromise (keySystem (format) and mediaKey), the last part pf this method (clear-lead resolution) could be simplified by just returning the results of attemptSetMediaKeys and generateRequestWithPreferredKeySession.

let keyId: Uint8Array | null | undefined;
let keySystemDomain: KeySystems | undefined;

if (initDataType === 'sinf' && keySystem === KeySystems.FAIRPLAY) {
Copy link
Collaborator Author

@robwalch robwalch Jan 8, 2025

Choose a reason for hiding this comment

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

It might be better to log when the "encrypted" event should be ignored:

Suggested change
if (initDataType === 'sinf' && keySystem === KeySystems.FAIRPLAY) {
if (initDataType === 'sinf') {
if (keySystem !== KeySystems.FAIRPLAY) {
this.log(`Ignoring "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
return;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

If this occurs (aka a mismatch between getting a sinf type and not having a media session that is fairplay), this (and the related one), it sound like warnings to me - aka we only expect to see sinf if/when we're using fairplay. Maybe switch to a warn and add the word "unexpected" ("Ignoring unexpected...")

}
}

if (!keySessionContextPromise && keySystemDomain === keySystem) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
if (!keySessionContextPromise && keySystemDomain === keySystem) {
if (!keySessionContextPromise) {
if (keySystemDomain !== keySystem) {
this.log(`Ignoring "${event.type}" event with ${keySystemDomain} init data for selected key-system ${keySystem}`);
return;
}

this.warn(`${logMessage} Failed to parse sinf: ${error}`);
return;
}
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should probably still check if the key system is either widevine or playready here and early bail + log/warn like we do for the fairplay + sinf case, above.

!frag.decryptdata &&
frag.encrypted &&
this.emeController &&
this.config.emeEnabled
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@cjpillsbury I remember you mentioning that you had issues with the behavior when loading encrypted content that requires EME with emeEnabled: false. This would be the ideal place to log an error since playlists must include keys for encrypted content. !frag.decryptdata && frag.encrypted means we encountered DRM protected content. Keep in mind, there is no error if the key isn't handled because the applications can implement EME on its own. Either way we shouldn't call out to the emeController here when emeEnabled: false which is the reason for the addition of && this.config.emeEnabled.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should treat that as a separate PR/effort, but this looks like a good candidate. There were a few hard to identify cases of errors and this is definitely one. Ideally we'd surface the error as early as possible for root cause analysis reasons. E.g. if we encounter a multi-variant playlist or media playlist with (session) keys but don't have sufficient config for said keys, dispatch an error then (near the playlist parsing stage).

The hardest one that I'm not sure belongs in hls.js at all is when folks think some media is DRM protected but actually isn't. We could make assumptions that if we encounter drm config that means folks think the content is DRM protected, but that's a bad assumption for folks who have mixed content with a shared config. Given the cases of clear lead or valid cases where lower quality renditions may be unencrypted, there may be too many permutations to safely assume this in an overly generic way. Food for thought, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree this is for another PR.

if we encounter a multi-variant playlist or media playlist with (session) keys

Session keys don't point to variants and may or may not be used. Encrypted fragments (media playlist keys) is the earliest place we can error with certainty.

when folks think some media is DRM protected but actually isn't

Is there an example where that errors? The eme-controller with emeEnabled and drmSystem configs have no effect outside of adding "encrypted" and "waitingforkey" event listeners to the media element (for unprotected content). Key-system access is only attempted when HLS KEY or SESSION-KEY tags are encountered or "encrypted" is emitted.

@robwalch robwalch added this to the 1.6.0 milestone Jan 8, 2025
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