Search code examples
android.netandroid-mediaplayermaui

Android `MediaPlayer`'s `prepare` method crashes only in release mode AND on physical devices when used in `MAUI` application


I have the following wrapper for a media player:

public class AndroidAudioPlayer
{
    private readonly MediaPlayer player;

    private readonly MemoryStream stream;

    internal AndroidAudioPlayer(Stream audioStream)
    {
        this.player = new MediaPlayer();
        this.stream = new MemoryStream();
        audioStream.CopyToAsync(this.stream);
        var mediaDataSource = new StreamMediaDataSource(this.stream);
        this.player.SetDataSource(mediaDataSource);
        this.player.Looping = true;
        this.player.Prepare();
    }
}

in a MAUI application. It works fine in debug mode, on physical device and emulator AND it works fine on release mode on emulator, but it crashes on physical devices in release mode. Using adb logcat I was able to find the following stack trace:

06-22 18:37:22.176  9487  9487 E AndroidRuntime: java.lang.IllegalStateException
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.media.MediaPlayer.prepareAsync(Native Method)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at crc640ec207abc449b2ca.ShellSectionRenderer.n_onCreateView(Native Method)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at crc640ec207abc449b2ca.ShellSectionRenderer.onCreateView(ShellSectionRenderer.java:44)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3104)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1899)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1823)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1760)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2985)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2888)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1899)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1823)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1760)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2985)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2895)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1510)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.Activity.performStart(Activity.java:8616)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.ActivityThread.handleStartActivity(ActivityThread.java:4194)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:106)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.os.Looper.loopOnce(Looper.java:226)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.os.Looper.loop(Looper.java:313)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:8747)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Native Method)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
06-22 18:37:22.176  9487  9487 E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

I can't figure it out. The fact that it stops working only in Release mode AND on a physical device is driving me crazy. But then again, I'm not used to mobile development so it might be a common thing?

UPDATE

I made some changes to the code, to use the async methods wherever possible:

        this.stream = new MemoryStream();
        await audioStream.CopyToAsync(this.stream).ConfigureAwait(false);
        var mediaDataSource = new StreamMediaDataSource(this.stream);
        await this.player.SetDataSourceAsync(mediaDataSource).ConfigureAwait(false);
        this.player.Looping = true;
        var taskCompletionSource = new TaskCompletionSource();
        this.player.Prepared += (sender, args) => taskCompletionSource.SetResult();
        this.player.PrepareAsync();
        await taskCompletionSource.Task.ConfigureAwait(false);

On top of that, I tried adding this to the project file:

<AndroidEnableMarshalMethods>false</AndroidEnableMarshalMethods>

With those two changes, the app works when deployed straight to my physical device from Visual Studio in release mode, but still fails to start correctly when deployed through the Google Play Store.

It also works when deployed using the .apk package built by running:

dotnet publish App/App.csproj -f net8.0-android -c Release -r android-arm64

It seems that now I can narrow it down to an issue with the aab package or with the Play Store.

UPDATE 2 Nevermind, after restarting my phone, which probably cleared the play store cache, and reinstalling the app, it now also works through the Google Play Store!


Solution

  • Even though I do not fully understand how, a combination of two changes solved the issue:

    1. Use the asynchronous versions of the media player methods:
            this.stream = new MemoryStream();
            await audioStream.CopyToAsync(this.stream).ConfigureAwait(false);
            var mediaDataSource = new StreamMediaDataSource(this.stream);
            await this.player.SetDataSourceAsync(mediaDataSource).ConfigureAwait(false);
            this.player.Looping = true;
            var taskCompletionSource = new TaskCompletionSource();
            this.player.Prepared += (sender, args) => taskCompletionSource.SetResult();
            this.player.Prepare();
            await taskCompletionSource.Task.ConfigureAwait(false);
    

    Note that I had to move this code out of the constructor of my media player wrapper, since I cannot make a constructor async.

    1. Add this to your project file:
    <AndroidEnableMarshalMethods>false</AndroidEnableMarshalMethods>
    

    Found out about this here: https://github.com/xamarin/xamarin-android/issues/7876#issuecomment-1528087178.

    1. (Optional) If deploying through Google Play Store, make sure you have cleared the cache by restarting your device.

    Thanks to the people in the comments for pointing me in the right direction.