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

Proposal: DRM API for V1 #2700

Closed
itsjamie opened this issue May 5, 2020 · 14 comments
Closed

Proposal: DRM API for V1 #2700

itsjamie opened this issue May 5, 2020 · 14 comments

Comments

@itsjamie
Copy link
Collaborator

itsjamie commented May 5, 2020

Is your feature request related to a problem? Please describe.
As I've been going through the various DRM CDM's and their respective implementations with different vendors, and then the supporting mechanisms for EME, it's become increasingly clear to me that each vendor / organizational unit can utilize different mechanisms provided by these specifications for storing information in the media, or in the HLS manifest, and it would be an increasingly difficult support task for hls.js to accommodate these one-off scenarios in a generic way. Leading me to wonder if the current API in hls.js will be something that we want to freeze with V1.

Describe the solution you'd like

Old proposal for pushing EME outside of the core hls.js controllers

What I'm thinking about would be a more generic and extensible API in V1.

We commit to offering a read-only API that exposes manifest level key information that we parse out of the manifest.

The intention here is to surface:

  • EXT-X-KEY skd:// data for Fairplay.
  • EXT-X-KEY init data for Widevine / Playready CDM data in manifest.

This information would be stored when manifest was parsed, and available from an Hls instance. Bikeshed on naming, but I would think of offering two specific property-based getter APIs.

  • sessionKeys - #EXT-X-SESSION-KEY
  • levelKeys (perhaps a keys attribute on exposed level) - #EXT-X-KEY in a level

These properties would return the parsed structure for their respective tags.
I prefer the specific API over a generic parsed manifest structure being exposed because we can offer stronger guarantees on the API.

Here's the kicker I want to propose. We rollback the current public API support beyond what I've outlined above for DRM. This would mean deprecating the widevine config, license URL.

Hls.js at least as how I perceive it is intended to extend and offer support to the MSE API to parse and playback HLS content. Beyond offering metadata from the HLS manifest I start to think of the EME integration as slightly out of scope, given what I've laid out above, we have given the user all the information they need to be able to create an implementation for their CDM, without locking an API design in, or how to parse and set up MediaKeys for the media element.

Describe alternatives you've considered

I hesitate to offer any implementations that are not easily removed if building from source, this is because of the earlier described vendor specifics I've seen, requiring rather extensive hooks into the flow if you want to support all variations.

An extension on the above idea is offering a much simpler extension API to the "encrypted" media event listener, that is fired when sessionKey / levelKeys is populated, or the HTML5 encrypted event is fired. This would be fired for both cases. If from an encrypted event, it would include the source event, otherwise it would be undefined, and the user would be able to hook their code to this.

This is an alternative because it brings us closer to bringing the DRM handling into the library, which I'm not sure is something to freeze into the V1 API since it will mean a lot of code written and part of the bundle, that will only be useful for a minority set of users.


Perhaps if we offered CDM implementations as pure functions outside of the core class Hls.js and iterated there it would lend itself to having smaller bundle sizes automatically for folks who built from source, and we could include them as static methods in the "full" distribution. Something to discuss there.

Context
The implementations we've seen over the last year for how to accomplish DRM have taken wildly different approaches, manifest-driven, encrypted event driven, including init data parsing, passthrough handling of init data, what configuration do you use for codec information when requesting CDM support? This is the variation I'm talking about in business rules and the deployment to handling specific CDM implementations. Depending on who they talk with, and who their vendor is, they are given different recommendations for the best way to work.

I don't know if we would ever get to a point with an API that pleased everyone, and the other side of users would end up with a larger library they didn't need, or much more complicated build-processes.

#2665 - navigator API, needing more control
#2664 - cleanup of media key sessions
#2630 - adding a new CDM (fairplay)
#1918 - adding a new CDM (playready)
#2194 - switching to manifest-driven encrypted events
#1915 - parsing initdata from manifest
#1442 - initial implementation

From #1442...

Looking at the 1500lines of "DRM-engine" that Shaka has, this looks "too easy". There are probably many many things that we are not aware of yet, or that we do not support (like renewing the license and other handling other CDM state things, various restriction modes, and more).

Given the input below, I think this is the API we want to freeze with for V1. I'm retracting this proposal, and will open a new issue to get manifest-delivered DRM supported for V1 API.

@itsjamie
Copy link
Collaborator Author

itsjamie commented May 5, 2020

Perhaps if we offered CDM implementations as pure functions outside of the core class Hls.js and iterated there it would lend itself to having smaller bundle sizes automatically for folks who built from source, and we could include them as static methods in the "full" distribution. Something to discuss there.

Ideally, I would rather we collaborate on a hypothetical DRM.js that isn't HLS specific and does the EME handshake.

@jgainfort
Copy link
Contributor

Hls.js at least as how I perceive it is intended to extend and offer support to the MSE API to parse and playback HLS content. Beyond offering metadata from the HLS manifest I start to think of the EME integration as slightly out of scope, given what I've laid out above, we have given the user all the information they need to be able to create an implementation for their CDM, without locking an API design in, or how to parse and set up MediaKeys for the media element.

I personally agree.

The intention here is to surface:

EXT-X-KEY skd:// data for Fairplay.
EXT-X-KEY init data for Widevine / Playready CDM data in manifest.

This information would be stored when manifest was parsed, and available from an Hls instance. Bikeshed on naming, but I would think of offering two specific property-based getter APIs.

sessionKeys - #EXT-X-SESSION-KEY
levelKeys (perhaps a keys attribute on exposed level) - #EXT-X-KEY in a level

I would also request if it's not already available, is the EXT-X-MAP tag and/or any PSSH info associated with the KEY. Specifically, as you mentioned this is required for

including init data parsing

@robwalch
Copy link
Collaborator

robwalch commented May 5, 2020

Providing access to events and data required by a DRM implementation makes sense. These are great suggestions.

Ideally, I would rather we collaborate on a hypothetical DRM.js that isn't HLS specific and does the EME handshake.

Here's where I differ, because think we could still offer solutions for different systems. We don't need to include everything in the main bundle. We could output a different target or perform code splitting with webpack if size is really a concern. I know it's more about maintainability. I would love to know how many users are taking advantage of the Widevine features built in - outside of contributors.

If we were to remove the widevine eme-controller from v1, it would be great to move the implementation rather than remove it, as a sample implementation of the methods and events discussed here. I would miss watching the clip of Angel One in the demo page without it! 😉

@itsjamie
Copy link
Collaborator Author

itsjamie commented May 5, 2020

I know it's more about maintainability.

Absolutely.

I would love to know how many users are taking advantage of the Widevine features built in - outside of contributors.

Yes!

If we were to remove the widevine eme-controller from v1, it would be great to move the implementation rather than remove it, as a sample implementation of the methods and events discussed here.

100%, not planning on throwing away the working code that users / contributors have wanted and written, but changing how a user interacts with it, and therefore our ability to change it.


My main goal is to stop the spread of configuration growth in relation to DRM for the core hls.js API. I'm not against offering a code-split module (that is even embedded in the full build) to accomplish DRM playback built-atop the proposed "I've encountered DRM content" hook, I just don't believe we need to ship with the EME handshake in a core controller.

Here's the future bare-minimum configuration options I'm aware of if we continue down the current path.

  • keySystemPreference (if both widevine and playready are supported)
  • preferredPsshSource: 'eme' | 'manifest'
  • widevineLicenseUrl
  • playreadyLicenseUrl
  • fairplayLicenseUrl
  • fairplayCertificationUrl
  • forceUint8CertParsing
  • HTTP header support for license calls
  • hooks into the process
    • navigator hooks (manipulating codecs for specific key systems)
    • license request
    • cert request
    • license response
    • token refreshment

@itsjamie
Copy link
Collaborator Author

itsjamie commented May 5, 2020

@jgainfort

I would also request if it's not already available, is the EXT-X-MAP tag and/or any PSSH info associated with the KEY. Specifically, as you mentioned this is required for init data parsing.

To be clearer, I wasn't planning on parsing the init segment itself looking for PSSH boxes, as the EME implementation on the browser should surface the PSSH box out of the init segment itself. But instead, make the PSSH information in EXT-X-KEY tags available. This way manifest based PSSH information would be available to a user in the exposed read-only APIs, and the segment based PSSHs would be exposed via the EME flow.

These two mechanisms would be combined into a single notification that the content you have requested for playback is encrypted, and requires media keys.

Am I missing something else with regards to EXT-X-MAP?


These two mechanisms would be combined into a single notification that the content you have requested for playback is encrypted, and requires media keys.

To make sure it's very clear: this is where I see a user being able to slot the implementation of our current EME code, but it would be moved outside of the core EMEController, and become its own standalone pure API.

@jgainfort
Copy link
Contributor

@itsjamie

To be clearer, I wasn't planning on parsing the init segment itself looking for PSSH boxes, as the EME implementation on the browser should surface the PSSH box out of the init segment itself.

I would expect the PSSH from the EME as well but I believe we experienced a case where this did not occur and we needed to do some parsing ourselves.

Matthew, can you confirm or does this look good?

@robwalch robwalch pinned this issue May 6, 2020
@robwalch
Copy link
Collaborator

robwalch commented May 6, 2020

@valotvince
Copy link
Contributor

100% for the code-splitting alternative embedded inside the core module that would load if EME is enabled per configuration and DRM is detected inside the manifest / stream.

If we were to build a DRM.js lib (offered alongside hls.js core module), we would still need the x configuration and hooks to http requests / initData transformation, ... etc (shaka is great on that point).
As a hls.js integrator, like with Shaka or dash.js, I like that the dirty drm work is made by the lib and that I only have to worry about the specifics of my DRM provider. If not, I would build my own MSE player.

@realeyes-matthew
Copy link

Ideally, I would rather we collaborate on a hypothetical DRM.js that isn't HLS specific and does the EME handshake.

One problem I see with splitting EME management out of hls.js is that, as has been noted, certain vendors' DRM configurations are reliant on HLS playlists. So, just in the way HLS playlists govern media playback via MSE, they could also govern DRM media playback via EME. To me, this would make it seem like configuration of EME would be within the scope of hls.js.

