google / ExoPlayer

This project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media
https://developer.android.com/media/media3/exoplayer
Apache License 2.0
21.7k stars 6.02k forks source link

PlayReady on Chromecast with Google TV fails if two key requests are in-flight at once #9038

Open guidofranceschini opened 3 years ago

guidofranceschini commented 3 years ago

Hello,

I need to protect DASH content with multiple keys, and unfortunately I have to use license servers that can return only one key at a time Apparently, EXO is supposed to deal with this scenario (just setting drm_multi_session to true), and indeed I see that multiple, correct invocations to the License Server are generated, one for each key, and the license server consistently serves them. However, playback fails. Digging into the past issues and googling around, the only hint I found is in https://github.com/SRGSSR/srgcontentprotection-apple/issues/5 and relates to the need to remove key persistence in Android (???)

I have performed tests using version 2.11.8, and verified that the problem persists also in 2.14.0 I have performed tests with both Widevine and Playready, on multiple devices. In the logs I see errors due to Crypto key not available, but the first alerting log is:

I attach:

To reproduce the problem, I will share via e-mail a test content using Playready (the Widevine license server would require extra parameters to be computed by the app). This content material has been verified successfully on Edge browser with shaka player. You would need a device with Playready support, such as an Amazon Firestick (I used a Fire TV Stick 2020)

Thanks in advance

bugreport-sheldonp-PS7234-2021-06-09-09-36-17.zip logA1V1_OK.txt logA1V2_KO.txt

claincly commented 3 years ago

@icbaker I assume this is related to DRM, could you take a look?

icbaker commented 3 years ago

