ExoPlayer icon indicating copy to clipboard operation
ExoPlayer copied to clipboard

Crash in OfflineLicenseHelper

Open utonautes opened this issue 3 years ago • 2 comments

ExoPlayer Version

2.17.1

Devices that reproduce the issue

Any. devices.

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

  1. In Widevine DASH (MP4, H264) -> HD (cenc) in media.exolist.json, modify the drm_license_uri to https://proxy.uat.widevine.com/1234567890proxy?video_id=2015_tears&provider=widevine_test to force to receive 404 not found error.
  2. Launch the demo app.
  3. Click the download button of Widevine DASH (MP4, H264) -> HD (cenc) in media.exolist.json.
  4. Do something (play other videos, seek video, download other media...)
  5. Repeat step 3 and 4.

Just call OfflineLicenseHelper.downloadLicense() many times at the same time.

Expected result

offline license failure

Actual result

The app crashes with an NPE rarely (under 5%).

2022-06-10 20:53:52.899 30707-31080/com.google.android.exoplayer2.demo E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #11
    Process: com.google.android.exoplayer2.demo, PID: 30707
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$4.done(AsyncTask.java:415)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: java.lang.NullPointerException
        at com.google.android.exoplayer2.util.Assertions.checkNotNull(Assertions.java:154)
        at com.google.android.exoplayer2.drm.OfflineLicenseHelper.blockingKeyRequest(OfflineLicenseHelper.java:277)
        at com.google.android.exoplayer2.drm.OfflineLicenseHelper.downloadLicense(OfflineLicenseHelper.java:193)
        at com.google.android.exoplayer2.demo.DownloadTracker$WidevineOfflineLicenseFetchTask.doInBackground(DownloadTracker.java:392)
        at com.google.android.exoplayer2.demo.DownloadTracker$WidevineOfflineLicenseFetchTask.doInBackground(DownloadTracker.java:357)
        at android.os.AsyncTask$3.call(AsyncTask.java:394)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:923) 

DefaultDrmSession.onError() changes its state after dispatching the error event and getError() returns null if the state is not STATE_ERROR.

  public final DrmSessionException getError() {
    return state == STATE_ERROR ? lastException : null;
  }

  private void onError(Exception e, @DrmUtil.ErrorSource int errorSource) {
    lastException =
        new DrmSessionException(e, DrmUtil.getErrorCodeForMediaDrmException(e, errorSource));
    Log.e(TAG, "DRM session error", e);
    dispatchEvent(eventDispatcher -> eventDispatcher.drmSessionManagerError(e));
    if (state != STATE_OPENED_WITH_KEYS) {
      state = STATE_ERROR;
    }
  }

In OfflineLicenseHelper.blockingKeyRequest(), both drmSession.getError() and drmSession.getOfflineKeySetId() returns null at the same time with a low probability.

    DrmSessionException error = drmSession.getError();
    byte[] keySetId = drmSession.getOfflineLicenseKeySetId();
    // error == null && keySetId == null

Media

Widevine DASH (MP4, H264) -> HD (cenc) , modify the widevine license URL. (https://proxy.uat.widevine.com/pppppproxy?video_id=2015_tears&provider=widevine_test)

Bug Report

  • [ ] You will email the zip file produced by adb bugreport to [email protected] after filing this issue.

utonautes avatar Jun 10 '22 12:06 utonautes

Thanks for reporting - I think this is a result of OfflineLicenseHelper constructing a Looper internally and passing that as the playbackLooper to DefaultDrmSessionManager#setPlayer. DefaultDrmSessionManager then expects every subsequent interaction to happen on this thread. OfflineLicenseHelper breaks this expectation when it directly calls DefaultDrmSessionManager#getError from whatever thread the OfflineLicenseHelper#downloadLicense is called from.

I'll have a think about the best way to resolve this.

icbaker avatar Jun 15 '22 14:06 icbaker

Hi @icbaker We too have observed this crash. When and which version this fix is targeted?

ramprasad-vuclip avatar Jan 24 '24 09:01 ramprasad-vuclip