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!
Even though I do not fully understand how, a combination of two changes solved the issue:
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.
<AndroidEnableMarshalMethods>false</AndroidEnableMarshalMethods>
Found out about this here: https://github.com/xamarin/xamarin-android/issues/7876#issuecomment-1528087178.
Thanks to the people in the comments for pointing me in the right direction.