Skip to content

[BUG]: MediaPlayer can block the main thread (cause of ANR) #5650

Description

@BenHenning

Describe the bug

It seems that Android's MediaPlayer performs some blocking operations that, when interacted with on the main thread, can cause ANRs in certain conditions.

Sample from the Play Console:

"main" tid=1 Blocked
  at android.media.MediaHTTPConnection.disconnect (MediaHTTPConnection.java:172)
  at android.media.IMediaHTTPConnection$Stub.onTransact (IMediaHTTPConnection.java:137)
  at android.os.Binder.execTransactInternal (Binder.java:1290)
  at android.os.Binder.execTransact (Binder.java:1249)
  at android.media.MediaPlayer._release (Native method)
  at android.media.MediaPlayer.release (MediaPlayer.java:2215)
  at org.oppia.android.domain.audio.AudioPlayerController.releaseMediaPlayer (AudioPlayerController.java:259)
  at org.oppia.android.app.player.audio.AudioViewModel.handleRelease (AudioViewModel.java:151)
  at org.oppia.android.app.player.audio.AudioFragmentPresenter.handleOnDestroy (AudioFragmentPresenter.java:217)
  at org.oppia.android.app.player.audio.AudioFragment.onDestroy (AudioFragment.java:73)
  at androidx.fragment.app.Fragment.performDestroy (Fragment.java:3219)
  at androidx.fragment.app.FragmentStateManager.destroy (FragmentStateManager.java:774)
  at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:350)
  at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:112)
  at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1647)
  at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3128)
  at androidx.fragment.app.FragmentManager.dispatchDestroy (FragmentManager.java:3107)
  at androidx.fragment.app.Fragment.performDestroy (Fragment.java:3214)
  at androidx.fragment.app.FragmentStateManager.destroy (FragmentStateManager.java:774)
  at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:350)
  at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:112)
  at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1647)
  at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3128)
  at androidx.fragment.app.FragmentManager.dispatchDestroy (FragmentManager.java:3107)
  at androidx.fragment.app.Fragment.performDestroy (Fragment.java:3214)
  at androidx.fragment.app.FragmentStateManager.destroy (FragmentStateManager.java:774)
  at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:350)
  at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete (SpecialEffectsController.java:745)
  at androidx.fragment.app.SpecialEffectsController$Operation.cancel (SpecialEffectsController.java:597)
  at androidx.fragment.app.SpecialEffectsController.forceCompleteAllOperations (SpecialEffectsController.java:332)
  at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3132)
  at androidx.fragment.app.FragmentManager.dispatchDestroy (FragmentManager.java:3107)
  at androidx.fragment.app.FragmentController.dispatchDestroy (FragmentController.java:334)
  at androidx.fragment.app.FragmentActivity.onDestroy (FragmentActivity.java:330)
  at androidx.appcompat.app.AppCompatActivity.onDestroy (AppCompatActivity.java:278)
  at android.app.Activity.performDestroy (Activity.java:8843)
  at android.app.Instrumentation.callActivityOnDestroy (Instrumentation.java:1472)
  at android.app.ActivityThread.performDestroyActivity (ActivityThread.java:5675)
  at android.app.ActivityThread.handleDestroyActivity (ActivityThread.java:5721)
  at android.app.servertransaction.DestroyActivityItem.execute (DestroyActivityItem.java:47)
  at android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
  at android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:176)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2440)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:211)
  at android.os.Looper.loop (Looper.java:300)
  at android.app.ActivityThread.main (ActivityThread.java:8315)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:581)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1028)

(Note that other thread stack traces have been removed for brevity since the main thread's stack trace seems sufficiently detailed to figure out what's going on here).

Steps To Reproduce

Unknown since this is an ANR reported by Android.

Expected Behavior

We should avoid avoidable ANRs.

Screenshots/Videos

No response

What device/emulator are you using?

Redmi Fire (Redmi 12)

Which Android version is your device/emulator running?

SDK 33

Which version of the Oppia Android app are you using?

0.14-beta-17f2ef3044

Additional Context