Thanks for providing the content over email. I played all 3 samples on a Chromecast with Google TV (I don't have a Fire TV Stick 2020) and didn't observe any playback errors, all 3 clips played to completion.

Without being able to reproduce the problem there's not much more I can do - if you're able to provide a widevine repro sample I can try that on a Pixel 3a XL.

guidofranceschini commented 3 years ago

Thanks, I worked to implement a change as simple as possible to provide you with a scenario for WV. In the end I hardcodied just a few strings within the code. And I got a surprise. The good news is that EXO works fine with WV. This is sort of bad news for me since I have to fix my own code. Yestarday I probably misread WV license server logs, under the bias that WV and PR behaved the same on the terminals. Instead, with WV I had my own bug in the code that caused the same key to be asked twice, therefore justifying the failure in the player. Nevertheless for PR the problem exists, thus I would recommend to keep this issue open until you have a chance to reproduce the scenario. I sent an e-mail providing instructions to enable the WV scenario and multiDRM content for test: this might be helpful to compare WV and PR behavior If you have hints on where to set breakpoint and what to investigate, I'd be glad to investigate the PR scenario myself

icbaker commented 3 years ago

What devices have you reproduced the PlayReady failure on apart from the Fire TV Stick 2020? Are you able to test it on a Chromecast with Google TV (the PlayReady-compatible device I have access to)?

I'm wondering if it's perhaps a device-specific problem.

guidofranceschini commented 3 years ago

Next week I'll have access to another PR enabled Android device. Wrt to chromecast ... how can I run EXO on it?

icbaker commented 3 years ago

Wrt to chromecast ... how can I run EXO on it?

The Chromecast with Google TV runs Android (unlike previous generations of Chromecast), so you can enable developer options + USB debugging the same as any other Android device - and then install the demo app.

guidofranceschini commented 3 years ago

Hi again, as promised, I made some tests using another playready enambled device (running AndroidTV 8.0) Results are interesting: sometimes the multikey scenario works, sometimes it fails. I have generated longer material, and created more complex manifest with several layers and 2, 3 or 4 keys. For this new set of content also my FireTV behaves similarly,, i.e. it randomly plays or fails. There seems to be some timing aspect that might determine success or failure I hope the new material (via e-mail) will let you reproduce the problem

icbaker commented 3 years ago

Thanks for the more detailed content - I tried the provided samples on the Chromecast with Google TV I have access to and didn't see any CryptoException failures.

I did see some other playback failures:

I intermittently saw:

V PlayReadyDrmPlugin: provideKeyResponse >> scope = PR79, response (1248 bytes)
V PlayReadySessionManager: getSession >>  session id [PR79]
V PlayReadySession: processLicenseResponse mSessionId.string() PR79
E PlayReadyUtil: Cannot find <LicenseNonce>
E PlayReadySession: Cannot extract nonce but use gnonce
E PlayReadySession: Cannot find drmHeader for given license response
E PlayReadyDrmPlugin: PlayReadySessionManager::processResponse() failed
...
java.lang.IllegalArgumentException: Failed to handle key response
        at android.media.MediaDrm.provideKeyResponse(Native Method)
        at com.google.android.exoplayer2.drm.FrameworkMediaDrm.provideKeyResponse(FrameworkMediaDrm.java:229)
        at com.google.android.exoplayer2.drm.DefaultDrmSession.onKeyResponse(DefaultDrmSession.java:492)

And l also saw some errors that looked like MP4 extraction errors, as if the extractor tried to read too much data from the MP4 file (which generally means the MP4 file is invalid/corrupt, because it has a size field in a box somewhere that's too large). I can no longer reproduce this one, and didn't save a stack trace unfortunately.

I didn't get very far digging into the IllegalArgumentException - but from the PlayReady logging it looks like the CDM didn't like something about the response from your license server?

I think at this point the CryptoExcpeption you're seeing looks like a device-specific error with the Fire TV Stick you're testing with. I would suggest you report it to Amazon directly.

guidofranceschini commented 3 years ago

Actually, I had similar behavior with a STB running AndroidTV 8. It's unfortunate you cannot replicate :( But I understand, if you cannot replicate, you can hardly help. Thanks anyway

guidofranceschini commented 3 years ago

Hi again, I investigated further. First of all, I realized that the test material I provided was corrupted. I am very sorry for that, now it is fixed. Secondly, I performed tests using 2 different License Server implementations. I observed similar results when using either License Server, just to confirm that the problem originates at the client side, Thirdly. I added logs here and there, and somehow understood where the issue originates. Whenever the request/response for key 1 is well separated from request/response for key 2, it works fine. If instead response for key 1 overlaps with request for key 2, something bad happens. One recognizable problem is that the second challenge becomes incorrect (easily detectable because the challenge string has a different length when OK vs KO: it turned out to be a couple of version fields within the challenge that are set to 0 instead of some bigger value); another apparent issue is that in some case the response of key 1 causes an "Illegal state exception: Failed to handle key response: Unsupported scheme or data format (-2006)"

Whatever the bug (whether device specific or not) a suitable work-around would be to just serialize request/response, i.e. to send request for key 2 after having processed the response for key 1.

Now my questions become:

Thanks in advance

guidofranceschini commented 3 years ago

I am trying to understand EXO code. I have verified that both postKeyRequest and onKeyResponse are executed by the same thread, thus I cannot simply delay execution of postKeyRequest for key 2, but I really need to insert some logic to make sure that postKeyRequest is invoked after onKeyResponse for key 1 has been executed. Or maybe signal somehow that the postKeyRequest shall be performed again. Granularity of the actual operations to be serialized might be finer, I guess, but this doesn't seem to change the dimension of the problem I would really appreciate some suggestion

guidofranceschini commented 3 years ago

In the end I implemented a solution that apparently works fine, Basically, I keep track of pending requests, and when a postKeyRequest is invoked I check whether there is a request still waiting for the response. If so, I "park" the current request, and resume it once the pending request is fulfilled. I can share the code if you like. Best regards

icbaker commented 3 years ago

Thanks for the detailed investigation. Just to make sure I understand what you've found, you reckon this sequence causes the problem (all on the same thread):

mediaDrm.getKeyRequest(sessionId1, ...)
mediaDrm.getKeyRequest(sessionId2, ...)
mediaDrm.provideKeyResponse(sessionId1, response1)
mediaDrm.provideKeyResponse(sessionId2, response2)

While this sequence is OK:

mediaDrm.getKeyRequest(sessionId1, ... )
mediaDrm.provideKeyResponse(sessionId1, response1)
mediaDrm.getKeyRequest(sessionId2, ...)
mediaDrm.provideKeyResponse(sessionId2, response2)

@jt64 Are you aware of PlayReady CDM implementations requiring that only a single key request is in-flight at once? Overlapping requests seem to cause problems.

As described above, the issue can be reproduced on a Chromecast with Google TV (Sabrina) device using the content sent to dev.exoplayer@gmail.com.

guidofranceschini commented 3 years ago

Exactly. I worked on a higher granularity (postKeyRequest and onKeyResponse) but the issue is certainly at the mediaDrm.getKeyRequest / mediaDrm.provideKeyResponse level