We commit to offering a read-only API that exposes manifest level key information that we parse out of the manifest.

I agree that hls.js should at least agree to provide any data from an EXT-X-KEY tag via a readonly getter API. However, I also think providing an hls.js API to configure EME would be a powerful feature to include in the 1.0 release.

...each vendor / organizational unit can utilize different mechanisms provided by these specifications for storing information in the media, or in the HLS manifest, and it would be an increasingly difficult support task for hls.js to accommodate these one-off scenarios in a generic way

I understand the concern that supporting different vendors' various DRM configurations could present a challenge as they can be so varied. However, conforming tightly with the EME API would allow us to provide built-in support for DRM while creating an extensible system for different vendors' configurations that would not block a user from using a custom DRM config, but also allow a user to leverage hls.js to reduce boilerplate overhead for supported DRM configs.


In our fork of hls.js (PR to feature/v1.0.0 pending), we modified eme-controller to implement an API that uses user-implemented hooks to configure EME.

eme-controller adds an event listener to the media for the encrypted event, and then uses the user-provided hooks to fulfill the EME configuration Promise chain. By configuration, the user can choose to trigger the Promise chain on initialization without the encrypted event being fired. This allowed us to support cases where the PSSH box had been stripped from the init segment and provided (encoded) in the manifest - yet which caused the encrypted event to not be fired.

The hooks pass the user any available information for each step in the Promise chain, but also allow for custom configuration if desired.

The hooks we provided are:

  • requestMediaKeySystemAccessFunc: Allows the user to specify the key system to be used
  • getEMEInitializationDataFunc: Allows the user to provide the initData needed to generate a license request from the Key Session (which would normally be provided on the encrypted event, but in our case, was provided in the manifest)
  • getEMELicenseFunc: Allows the user to obtain and provide the license that will be used to update the Key Session themselves

The benefit of the hook-based DRM configuration system is that it allows the user to adapt to vendor-specific DRM configurations for each step of the EME configuration Promise chain, especially providing initData that was surfaced outside of an (unfired) encrypted event, and providing a license obtained independently of an hls.js XHR request.


Overall, I believe that managing EME is within the scope of hls.js and would be a powerful feature to provide in the v1.0.0 release. While there are many different vendors' DRM configurations, if we conform tightly to the EME API, handle the boilerplate configuration internally, and allow the user to provide hooks to handle vendor-specific DRM implementations, we could include DRM-protected content playback as a feature of hls.js.

@realeyes-matthew
Copy link

realeyes-matthew commented May 12, 2020

^ in lieu of a premature PR, here is the EME controller adaptation that we wrote in our fork that implements the described hooks
https://github.com/realeyes-media/hls.js/blob/master/src/controller/eme-controller.ts

@itsjamie
Copy link
Collaborator Author

itsjamie commented May 12, 2020

@realeyes-matthew thank you for the consideration!

I think it's clear that I'm in the minority for wanting to strip the EME handshake out of hls.js. So, I'll put together a branch to demonstrate the alternative I have in mind where hls.js will notify you when it encounters encrypted content so that folks can see now that impacts the current structure.

Thank you everyone for the input!

I think it's better that we move forward together, and I can understand the benefit of the simplicity of including the EME handling inside hls.js, so let's go towards that single goal.

I've left a new comment below with the next step towards adding support for manifest-based DRM keys. If anyone has preferences on how we handle the priority between EME signalled and Manifest signalled DRM, more input would be appreciated there!

@itsjamie
Copy link
Collaborator Author

@realeyes-matthew @springuper

First step: #2735
If you have a chance, would appreciate a review.

Intention here is to start to populate the internal data model with the information we need to populate the public levelKeys API.

So, adding parsing for the manifest signalled DRM keys. Currently, since we don't have code that supports them their skipped.

@itsjamie
Copy link
Collaborator Author

itsjamie commented Jun 24, 2020

Now that #2735 is merged, the Fragment class encrypted property now reports true for DRM signalled keys.

During the implementation of this, something I didn't address was multi-drm streams. Currently the data model assumes one levelkey per fragment. However, it's possible for there to be multiple levelkey(s) per fragment.

MultiDRM manifests I have found are relatively common and currently are unsupported due to the fact that we only store a single level key on a fragment. This will need to change if we want to support manifest delivered keys in a multi-keysystem manifest.


I'm currently focused on stamping out any issues that may be stemming from rollover PTS handling. If someone wanted to take on switching our key handling to be array based for Fragment that would be a great next step, just leave a comment and it'd be much appreciated!


After we update the data model from a AES-128 Identity key centric style to support a DRM case, I see the steps roughly as;

  • Discuss the extension property naming for the hls.js public config to signal preference for manifest delivered keys or initialization segment delivered
  • Add pre-emptitive MediaKeySession negotiation for manifest delivered DRM keys if the preference is given to use them over EME "encrypted" event keys

@itsjamie
Copy link
Collaborator Author

Closing this issue in favor of a new one with the direction above.

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

No branches or pull requests

5 participants