1

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!

Julien Debache
  • 319
  • 5
  • 13
  • Maybe https://stackoverflow.com/q/35079310/199364, though that isn't just on release. Note that release vs debug changes the *timing*, as does emulator vs actual device. Thus, I suspect there is a latent timing-related problem with your code, that only shows up in the case you mention. Looking at more examples of how people code this, might find a way that is robust. – ToolmakerSteve Jun 22 '23 at 21:38
  • have you tried adding exception handling or crash reporting? – Jason Jun 23 '23 at 00:26
  • @Jason There's not really an exception being thrown. The page where this component is created in `OnAppearing` simply never loads. As this is my main page, the app gets stuck on the splash screen. The stack trace above is the only indication I have that something is going wrong. – Julien Debache Jun 23 '23 at 15:52
  • @ToolmakerSteve Thanks for the hints, I will be looking into timing issues. – Julien Debache Jun 23 '23 at 15:54
  • The really weird thing is that there is an async method `PrepareAsync` which seems to be the async counterpart of `Prepare`. However it does not return a task and therefore is not awaitable. This does not make any sense. – Julien Debache Jun 23 '23 at 18:09
  • I actually don't know what to do as I solved my own issue, with some hints. Does one of you want to post an answer that I could accept ? – Julien Debache Jun 23 '23 at 19:21
  • Go ahead and post your own answer, showing the actual code. Feel free to mention the hints that helped you develop an answer. Accept that after 48 hours. – ToolmakerSteve Jun 23 '23 at 19:32
  • One more hint; hopefully related to your solution: *"is not awaitable"* - It is a wrapper to the corresponding native Android method, that has no concept of `await`. To know when media is ready, attach a handler to [MediaPlayer.Prepared event](https://learn.microsoft.com/en-us/dotnet/api/android.media.mediaplayer.prepared?view=xamarin-android-sdk-13). – ToolmakerSteve Jun 23 '23 at 19:34

1 Answers1

0

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.

Julien Debache
  • 319
  • 5
  • 13