Skip to content
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

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

Open
BenHenning opened this issue Jan 14, 2025 · 1 comment
Open

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

BenHenning opened this issue Jan 14, 2025 · 1 comment
Labels
bug End user-perceivable behaviors which are not desirable. Impact: Medium Moderate perceived user impact (non-blocking bugs and general improvements). Work: Medium The means to find the solution is clear, but it isn't at good-first-issue level yet.

Comments

@BenHenning
Copy link
Member

BenHenning commented Jan 14, 2025

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.

@BenHenning BenHenning added bug End user-perceivable behaviors which are not desirable. triage needed labels Jan 14, 2025
@BenHenning
Copy link
Member Author

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).

@adhiamboperes adhiamboperes added Impact: Medium Moderate perceived user impact (non-blocking bugs and general improvements). Work: Medium The means to find the solution is clear, but it isn't at good-first-issue level yet. and removed triage needed labels Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug End user-perceivable behaviors which are not desirable. Impact: Medium Moderate perceived user impact (non-blocking bugs and general improvements). Work: Medium The means to find the solution is clear, but it isn't at good-first-issue level yet.
Development

No branches or pull requests

2 participants