-
Notifications
You must be signed in to change notification settings - Fork 133
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
Single license per content implementation #904
Conversation
This PR will also simplify the implementation of the following features:
|
lastKeyUpdate$.next(evt.value); | ||
} | ||
}), | ||
catchError(err => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From now on, t's just re-indentation, nothing to see here
@@ -322,8 +323,8 @@ function handleKeyStatusesChangeEvent( | |||
throw err; | |||
}) | |||
); | |||
return observableConcat(getKeyStatusesEvents(session, keySystemOptions, keySystem), | |||
callback$); | |||
return observableMerge(getKeyStatusesEvents(session, keySystemOptions, keySystem), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, there's no difference here as everything from the first Observable is synchronous.
But I felt this should be more of a merge.
…-decipherable before This commit is based on the multiple-keys-per-license commits (in #904) and add on top of it a new behavior. Representations which have their key-id added to one of the `MediaKeyStatusMap` linked to the current content and which are not fallbacked from, will now have their `decipherable` property updated to `true` - even if it was set to `false` before (and thus even if we had fallbacked from it in the past). This development was made a lot easier by the work done to support the `singleLicensePer` option (#904) with now the concept of "whitelisted key ids", in opposition of the "blacklisted key ids" we fallback from. When a key id is found to be whitelisted (technically this means currently that its `MediaKeyStatus` is not either: `"internal-error"`, `"output-restricted"` or `"expired"`, the last one depending on `keySystems options) we will now: - update the related `Representation`'s `decipherable` property to `true` - regardless of its previous state - schedule a `decipherabilityUpdate` event with that update through the API (and through the `Manifest` object with the event of the same name) - In the `AdaptationStream` - only if the updates changed the list of available Representations for that Adaptation, re-construct the list of the Representations the ABR has to choose from --- To note that this has never been tested in real conditions.
src/core/eme/eme_manager.ts
Outdated
if (options.singleLicensePer === "content" && handledSessions.getLength() > 0) { | ||
const keyIds = initializationData.keyIds; | ||
if (keyIds === undefined) { | ||
log.warn("EME: Initialization data linked to unknown key id, we'll" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will log "...we'llnot.."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/core/eme/eme_manager.ts
Outdated
const lastKeyUpdate$ = new ReplaySubject<IKeyUpdateValue>(1); | ||
|
||
// First, check that this initialization data is not already handled | ||
if (options.singleLicensePer === "content" && handledSessions.getLength() > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we have two different objects ? handledSessions
for singleLicensePer === "init-data"
and another one for "content" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that just complexify the code?
Also, this code could be adaptable to a singleLicensePer: "period"
by adding some metadata to both handledSessions
and the initializationData
sent to the EMEManager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was just a little bit strange for me that the way to check if we already handled the init data was to see if there was at least one handled session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand.
The fact is singleLicensePer: "content"
also means a single MediaKeySession per content. Maybe I should define a isEmpty
methode on handledSessions
to make that more readable, and maybe I should find a better name that handledSessions
, I probably should add a comment too.
Instead of handledSessions
maybe , contentSessions
?
This would make:
if (options.singleLicensePer === "content" && !contentSessions.isEmpty()) {
// If only a single license is necessary per content, we only need to create
// a single MediaKeySession for that content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I had thought of a new method, without knowing exactly what it could be!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thus, will you add a new method ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Didn't take the time yet :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
More likely Widevine L2 STBs (if you're referring to X1/X2 STBs aka "Cube C"). |
Yes, I was talking about those and found out only after that they actually were L2 |
7f1058c
to
b6be79d
Compare
b6be79d
to
54062a3
Compare
…-decipherable before This commit is based on the multiple-keys-per-license commits (in #904) and add on top of it a new behavior. Representations which have their key-id added to one of the `MediaKeyStatusMap` linked to the current content and which are not fallbacked from, will now have their `decipherable` property updated to `true` - even if it was set to `false` before (and thus even if we had fallbacked from it in the past). This development was made a lot easier by the work done to support the `singleLicensePer` option (#904) with now the concept of "whitelisted key ids", in opposition of the "blacklisted key ids" we fallback from. When a key id is found to be whitelisted (technically this means currently that its `MediaKeyStatus` is not either: `"internal-error"`, `"output-restricted"` or `"expired"`, the last one depending on `keySystems options) we will now: - update the related `Representation`'s `decipherable` property to `true` - regardless of its previous state - schedule a `decipherabilityUpdate` event with that update through the API (and through the `Manifest` object with the event of the same name) - In the `AdaptationStream` - only if the updates changed the list of available Representations for that Adaptation, re-construct the list of the Representations the ABR has to choose from --- To note that this has never been tested in real conditions.
…-decipherable before This commit is based on the multiple-keys-per-license commits (in #904) and add on top of it a new behavior. Representations which have their key-id added to one of the `MediaKeyStatusMap` linked to the current content and which are not fallbacked from, will now have their `decipherable` property updated to `true` - even if it was set to `false` before (and thus even if we had fallbacked from it in the past). This development was made a lot easier by the work done to support the `singleLicensePer` option (#904) with now the concept of "whitelisted key ids", in opposition of the "blacklisted key ids" we fallback from. When a key id is found to be whitelisted (technically this means currently that its `MediaKeyStatus` is not either: `"internal-error"`, `"output-restricted"` or `"expired"`, the last one depending on `keySystems options) we will now: - update the related `Representation`'s `decipherable` property to `true` - regardless of its previous state - schedule a `decipherabilityUpdate` event with that update through the API (and through the `Manifest` object with the event of the same name) - In the `AdaptationStream` - only if the updates changed the list of available Representations for that Adaptation, re-construct the list of the Representations the ABR has to choose from --- To note that this has never been tested in real conditions.
9395439
to
82a3263
Compare
## The need ### What is this? This commit implements a `singleLicensePer` option, which allows an application to tell the RxPlayer that the current content only has a single license for the whole content, even when it has multiple keys associated to different tracks or qualities. The RxPlayer will then be able to perform a single license request and, if the right options are set, to fallback from non-decryptable contents, which will be both: 1. keys which have the wrong key-status ("output-restricted" if `fallbackOn.keyOutputRestricted` is set and "internal-error" if `fallbackOn.keyInternalError` is set). 2. keys which were not included in that license. Those will be fallbacked from, without needing to set any API. The idea is also to be able to make evolutions to that API to implement for example a single license per Period in the future. ### Why doing that? The idea here is to perform a single license request, initially for a single quality, which will in response return a license (technically it could be multiple concatenated licenses in the CDM's point of view depending of the key system, but it is still a single request and data structure in the player's point of view) allowing to decrypt all encrypted media from that same content. The advantages of doing that are multiple. Most of all: 1. it allows to only perform a single license request instead of multiple ones. Reducing possible server loads but also reducing network latencies. 2. We could be able to tell much sooner which key are not supported and thus avoid switching to an undecipherable Representation to only later fallback from it. 3. some other players not being compatible with contents necessitating multiple license requests, putting all keys in a single license can improve compatibility for when streams are used with multiple players. 4. it reduces interactions with EME APIs, which we found to be very slow on some devices (we for example found out that generating a challenge can take more than 1 second on some embedded devices). And surely many others. Before that commit, the RxPlayer would already play those contents, but would still perform multiple license requests reducing the potential gains. ### What it means technically Technically, the description of this feature is simple: When `singleLicensePer` is set to `"content"`: - we should only perform a single license request, event when there's multiple initialization data encountered in the MPD and initialization segments - we should fallback from `Representation`s whose corresponding key ids are either: 1. Not signaled in the license 2. Signaled in the license but have to be fallbacked from according to the `fallbackOn` options ## Implementation ### Re-purposing `handledInitData` To implement that, the main idea is to re-purpose the `handledInitData` cache (in the `EMEManager`), which stores initialization data that has already been encountered. The only goal of that structure before being to avoid creating multiple MediaKeySessions for the same initialization data, it isn't a stretch to redefine it as a structure avoiding creating multiple MediaKeySessions linked to the same key ids. I changed the way it is used in two ways: 1. When `singleLicensePer` is set to `"content"` we can simply check when it's empty to only create a session on the first initialization data encountered (we can imagine also adding several IDs to that structure to easily implement `"period"` etc.). 2. As we still need to fallback from any initialization data that has no corresponding key in the license when the latter is loaded, I had to add to that structure an Observable emitting `"blacklisted"` key ids from the corresponding `MediaKeySession` and the other key ids, now considered by opposition as `"whitelisted"`. Simply said, every key ids linked to a MediaKeySession which are not present blacklisted (because of the usual `fallbackOn` options) will be whitelisted. By listening to that Observable everytime new initialization data is encountered, we can just compare the linked key id with whitelisted key ids that Observable emits - which will be once the license is pushed. If the license has already been pushed, the Observable will emit immediately. ### Adding the key id to `contentProtections$` event Because we are using key ids here to detect which keys are not in a given license, we have to emit those alongside the initialization data to the `EMEManager`. This is done through the already-existing `contentProtections$` Observable. In the hypothesis that no key id is anounced in the Manifest, we may thus have to parse initialization data to extract it from there, so we can support multi-key per license mode with such contents. This is not done here for the moment, but I'm working on it now. In the meantime, initialization data for which the corresponding key id is not known will still lead to another license request, which I felt was a good compromise. ## Does it work? I tested it with success on: - Widevine L1 on STBs - Widevine L3 on Linux - Chrome - Widevine L3 on Linux - Firefox - Widevine L3 on Mac - Chrome - Widevine L3 on Windows - Edge - PlayReady SL3000 on Windows - Edge I know it doesn't work yet on some set-top boxes, but this seems more an exception than the norm. We've still scheduled a meeting to learn more about this issue. Depending on the result, we may want to perform updates on that code.
Before this commit, persistent MediaKeySessions were added to the `PersistentSessionsStore` (for later retrieval) as soon as possible: when their `sessionId` property was known (meaning as soon as the `generateRequest` answered). This worked pretty well, but we found a persistence-related issue that could profit from taking another strategy: After loading a persistent MediaKeySessions (that has previously been added to the `PersistentSessionsStore`), the RxPlayer immediately checks the status of its keys through the `keyStatuses` property. However, on some platforms, the `keyStatuses` property is not directly populated once the `load` call is done, but only after a difficult-to-predict delay (sometimes immediately after, sometimes more than 10 milliseconds after). This imply that we might now have to wait after loading a persistent session just to be sure that all keys information have been added to it. We limited that by only waiting a delay if the `keyStatuses` property is empty, yet this pre-condition was frequent: any time the user loaded a new content before the license request succeeded (but after the `generateRequest` call) we would be legitimately in a case where the `keyStatuses` property is actually empty. To avoid penalizing too much legitimate cases, this commit adds a persistent session to the `PersistentSessionsStore` only once at least one key is added to its `keyStatuses` property. This ensures that having an empty `keyStatuses` property will now be unusual enough. In that case, incurring a delay (let's say up to 100ms) will be much less penalizing.
…update eme: only persist MediaKeySessions once its keys are known
…d by a single session in content mode
82a3263
to
bfe2826
Compare
Single license per content implementation
…-decipherable before This commit is based on the multiple-keys-per-license commits (in #904) and add on top of it a new behavior. Representations which have their key-id added to one of the `MediaKeyStatusMap` linked to the current content and which are not fallbacked from, will now have their `decipherable` property updated to `true` - even if it was set to `false` before (and thus even if we had fallbacked from it in the past). This development was made a lot easier by the work done to support the `singleLicensePer` option (#904) with now the concept of "whitelisted key ids", in opposition of the "blacklisted key ids" we fallback from. When a key id is found to be whitelisted (technically this means currently that its `MediaKeyStatus` is not either: `"internal-error"`, `"output-restricted"` or `"expired"`, the last one depending on `keySystems options) we will now: - update the related `Representation`'s `decipherable` property to `true` - regardless of its previous state - schedule a `decipherabilityUpdate` event with that update through the API (and through the `Manifest` object with the event of the same name) - In the `AdaptationStream` - only if the updates changed the list of available Representations for that Adaptation, re-construct the list of the Representations the ABR has to choose from --- To note that this has never been tested in real conditions.
…-decipherable before This commit is based on the multiple-keys-per-license commits (in #904) and add on top of it a new behavior. Representations which have their key-id added to one of the `MediaKeyStatusMap` linked to the current content and which are not fallbacked from, will now have their `decipherable` property updated to `true` - even if it was set to `false` before (and thus even if we had fallbacked from it in the past). This development was made a lot easier by the work done to support the `singleLicensePer` option (#904) with now the concept of "whitelisted key ids", in opposition of the "blacklisted key ids" we fallback from. When a key id is found to be whitelisted (technically this means currently that its `MediaKeyStatus` is not either: `"internal-error"`, `"output-restricted"` or `"expired"`, the last one depending on `keySystems options) we will now: - update the related `Representation`'s `decipherable` property to `true` - regardless of its previous state - schedule a `decipherabilityUpdate` event with that update through the API (and through the `Manifest` object with the event of the same name) - In the `AdaptationStream` - only if the updates changed the list of available Representations for that Adaptation, re-construct the list of the Representations the ABR has to choose from --- To note that this has never been tested in real conditions.
Single license per content implementation
Single license per content implementation
resolves #863
The need
What is this?
This PR implements a
singleLicensePer
option, which allows an application to tell the RxPlayer that the current content only has a single license for the whole content, even when it has multiple keys associated to different tracks or qualities.The RxPlayer will then be able to perform a single license request and, if the right options are set, to fallback from non-decryptable contents, which will be both:
keys which have the wrong key-status ("output-restricted" if
fallbackOn.keyOutputRestricted
is set and "internal-error" iffallbackOn.keyInternalError
is set).keys which were not included in that license. Those will be fallbacked from, without needing to set any API.
The idea is also to be able to make evolutions to that API to implement for example a single license per Period in the future.
Why doing that?
The idea here is to perform a single license request, initially for a single quality, which will in response return a license (technically it could be multiple concatenated licenses in the CDM's point of view depending of the key system, but it is still a single request and data structure in the player's point of view) allowing to decrypt all encrypted media from that same content.
The advantages of doing that are multiple. Most of all:
it allows to only perform a single license request instead of multiple ones.
Reducing possible server loads but also reducing network latencies.
We could be able to tell much sooner which key are not supported and thus avoid switching to an undecipherable Representation to only later fallback from it.
some other players not being compatible with contents necessitating multiple license requests, putting all keys in a single license can improve compatibility for when streams are used with multiple players.
it reduces interactions with EME APIs, which we found to be very slow on some devices (we for example found out that generating a challenge can take more than 1 second on some embedded devices).
And surely many others.
Before that commit, the RxPlayer would already play those contents, but would still perform multiple license requests reducing the potential gains.
What it means technically
Technically, the description of this feature is simple:
When
singleLicensePer
is set to"content"
:we should only perform a single license request, event when there's multiple initialization data encountered in the MPD and
initialization segments
we should fallback from
Representation
s whose corresponding key ids are either:fallbackOn
optionsImplementation
Re-purposing
handledInitData
To implement that, the main idea is to re-purpose the
handledInitData
cache (in theEMEManager
), which stores initialization data that has already been encountered.The only goal of that structure before being to avoid creating multiple MediaKeySessions for the same initialization data, it isn't a stretch to redefine it as a structure avoiding creating multiple MediaKeySessions linked to the same key ids.
I changed the way it is used in two ways:
When
singleLicensePer
is set to"content"
we can simply check when it's empty to only create a session on the firstinitialization data encountered (we can imagine also adding several IDs to that structure to easily implement
"period"
etc.).As we still need to fallback from any initialization data that has no corresponding key in the license when the latter is loaded, I had to add to that structure an Observable emitting
"blacklisted"
key ids from the correspondingMediaKeySession
and the other key ids, now considered by opposition as"whitelisted"
.Simply said, every key ids linked to a MediaKeySession which are not present blacklisted (because of the usual
fallbackOn
options) will be whitelisted.By listening to that Observable every time new initialization data is encountered, we can just compare the linked key id with whitelisted key ids that Observable emits - which will be once the license is pushed.
If the license has already been pushed, the Observable will emit immediately.
Adding the key id to
contentProtections$
eventBecause we are using key ids here to detect which keys are not in a given license, we have to emit those alongside the initialization data to the
EMEManager
.This is done through the already-existing
contentProtections$
Observable.In the hypothesis that no key id is anounced in the Manifest, we may thus have to parse initialization data to extract it from there, so we can support multi-key per license mode with such contents.
This is not done here for the moment, but I'm working on it now.
Does it work?
I tested it with success on:
I know it doesn't work yet on some set-top boxes, but this seems more an exception than the norm. We've still scheduled a meeting to learn more about this issue.
Depending on the result, we may want to perform updates on that code.