Note that this probably relates to #2430. It seems that per MediaPlayer's documentation that it doesn't require use on the main thread (though it's important to realize that its callbacks default to the main thread unless specifically configured to use a different handler/looper). I suspect that an ideal solution here may be to move all media player interactions to a background controller and have it liaise with the frontend using data providers (much in the same way that we do for other controllers in the app). We may need a mixin configuration (similar to that use for app language) to ensure that the controller is properly notified of lifecycle events for pausing/resuming/starting/stopping the media.

Note that this solution also will allow other classes of issues to be fixed (including preserving play location across configuration changes).

Due to the sensitivity of media player, we should gate this fix behind a feature flag for thorough testing.

Activity

  1. BenHenning commented on Jan 14, 2025

    @BenHenning
    SponsorMemberAuthor

    Note that this seems likely to be a bug in MediaPlayer itself (or some very complex interaction I'm not sure I fully understand) since it's a deadlock produce across the main thread (during fragment exit) and the media player's thread that's trying to download the audio source. It's quite possible we're doing something wrong in a callback (I haven't analyzed the code in detail to see if that's the case).

  2. added
    Impact: MediumModerate perceived user impact (non-blocking bugs and general improvements).
    Work: MediumThe means to find the solution is clear, but it isn't at good-first-issue level yet.
    and removed on Jan 16, 2025
  3. self-assigned this
    on Feb 5, 2025
  4. removed their assignment
    on Mar 4, 2025
  5. added
    Impact: HighHigh perceived user impact (breaks a critical feature or blocks a release).
    and removed
    Impact: MediumModerate perceived user impact (non-blocking bugs and general improvements).
    on Nov 12, 2025
  6. BenHenning commented on Nov 12, 2025

    @BenHenning
    SponsorMemberAuthor

    This isn't really a release blocker until GA (since we keep bumping it), but it is a priority issue so upping the impact given that it makes up a large number of ANR crashes (most).

  7. harshsomankar123-tech commented on Mar 6, 2026

    @harshsomankar123-tech
    Contributor

    Hii @BenHenning ,
    I investigated the ANR described in this issue and considered an approach to ensure that potentially blocking MediaPlayer operations do not run on the main thread.

    The idea is to move MediaPlayer interactions in AudioPlayerController (such as release(), reset(), start(), pause(), and seekTo()) to the injected @BackgroundDispatcher. Since this change affects core playback behavior, the new behavior could be gated behind a feature flag (BACKGROUND_MEDIA_PLAYER) so the existing synchronous implementation remains the default.

    When the feature flag is enabled:

    • MediaPlayer operations would be dispatched to the background dispatcher to avoid blocking the main thread.
    • Existing synchronization (audioLock) would be preserved to maintain thread safety.

    When the flag is disabled:

    • The current synchronous behavior would remain unchanged.

    This would also require adding the corresponding feature flag wiring (proto definition, bindings, constants, and configuration) following the existing feature flag patterns used in the project.

    Does this approach align with the intended direction for fixing the ANR? If so, could I please be assigned to work on this issue?

  8. BenHenning commented on Mar 9, 2026

    @BenHenning
    SponsorMemberAuthor

    @harshsomankar123-tech the current assumption is that #2430 will need to be addressed to fix this. The idea of pushing this to a background thread could work (and may be ideal), but there are some gotchas to ensure that the threads actually are set up the way the player expects. We were assuming that we'd need to use a coroutine actor to synchronize access to the player (since technically using a lock with the background dispatchers is likely to cause other problems).

    However, it seems like it may be possible to use ExoPlayer (haven't looked into this, and I'm not sure if @adhiamboperes has either).

    @adhiamboperes I defer to you on whether this issue should be available to work on since I'm assuming this will be covered as part of your planned audio work.

  9. added theissue type on Apr 11, 2026
  10. adhiamboperes commented on Oct 9, 2026

    @adhiamboperes
    Contributor

    I recently filed #6446 which I now realize is a duplicate of this one. I will keep the newer issue open since it has more information, but I have added a link to this issue so that context isn't lost.

    From the investigations into #6446, this issues is quite complex as reviewing #6464 has shown. It is not easy to fix this without doing a full refactor of the audio player architecture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Impact: HighHigh perceived user impact (breaks a critical feature or blocks a release).Work: MediumThe means to find the solution is clear, but it isn't at good-first-issue level yet.bugEnd user-perceivable behaviors which are not desirable.

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions