[Bug] Fix decryption issue on legacy CDM (e.g. Tizen 3.0)
This is a attempt fixes for issue: https://github.com/canalplus/rx-player/issues/1415
We observed a CDM issue on Tizen 3.0, the issue happens when playback starts from non-drm content and switch to drm protected content.
Turns out Tizen 3.0 CDM tries to decrypt the content by using the first init-segment appended to source buffer, if playback starts from non-drm segment, CDM will try to use the unencrypted init segment to decrypt the encrypted segment, which will eventually lead to a screen artifacts or blank screen.
re-create media source seems is the only way to fix this issue.
This PR is to introduce a new load video option reloadMediaSourceForFirstIncompatiblePeriodSwitch to make the user decide whether to enable the fixes. If so, rx-player will perform a reload media source action when:
- first period is non-drm protected
- first time period switch to drm-protected
Hi @KunXi-Fox,
Thanks for the PR! It's always nice to see external contributions and the implementation makes sense :D
For the API, I'm wondering if we could perhaps be higher-level and provide an option like keySystems[].onEncryptionStatusChange: "reload".
This way the RxPlayer would be able to perform more optimizations. E.g. Period-preloading won't unnecessarily run if we know in advance that the next Period won't be playable without reloading.
Also, the application would not have there to handle the logic of what we call "reloading the MediaSource" which is a fairly low-level mechanism.
For the latter in worst scenarios, there is the reload API which also can reload the last content at the same position with the same options.
Hi @peaBerberian , Thanks for the feedback. I'm not very familiar with rx-player codebase, so my changes may not be proper one, that's why I put it as a draft and waiting for any feedback.
For more context, the issue only happens under this condition:
- Playback starts with non-drm protected period
- playback switch to drm protected period
keySystems[].onEncryptionStatusChange: "reload" Sounds good, the only concern is Tizen 3.0 issue not happens on every encryption change. keySystems[].onEncryptionStatusChange: "reload" could solve the issue, but it's not that "precision", , because:
- We only want to make a "reload" when ⬆️ condition matches
- Current "reload" method more like a destroy + re-open video, which takes more time, that's not what we want.
The perfect solution from my brain is:
- we have a flag let's call it
enableLegacyDrmFix - when the flag equals
true, when playback starts from non-drm, we'll not pre-loading drm content (or loading and cache the buffer locally without append buffer) - when first time switch to drm protected content, we perform a
mediaSourceReloadaction and append drm protected buffer
Do you think it's a proper solution? If so, could you pls point me the right direction to implement it, I'm happy to make it happen. Thanks in advance!
If we can pinpoint it to a specific device (Tizen 2017), we could even make it automatic and specific to this device without needing an option.
Generally the encryption status change at Period transitions - but it can also happen at track of even Representation transition technically, so there could be a lot of ways this could be implemented depending on if we want to handle all cases or only the most frequent ones.
To handle all possible cases: I guess we could create some logic somewhere in src/main_thread/init which catches information from the ContentDecryptor (the module handling DRM information) ) - and we'll also need to detect if playback already began without needing decryption (by listening to the playbackObserver and checking if any representation is encrypted?
To handle only at Period transitions: we'll have to update some code in the code handling Period transitions I guess, in: src/core/stream/orchestrator/stream_orchestrator.ts.
Though there are still many things to handle: detecting when it's the first transition clear->encrypted for example.
Alternatively, we could look at other solutions for this problem. E.g. I don't remember if pushing segments only once the license has been communicated fixes the issue? If it does, it may be much simpler to work-around by only pushing media segments once they are known to be decipherable (or clear) on that device.
This could be done in src/core/stream/representation/representation_stream.ts.
This has the advantage of still being able to pre-load Period and keep a smooth transition between them.
Though even in those, I'm unsure of what the extent of the issue is on Tizen. For example let's imagine that the audio is encrypted but not the video for a Period A, but the reverse once we transition to a Period B: Does the issue happen or are we good because we initialized the decryption logic?
Though even in those, I'm unsure of what the extent of the issue is on Tizen. For example let's imagine that the audio is encrypted but not the video for a Period A, but the reverse once we transition to a Period B: Does the issue happen or are we good because we initialized the decryption logic?
In our cases the audio + video was encrypted together, so ⬆️ scenario will not happens, so I can't tell, sorry for that.
Hi @peaBerberian baed on your suggestion, I revert the changes for expose reloadMediaSource API and add a config reloadMediaSourceForFirstIncompatiblePeriodSwitch and make the fixes determine the flag value inside the rx-player code base.
Could you please help review? Thanks.
Hi @peaBerberian , Could I please get this PR reviewed?
The current implementation looks good and simple for the most usual case but I have some reservation before integrating it officially:
- In absolutes, encryption status change could even happen in-Period, such as a representation switch
- Technically, the first Period that is communicated to the
StreamOrchestratormight not correspond to the first segments that are actually pushed or decoded in some rare conditions
Those are rarer cases, I think that inevitably people are going to encounter those and not understand why that option didn't work for their cases.
Though I get what it solves regarding mixed encryption on some devices and that's a real issue we see multiple applications have issue with.
Maybe I could see this being solved by giving the application the power to trigger that? Letting the application do it seems less problematic to me because an application knows what kind of content it is going to encounter in a way we don't. But this may mean a more powerful API.
Current "reload" method more like a destroy + re-open video, which takes more time, that's not what we want.
I would think, without actually testing so I'm not 100% sure here, that the main difference in terms of timing between the reload API and what we here call "reloading the MediaSource" are:
- In
"multithreading"mode, we reload the manifest inreload - We may also be resetting some DRM stuff depending on the device and options (e.g.
closeSessionsOnStop)
I don't know which step takes the main chunk of the time in your case (maybe it's both, maybe it's neither but some other thing), though both look solvable with some effort (we could save the last manifest worker-side, we could keep some option around to not reset DRM etc.)
Letting the application do it seems less problematic to me because an application knows what kind of content it is going to encounter in a way we don't. But this may mean a more powerful API.
Yeah, I agree with this. For this issue, I tried to solve it from application level via reload, but it's not perfect since:
- Currently I didn't found a proper event to detect period switching is happening, so the timing call
reloadis a little bit late, which means the user already saw the issue before we callreload. -
reloadtakes more time than we expected
So if we could expose the a event for period switching and an extra API for just reloading all segments, I think that's enough to solve this issue from application level.