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

Multi-DRM Support #4930

Merged
merged 19 commits into from Dec 15, 2022
Merged

Multi-DRM Support #4930

merged 19 commits into from Dec 15, 2022

Conversation

robwalch
Copy link
Collaborator

@robwalch robwalch commented Sep 28, 2022

This PR will...

Add Multi-DRM support by handling FairPlay (v3), PlayReady and Widevine playlist keys.

  • Keys can be handled (loaded or interacting with EME) while fragments are loaded (Parallelize key and segment requests #4861)
  • Key-system access is requested based on whichever comes first: multi-variant playlist session-key formats, media playlist key formats, or "encrypted" event system-id
    • Access is only requested for key-systems for options are defined in the player config (drmSystems[{key-system}]
    • Multiple requestMediaKeySystemAccess requests are made sequentially until one succeeds (to avoid obtaining access to more than one system on platforms that support more than one (ex: Widevine and PlayReady on Windows)
  • Key-sessions are created based on playlist keys or "encrypted" events for clear-lead data
  • DRM keys are not considered "loaded" until key session key state is usable after license exchange (allows fragment/level error handling with HDCP restrictions or any other license or key issues).
  • License is renewed on key session "license-renewal" message or key status change "expired"

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

  • Please comment on API enhancements in Files changed under API.md after reading updates and new examples.

Resolves issues:

Not Addressed in this PR:

Follow Up:

Either fixed or depends on content, need confirmation from contributors:

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable)
  • API or design changes are documented in API.md

@robwalch robwalch added this to Top priorities in Release Planning and Backlog via automation Sep 28, 2022
@robwalch robwalch added the DRM label Sep 28, 2022
@robwalch robwalch added this to the 1.3.0 milestone Sep 28, 2022
@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from 025ef18 to dd934b7 Compare September 28, 2022 19:26
@robwalch robwalch moved this from Top priorities to DRM in Release Planning and Backlog Sep 28, 2022
@itsjamie
Copy link
Collaborator

itsjamie commented Oct 4, 2022

The manifest delivered keys are working awesome! Tried it out with a widevine delivered key 🎉

The only thing I was wondering about was sequencing of requests, it looks like it delays loading the manifest key until after the init segment download. It would be cool to parallelize a manifest key load with the initial init segments. Right now, it delays playback for the time it takes for the licenseXhrSetup call and response dance to take, and if that flow requires additional network requests (to get authorization data, etc) then it would be nice to be able to do that alongside.

In our case it goes;

  • main manifest loads
  • rendition manifests load (audio, video)
  • init segments load (audio, video)
  • blocked waiting for init segments
  • license key is fetched (takes two requests)
  • not sure if blocking here, or just delayed
  • first media requests occur

Including in the sample around licenseXhrSetup might be good to include a note that undefined return from either a Promise or directly will make it directly use the result from the XHR request.

This was important because we could get the authorization header for the license request in a promise we returned.

this.hls.config.licenseXhrSetup = (xhr, url, keyContext, licenseChallenge) => {
      xhr.open('POST', url, true);
      return fetchDRMToken(this.authData).then((token) => {
        xhr.setRequestHeader('token', token);
      });
    }

@robwalch robwalch mentioned this pull request Oct 5, 2022
@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from dd934b7 to 7379d7e Compare October 5, 2022 21:48
@robwalch
Copy link
Collaborator Author

robwalch commented Oct 6, 2022

Hi @itsjamie,

The only thing I was wondering about was sequencing of requests, it looks like it delays loading the manifest key until after the init segment download. It would be cool to parallelize a manifest key load with the initial init segments. Right now, it delays playback for the time it takes for the licenseXhrSetup call and response dance to take, and if that flow requires additional network requests (to get authorization data, etc) then it would be nice to be able to do that alongside.

I'm thinking that at least the first key within the same discontinuity should be prepared when first selecting a fragment for loading. This would be the init segment in your case even if it is not encrypted. This would kick off requesting key-system access instantly once a playlist is loaded and the player has decided where to start. This could be a little expensive to scan an entire playlist or list of fragments within a disco looking for a level key if the content is not encrypted. I'm considering whether to hold off until I cover session-keys. Another option is to allow you to specify that you want to warm up CDMs on attach (similar to how emeEnabled worked prior to these changes: on attached request access).

Including in the sample around licenseXhrSetup might be good to include a note that undefined return from either a Promise or directly will make it directly use the result from the XHR request.

Added a comment.

This was important because we could get the authorization header for the license request in a promise we returned.

I see. That makes sense. You still need to call setup, but you are not modifying the license challenge in any way.

@robwalch
Copy link
Collaborator Author

robwalch commented Oct 6, 2022

In our case it goes;

main manifest loads
rendition manifests load (audio, video)
init segments load (audio, video)
blocked waiting for init segments
license key is fetched (takes two requests)
not sure if blocking here, or just delayed
first media requests occur

The shaka-packager test stream has pssh in all segments even though the first two segments and the init segment are not encrypted (the key appears after the second segment in the playlist). This is known as "clear-lead".

Since the eme-controller now ignores the "encrypted" event by default, we do not request key-access and the license until the stream-controller reaches the point where it will load the segment with the playlist key preceding it. You can change this behavior to act on "encrypted" by setting useEmeEncryptedEvent to true. Key-system access is still delayed until that point rather than on attached.

While this allows us to avoid attaching to CDMs on streams without keys, the setup and sequence is too lazy to minimize impact of delays caused by key system access and license retrieval. I'll add a todo item in the description for handing keys earlier.

@robwalch
Copy link
Collaborator Author

robwalch commented Oct 20, 2022

Hi @itsjamie,

it looks like it delays loading the manifest key until after the init segment download

I pushed 666ad22 yesterday which will initiate key-system access with the initial fragment request even when the first key comes after the first/init segment. This is not as early as "attach" where access to widevine was always requested with emeEnabled previously, but it is earlier than it would be if we waited until the first segment with a key in the playlist (like in the case of the shaka-packager test stream).

The key-session and license request is still not initiated until a fragment with a key is requested which may not be before the "encrypted" event (depending on playback vs network speed). Setting useEmeEncryptedEvent to true will start the session when the inband pssh is encountered (after the initial segment is appended). I don't know if this kind of setup is unique to shaka-packager with HLS or if other DRM providers do the same thing.

I have given some thought to making the eme-controller choose without any configuration, but that would likely require parsing the pssh box ("encrypted" initData) and making some informed decision based on its content. I'm unsure exactly what is present (or could be absent) in the pssh for us to determine that we should start a session and thus request a license using that as the license challenge, or wait for a playlist key (so that all key sessions are tied to the playlist). With this new change, we now know about the future playlist key in eme-controller before the "encypted" event. So this seems doable, but I haven't figured out the specific requirements.

@robwalch robwalch marked this pull request as ready for review October 21, 2022 01:17
@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from bae6f65 to 502c0fa Compare October 25, 2022 20:12
@robwalch
Copy link
Collaborator Author

robwalch commented Oct 27, 2022

Update: 3a6ceb8 Removed useEmeEncryptedEvent and added support for clear-lead Widevine "encrypted" events. Contributions for ClearKey and PlayReady are welcome in this area. "encrypted" events where we do not yet extract keyId(s) are ignored - playlist keys will be used instead.

Thinking about adding additional key id validation. Instead of trying to start a session that will throw "bad data" on generateRequest, we ignore the event in that case too, but I let truncated key data pass through to satisfy existing tests.

@itsjamie this should get order of events closer to the release version, with the exception of key-system request starting on first fragment pick rather than attach.

@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from d145503 to 3a6ceb8 Compare October 27, 2022 22:24
@itsjamie
Copy link
Collaborator

Awesome.

@Devjeel
Copy link

Devjeel commented Nov 16, 2022

Hey @robwalch. I am testing PlayReady with @itsjamie on this branch. We are having issue where it keeps requesting init segment of the video continuously.

From Media manifest, it knows about the PlayReady:

#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;charset=UTF-16;base64, 9...",KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1"

And this is what hls.js config looks like:

emeEnabled: true,
useEmeEncryptedEvent: false,
requestMediaKeySystemAccessFunc: (keySystem, supportedConfigurations) => {
  if (keySystem === 'com.microsoft.playready') {
    keySystem = 'com.microsoft.playready.recommendation';
  }
  return navigator.requestMediaKeySystemAccess(keySystem, supportedConfigurations);
}

Any thoughts?

@robwalch
Copy link
Collaborator Author

robwalch commented Nov 16, 2022

Hi @Devjeel,

I rebased this branch and merged #5018 into this PR which improves error handling. The loop loading seems like an issue handling an error at start that should have been fatal. Let me know if this update handles the error correctly, and to followup we can look into why you were getting the error.

itsjamie
itsjamie previously approved these changes Nov 28, 2022
@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from 58693d9 to 497f17d Compare December 10, 2022 00:03
@robwalch robwalch force-pushed the feature/drm-fairplay-key-system branch from f5a33d4 to d3eefb7 Compare December 15, 2022 00:45
@robwalch robwalch merged commit 800c35f into master Dec 15, 2022
@robwalch robwalch deleted the feature/drm-fairplay-key-system branch December 15, 2022 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

4 participants