Android app crashed when set androidStopForegroundOnPause: false
Documented behaviour
This plugin wraps around your existing audio code to allow it to run in the background or with the screen turned off, and allows your app to interact with headset buttons, the Android lock screen and notification, iOS control center, wearables and Android Auto.
Actual behaviour
The android app crashed when clicking pause from the notification. An error occurred when I set androidStopForegroundOnPause: false.
Future<AudioHandler> initAudioService() async {
return await AudioService.init(
builder: () => MyAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.mycompany.myapp.audio',
androidNotificationChannelName: 'Audio Service Demo',
androidStopForegroundOnPause: false, // Error occurred on this line
),
);
}
The example app is working fine on the Samsung device. It's happening on the RealMe device.
Runtime error
D/AndroidRuntime(17999): Shutting down VM
E/AndroidRuntime(17999): FATAL EXCEPTION: main
E/AndroidRuntime(17999): Process: com.example.audio_demo, PID: 17999
E/AndroidRuntime(17999): android.app.RemoteServiceException: Bad notification(tag=null, id=1124) posted from package com.example.audio_demo, crashing app(uid=10681, pid=17999): Couldn't inflate contentViewsjava.lang.IllegalArgumentException: setShowActionsInCompactView: action 1 out of bounds (max 0)
E/AndroidRuntime(17999): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2249)
E/AndroidRuntime(17999): at android.os.Handler.dispatchMessage(Handler.java:106)
E/AndroidRuntime(17999): at android.os.Looper.loop(Looper.java:263)
E/AndroidRuntime(17999): at android.app.ActivityThread.main(ActivityThread.java:8276)
E/AndroidRuntime(17999): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(17999): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
E/AndroidRuntime(17999): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006)
I/Process (17999): Sending signal. PID: 17999 SIG: 9
Minimal reproduction project
https://github.com/jargalbat/audio_demo
Reproduction steps
- Set androidStopForegroundOnPause: false on AudioServiceConfig.
- Launch the example app and play audio. https://github.com/jargalbat/audio_demo
- Click the pause button from the notification.
Devices exhibiting the bug
OS: Android 11, Device: RealMe XT
Output of flutter doctor
[✓] Flutter (Channel stable, 3.0.5, on macOS 12.4 21F79 darwin-arm, locale en-MN)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.71.2)
[✓] Connected device (3 available)
[✓] HTTP Host Availability
• No issues found!```
Are you saying that I need to edit the code in reproduction step 1? These steps should be user steps, not code.
Also, your "minimal reproduction project" is not at all "minimal". is the database code a necessary part of it in order to reduce the bug?
Ideally you could fork this repository and make minimal changes to the example to reproduce the bug because that helps to narrow it down to the relevant changes that you needed to make beyond the official example.
If you really need all that complexity in there to reproduce the bug, let me know and we'll proceed with that.
Also, assuming you have a RealMe device (which I don't have), would you be willing to help doing a little debugging yourself?
In particular, the part of the code that's relevant here is in AudioService.java:
void setState(List<MediaControl> actions, long actionBits, int[] compactActionIndices, AudioProcessingState processingState, boolean playing, long position, long bufferedPosition, float speed, long updateTime, Integer errorCode, String errorMessage, int repeatMode, int shuffleMode, boolean captioningEnabled, Long queueIndex) {
boolean notificationChanged = false;
if (!Arrays.equals(compactActionIndices, this.compactActionIndices)) {
notificationChanged = true;
}
if (!actions.equals(this.actions)) {
notificationChanged = true;
}
this.actions = actions;
this.nativeActions.clear();
for (MediaControl action : actions) {
nativeActions.add(createAction(action.icon, action.label, action.actionCode));
}
this.compactActionIndices = compactActionIndices;
boolean wasPlaying = this.playing;
AudioProcessingState oldProcessingState = this.processingState;
this.processingState = processingState;
this.playing = playing;
this.repeatMode = repeatMode;
this.shuffleMode = shuffleMode;
PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
.setActions(AUTO_ENABLED_ACTIONS | actionBits)
.setState(getPlaybackState(), position, speed, updateTime)
.setBufferedPosition(bufferedPosition);
if (queueIndex != null)
stateBuilder.setActiveQueueItemId(queueIndex);
if (errorCode != null && errorMessage != null)
stateBuilder.setErrorMessage(errorCode, errorMessage);
else if (errorMessage != null)
stateBuilder.setErrorMessage(-987654, errorMessage);
if (mediaMetadata != null) {
// Update the progress bar in the browse view as content is playing as explained
// here: https://developer.android.com/training/cars/media#browse-progress-bar
Bundle extras = new Bundle();
extras.putString(MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, mediaMetadata.getDescription().getMediaId());
stateBuilder.setExtras(extras);
}
mediaSession.setPlaybackState(stateBuilder.build());
mediaSession.setRepeatMode(repeatMode);
mediaSession.setShuffleMode(shuffleMode);
mediaSession.setCaptioningEnabled(captioningEnabled);
if (!wasPlaying && playing) {
enterPlayingState();
} else if (wasPlaying && !playing) {
exitPlayingState();
}
if (oldProcessingState != AudioProcessingState.idle && processingState == AudioProcessingState.idle) {
// TODO: Handle completed state as well?
stop();
} else if (processingState != AudioProcessingState.idle && notificationChanged) {
updateNotification();
}
}
It would be helpful to add some debug output inside of this method to see the timing of when it is called and the length of the various actions lists:
System.out.println("setState. actions.size = " + actions.size()
+ ", nativeActions.size = " + nativeActions.size()
+ ", compactActionIndices.length = " + compactActionindices.length);
Can you test that and report the logs?
Hello @ryanheise. Thank you for the reply. I found the issue and fixed it. It happened when adding a playback state which is not necessary. I learned the tutorial from "suragch". https://suragch.medium.com/background-audio-in-flutter-with-audio-service-and-just-audio-3cce17b4a7d The tutorial is good but it is quite old in 2022. The source code is working fine on Samsung devices, but not on RealMe.
@override
Future<void> play() async {
// Audio Session
var session = await AudioSession.instance;
await session.setActive(true);
// Audio Service
// Issue happened on this code
playbackState.add(playbackState.value.copyWith(
playing: true,
controls: [MediaControl.pause],
));
// Just audio
await _player.play();
}
@override
Future<void> pause() async {
// Audio Service
// Issue happened on this code
playbackState.add(playbackState.value.copyWith(
playing: false,
controls: [MediaControl.play],
));
// Just audio
await _player.pause();
}
Reopening - your reproduction project should not crash since it's not doing anything that should be able to cause that. So there is still a bug here which I would like to investigate if you are willing to help since I don't have your device.
Oh wait, I see. I had only checked one place in the code where you set the controls: parameter and that was fine, but there were other places in the code where you set controls: with a smaller list and you forgot to also set the compact action indices to match. So yes that is a bug in your app, although I could probably make the plugin more robust to prevent it from crashing in this case.