audio_service icon indicating copy to clipboard operation
audio_service copied to clipboard

Android app crashed when set androidStopForegroundOnPause: false

Open jargalbat opened this issue 3 years ago • 5 comments

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

  1. Set androidStopForegroundOnPause: false on AudioServiceConfig.
  2. Launch the example app and play audio. https://github.com/jargalbat/audio_demo
  3. 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!```


jargalbat avatar Oct 04 '22 06:10 jargalbat

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.

ryanheise avatar Oct 04 '22 10:10 ryanheise

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?

ryanheise avatar Oct 04 '22 12:10 ryanheise

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();
  }

jargalbat avatar Oct 05 '22 06:10 jargalbat

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.

ryanheise avatar Oct 05 '22 07:10 ryanheise

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.

ryanheise avatar Oct 05 '22 07:10 ryanheise