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

Multiple keys per license implementation #863

Closed
peaBerberian opened this issue Dec 15, 2020 · 8 comments
Closed

Multiple keys per license implementation #863

peaBerberian opened this issue Dec 15, 2020 · 8 comments
Labels
DRM Relative to DRM (EncryptedMediaExtensions)
Milestone

Comments

@peaBerberian
Copy link
Collaborator

To speed-up the license-fetching process, a proposal to communicate multiple keys in a given license has been made.

This could take the following form: for a given content, a license request linked to any of its key id will return a license containing every keys linked to that content the CDM could be trusted to use (e.g. a widevine L3 might not be trusted handling a license containing a Widevine L1 key, so it would not be contained in it).

For now, the RxPlayer performs a license request every time it encounters new initialization data (typically PSSH boxes) which is principally every time a new key id is encountered. So contents with multiple keys per license (let's call that... MKPL!) work but lead to unnecessary requests.

This issue is here to discuss on what we could do to improve on that.


Doing only a single license request (doable today by doing the request only for the first getLicense) works here but I'm afraid of future scenarios where we could still have multiple licenses each containing possibly multiple keys.

For example in a multi-Period DASH content, where each Period will have its own MKPL license. We could also imagine that a license might contain keys for every audio tracks but another for every video tracks.

At first, I thought that a general solution could be to perform a single license request at a time, waiting for the result of one to know if it applied to another initialization data (by checking in the MediaKeyStatusMap if the key id was present), but this doesn't work due to the fact that a license may not contain some keys on purpose, because the CDM is not trusted.
Here we would not know that the license should have contained that key, and still perform a license request when its initialization data is encountered.


If a solution has to be brought here, I become more and more convinced that maybe the particularity of that stream could be explicitely communicated to the RxPlayer.

For example, an API telling us "a license applies to the whole content" or "each license applies to a single Period" could work.
We could imagine something like:

player.loadVideo({
  // ...
  keySystems: [{
    // ...
    singleLicensePer: "period",
  }]
});

But then, an explicit API might not be flexible enough for some complicated cases (e.g. video tracks have different licenses but not audio tracks.)

To make it more flexible, we could imagine something like a callback. But it has to be powerful enough to communicate the whole extent a license has, so we can properly treat keys that are not present in it but should have been.

What are your thoughts ?

@peaBerberian peaBerberian added the DRM Relative to DRM (EncryptedMediaExtensions) label Dec 15, 2020
@lfaureyt
Copy link
Contributor

I agree that MKPL license handling in the RxPlayer should remain as reliable and as resilient as possible in the presence of future use cases or new MKPL license delivery schemes. So, how about designing the RxPlayer MKPL mode to be compatible with some kind of "worst case" or "minimal" MKPL license server model ? Let's assume the MKPL license server only guarantees that, for any MKPL license request:

  • the server returns, if anything, a valid MKPL license containing at least one key
  • the returned MKPL license always includes the explicitly requested keys (i.e. keys associated with key ids referenced in the license request), unless the CDM is not trusted for these keys

With this model, a simple and resilient implementation of the RxPlayer MKPL mode could go as follows:

  • maintain a per-content MediaKeyStatusMap with an extended key status (adding special key status disallowed to standard EME key statuses)
  • in this map, set to disallowed the status of any key that was retained by MKPL server when explicitly requested
  • keep requesting MKPL licenses one at a time, according to newly or previously discovered init data, until there are no more keys with status expired or status-pending in the map

Now a simple example. Let's assume we have a decent MKPL license server (that returns all allowed keys for any license request), a content with 3 keys (AU, SD, HD) and a CDM trusted only for AU and SD keys.

  • if the 1st issued license request is for the HD track, then the 1st received license will bring AU and SD track keys and mark HD track key as disallowed, halting license fetching requests and allowing decoding to proceed as soon as AU and SD segments are loaded
  • if the 1st issued license request is for the AU or SD track, then the 1st received license will also bring AU and SD track keys, allowing decoding to proceed right away, the only difference being that the RxPlayer might, later, unnecessarily request a license for the HD track (only to discover that it is disallowed)

So, if the app has the possibility to give a hint to the RxPlayer on the preferred initial video quality (SD vs HD here), it can choose between:

  • the 1st option where the RxPlayer will not issue unnecessary license requests (but loose some time initially loading init data and some segments from the HD video track)
  • the 2nd option where the RxPlayer will not loose time in initial stream loading (but will later invisibly issue an unnecessary license request and attempt uselessly to raise video quality)

I have the feeling this is not really satisfactory, but that is the simplest and most resilient strategy I can think of right now without the app expressing and transmitting to the RxPlayer some specific and/or complex expectations on the behaviour of the MKPL license server or on the properties of the content itself.

@lfaureyt
Copy link
Contributor

New thought. On top of the above "minimum assumption" behaviour, the RxPlayer could optionaly pre-fill the extended MediaKeyStatusMap with the key ids declared in the manifest, and give them a special initial status so that all keys not provided in the 1st received MKPL license automatically get the disallowed status ! This would yield a somehow "ideal" behaviour in my previous example. The option would be named something like mkplExpectAllDefaultKidsFromManifest for instance.

@peaBerberian
Copy link
Collaborator Author

For the first solution, that should work but yes we might perform multiple unnecessary requests for undecipherable profiles.
This is the most flexible solution though.

For the "extended MediaKeyStatusMap", this is more or less the same solution I ended with in my initial post.
The problem here is we might be again in a complex case where not all key ids declared in a manifest will be in the same license (e.g. in a multi-Period content where license are per-Period). In that case, we don't want to give the disallowed status to all keys not declared in a license.
That mkplExpectAllDefaultKidsFromManifest could thus tell us what to expect (the singleLicensePer option) but there's again the problem of flexibility in cases of more complex configurations.

@peaBerberian
Copy link
Collaborator Author

peaBerberian commented Dec 16, 2020

For now, I see two possible strategies:

  1. We only do one license request at a time.
    If the license contains keys linked to the key id of other profiles, we can skip those future profiles' license requests
    If the license does not contain the key for which the request was done (linked to the key id of the initialization data generating the challenge), we consider that key id (and by extension, any profile linked to it) as not usable.

    Pros:

    • the most flexible solution. Handle any case

    Cons:

    • we might have multiple request for a given license when a license does not contain a key because it does not trust the CDM

    • we have one license request at a time at most.
      The previous of these cons and this one could even mean poorer performance than today's one key per license scheme when some keys are not delivered in a MKPL license. Because we would still have multiple requests, but now they wouldn't be in parallel

    • the CDM has to be well implemented: the MediaKeysStatusMap has to contain information about every keys in a license an acceptable time after the license has been pushed. The onkeystatuseschange event should be triggered etc.

  2. We specify an API which allows an application to signal the "extent" a licence has.
    Every key not encountered for that "extent" will be considered as not usable.

    Pros:

    • Only one licence request at most when only one is needed

    • We're still able to do multiple requests at the same time when needed

    • Less possible compatibility problems when the CDM is not well implemented

    Cons:

    • Much less flexible. If a new complex case (e.g. multiple video tracks, each with its own MKPL licence) arrive, the API will be difficult to specify to handle that case.

    • The application has to know the licence's extent in advance

@lfaureyt
Copy link
Contributor

lfaureyt commented Dec 22, 2020

OK, your option 2 looks promising and quite extensible after all. I would vote for an API allowing to tell directly to the RxPlayer about the expected "scope" of any license e.g. a parameter such as licenseScope whose value could either be representation (safe default), adaptation, period or media. Or would this be maybe a little too DASH-specific ?

Now about CDM compatibility, I'd like to draw your attention to some embedded CDM implementations (STB) that:

  • use a per-keyId decryption channel (this maps to some hardware resource and is logically bound to the key-Id value)
  • currently require such decryption channel to be already allocated when encrypted fragments are pushed by the browser
  • currently allocate such decryption channel when the generateRequest EME API is invoked

So, we'll probably have to discuss whether this CDM implementation should be modified (e.g. memoize keyId / initdata pairs inside CDM and allocate corresponding decryption channel on-the-fly when fragments with this keyId are pushed ?) or whether the RxPlayer should/could still perform generateRequest calls even when not emitting corresponding license requests. Also, this brings the question of the lifecycle of these decryption channels: if they are not bound 1-1 to EME sessions anymore, then when should they be closed/discarded by the CDM ?

This is food for thought so we don't forget both ends of the problem while trying to figure out a good solution on the player side.

@peaBerberian
Copy link
Collaborator Author

peaBerberian commented Dec 28, 2020

Or would this be maybe a little too DASH-specific ?

The RxPlayer already integrated those words in its API (though it is true that I sometimes prefer that the term adaptation would be called "track" instead and representation would be called "rendition" or "profile", as it would be clearer for API discoverability) so no problem with those terms.

Also, an RxPlayer's adaptation is a special case that doesn't translate absolutely to a DASH AdaptationSet (e.g. AdaptationSets that can be switched from one another are currently considered as part of the same RxPlayer's Adaptation), so until we're set on a clear definition here (we still let us the flexibility to adjust that for now), I think we could just go with either:

  • "representation" (default)
  • "period"
  • "media"

Now about CDM compatibility, I'd like to draw your attention ...

You mean that in some implementations, a generateRequestcall might be necessary, even if it leads to no license request?

That's not optimal for us, we thought that key-id where inserted in a MediaKeyStatusMap (the only browser API we have access to which lists us the key ids linked to a Session, I think) based on the content of the license, and not of the initialization data (PSSH boxes).

Here this means that some MediaKeySession would have to be created (and be kept alive at least as long as the one containing the license?) despite never actually calling "update" on it (but maybe still listening to linked errors?).

It is much harder to implement than just having a single MediaKeySession per scope, linked to the license and containing every key ids in its MediaKeyStatusMap

@lfaureyt
Copy link
Contributor

You mean that in some implementations, a generateRequest call might be necessary, even if it leads to no license request?

Yes, that is my understanding from reading the CDM source code (last time I checked). So we'll have to discuss this with the CDM developers.

First, they might be reluctant to defer such hardware resources allocation until the license is transmitted to the CDM.
Second, they might not like to allocate as many hardware decryption channels as there are key ids in the received license.
The feasability and "optimality" of both changes above have to be checked.

Third, I think they currently rely on a native DRM agent API that expects either an opaque PSSH (carrying a single key-id) or a raw key id to perform this hardware decryption channel allocation (retrieving the list of key ids from the received license might not be as straightforward as it sounds, even though such licenses are known to be parseable).

Anyway, I agree with you and would also want to avoid creating these unfired MediaKeySession on the RxPlayer side so if this works with the most common playready and widevine CDM, then we should be able to make it work as well on the STB.

@peaBerberian
Copy link
Collaborator Author

Closed as #904 has just been merged

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

No branches or pull requests

2 participants