-
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
Cast License in Uint8Array #974
Conversation
As specified in the EME recommendation, update should take a A I think the issue we're having here is linked to the corresponding FairPlay implementation which is either incorrect or rely on another version of the specification. In that regard, are we sure we're not relying on a specific EME implementation in |
I think we could be sure that we are not relying on a specific EME implementation, I saw that the method As u suggested, it could be nicer to only patch that issue only on the env that throw. |
I guess that identifying the key system is a lot more reliable than guessing the browser although that doesn't answer the following question: "is this anomaly specific to the Safari browser or to the Fairplay key system?" |
From a quick check, it seems to have its source in Safari/WebKit: To note that it's only for the vendored It seems that on Safari we rely on the former when available due to an issue with requestMediaKeysSystemAccess (which leads to the creation of a WebkitMediaKeys directly, without going through the So we do seem to rely on a specific EME implementation when the issue is triggered, defined in the RxPlayer in What's weird is that we did consider that the license there should be a Uint8Array, so TypeScript is letting us do something we did not want here (still our fault but with a mitigating circumstance :p). At first I thought it was linked to microsoft/TypeScript#31311 (in short, TypeScript only does structural typing and an Uint8Array has all properties an ArrayBuffer has) but here we're the other way around: we're giving an ArrayBuffer to a function awaiting an Uint8Array. This should have been catched by TypeScript, so something else is also at play here. |
@@ -123,9 +123,12 @@ class WebkitMediaKeySession | |||
return reject("Unavailable WebKit key session."); | |||
} | |||
try { | |||
const licenseTypedArray = license instanceof ArrayBuffer |
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.
We might want to re-define update
as accepting a BufferSource if we're going down this path.
You also named it licenseTypedArray
which seems to me a too precize name for not being not enough precize: if we name it like that, not only is it a TypedArray, it is a Uint8Array specifically, not any other one.
Also on the same point, what if the license given was a Uint16Array, or a Float64Array? Those are all valid BufferSource but still not a Uint8Array.
return (castToObservable(getLicense) as Observable<Uint8Array|null>) | ||
.pipe(map(license => license !== null ? new Uint8Array(license) : null), | ||
getLicenseTimeout >= 0 ? timeout(getLicenseTimeout) : | ||
return (castToObservable(getLicense) as Observable<BufferSource|null>) |
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.
Why casting is necessary at all here? What is inferred if we do not?
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.
After doing some research, I think Typescript can't infer the type cause of the function castToObservable
and his type signature.
The current signature is this:
src/utils/cast_to_observable.ts
function castToObservable<T>(value? : T) : Observable<T>;
So in our case, the typescript infers the type with:
castToObservable<ArrayBufferView | ArrayBuffer | Promise<BufferSource | null> | null>(...)
And so, the same type will be returned while we have to return an Observable<BufferSource | null>
I managed to get it to work by editing the signature but it would mean refactoring elsewhere:
function castToObservable<T, J>(value? : T) : Observable<J>;
castToObservable<ArrayBufferView | ArrayBuffer | Promise<BufferSource | null> | null, ILicense | null>(getLicense)
By the way, I always wondered where the bindings of MSE/EME APIs where in browsers (like, I can see where they are implemented on the browser engine-side, but how does the JS engine knows about them and call them, considering that those are part of w3c/WHATWG browser specs, not ECMAScript ones). After doing some reading (it's very well documented actually!), it seems that it mostly done - at least in WebKit, Firefox and Chrome, through the special-purpose web IDL language. The idea just seems to add an IDL file named the same way than the header file. Binding functions are then automatically generated by some kind of compiler or script (https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/bindings/IDLCompiler.md). Those can then be called into by the JS engine (still curious how exactly, but that will be for another time). Will be used to generate bindings to methods in: Exact same way for Chromium (https://www.chromium.org/blink/webidl#TOC-File-organization): I did not look into firefox for now, but it seems they do it the same way: https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings#adding_webidl_bindings_to_a_class Found it was interesting enough to share. |
As we discussed about it @PaulRosset, I post here the rest about what I found on JS engine -> browser APIs interactions. I specifically searched for Chromium, mainly because I like their online code search tool :p. V8 <-> blink interactions seems to be performed through a part of blink code called "V8 bindings", whose code is here: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/bindings/ Inside it, a documentation page was particularly interesting including both the notion of the JS's context and more generally how workers and extensions are each sandboxed: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md The directory in which it is contains a lot of code that seems to make it work. Here begin speculation but I found idl_dictionary_base.h which implies that some files generated from IDL implement a
Continuing deeper in the code, I switched to I stopped here (for now!). |
? new Uint8Array(license) | ||
: license instanceof Uint8Array | ||
? license | ||
: new Uint8Array((license as ArrayBufferView).buffer); |
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.
Weird indent.
I guess that's OK, but I generally find it more readable that way:
const uInt8Arraylicense = license instanceof ArrayBuffer ? new Uint8Array(licence) :
license instanceof Uint8Array ? license :
new Uint8Array(
(license as ArrayBufferView).buffer
);
Also, casting as ArrayBufferView should not be needed. Why is that the case? If it is, it means that we forgot to handle other cases (as TypeScript should be smart enough to notice that this cannot be an ArrayBuffer)
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.
Ho yes, thanks for the indent suggestion, since I'm using prettier, I was more used to write it like this, but it's totally fair that since we don't use prettier in this project we should use another indent style.
And yes, I forgot to edit the update
method signature by this:
this.update = (license: Int8Array | ArrayBuffer) => {...}
So Typescript was not understanding cause we already checked that the license was a UInt8Array
@@ -114,7 +114,7 @@ class WebkitMediaKeySession | |||
this.keyStatuses = new Map(); | |||
this.expiration = NaN; | |||
|
|||
this.update = (license: Uint8Array) => { | |||
this.update = (license: Int8Array | ArrayBuffer) => { |
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.
One contains signed integers the other not
Also, I'm under the impression that the current implementation allows every ArrayBuffer + ArrayBufferView so every BufferSource
, no need to be picky here.
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.
ho yes, VSCode autocompletion got me here :(
but yes, Typescript seems to be lost, because he doesn't know the type anymore after checking:
license instanceof Uint8Array
Actually, its true, what could be the type if the method waits for a Uint8Array
but we check for it and it's not?
The linter is not happy right now due to trailing spaces and 95 columns being reached. |
@@ -123,9 +123,15 @@ class WebkitMediaKeySession | |||
return reject("Unavailable WebKit key session."); | |||
} | |||
try { | |||
const uInt8Arraylicense = license instanceof ArrayBuffer ? | |||
new Uint8Array(license) : |
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.
Weird indentation now, and the nested ternary becomes just slightly less readable for it to be worth it.
Why not using regular readable conditions, indented at the start of the line, and be done with it?
7cb2c7a
to
7f1410b
Compare
implementation of EME update method is waiting for a uInt8Array, so it means that if we would pass an other type like an ArrayBuffer, webkit implementation would throw because he is waiting a strict uIn8Array (ttps://github.com/WebKit/WebKit/blob/8afe31a018b11741abdf9b4d5bb973d7c1d9ff05/Source/WebCore/Modules/encryptedmedia/legacy/WebKitMediaKeySession.cpp#L134). So, we are basically casting everything we receive into a uInt8Array to pass it to the CDM then.
7f1410b
to
5cb9aea
Compare
We noticed a bug inside the RxPlayer on the FairPlay DRM environment where the
update()
EME method was expecting a strictUint8Array
type but sometimes it happens that the application for some reasons provides anArrayBuffer
.For most of the CDM, it's not a problem, but for FairPlay on Safari, it waits for a strict
UInt8Array
.In this PR, we are still allowing the
ArrayBuffer
as the rx-player doc was explicit about it:But internally, we cast whatever we receive except null in an Uint8Array.