audio_service icon indicating copy to clipboard operation
audio_service copied to clipboard

Stopping the audio doesnt remove the notification

Open animesh-flashfeed opened this issue 1 year ago • 8 comments

Documented behaviour

stop() → Future Stop playback and release resources.

I have used both for dismissing the notification
await audioHandler?.stop(); await BaseAudioHandler().stop();

Actual behaviour

The notification doesn't get dismissed

Minimal reproduction project

Official example: example_playlist.dart

Reproduction steps

  1. Click the stop button and it doesn't dismiss the notification

Output of flutter doctor

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] VS Code (version 1.91.1)
[✓] Connected device (4 available)            
[✓] Network resources

• No issues found!```
### Devices exhibiting the bug
Pixel 6a, Android Version 14

animesh-flashfeed avatar Jul 19 '24 07:07 animesh-flashfeed

I have used both for dismissing the notification await audioHandler?.stop(); await BaseAudioHandler().stop();

Your code is not pertinent to the bug report because your bug report submits the "official example: example_playlist.dart" as the minimal reproduction project.

Note that there are several examples demonstrating different features of audio_service. If you want to see an example that demonstrates how to close the notification, see lib/main.dart.

ryanheise avatar Jul 19 '24 08:07 ryanheise

Same and I've done some debugging. It does not cancel the notification even though it did call NotificationManager.cancel();
Check the answer here The legacyStopForeground() must be called before onDestroy() or else the notification cannot be removed by code.

XuanTung4195 avatar Jul 26 '24 17:07 XuanTung4195

Got the same issue

IliaKhuzhakhmetov avatar Jul 28 '24 10:07 IliaKhuzhakhmetov

Facing the same issue here. But, this only happens when androidStopForegroundOnPause: false and androidNotificationOngoing: false if both are true the service does stop as expected after removing the app from task manager

But, this results in an issue where user pauses for momentarily and the OS kills the whole app for memory which is not ideal. This is espeically bad in Chinese android skins. Those OEM skins are too much aggressive

One cheeky way to get around this is while keeping both androidNotificationOngoing: false, androidStopForegroundOnPause: false, is to exit(0) on the BaseAudioHandler's overriden onTaskRemoved method

class MyAudioService extends BaseAudioHandler {
  // ... other stuff
  @override
  Future<void> onTaskRemoved() async {
    await myAudioPlayer.stop();
    if(Platform.isAndroid) exit(0);
  }
 // ... other stuff
}

[!CAUTION] This can get the app rejected on AppStore. Usually, programmatic process termination is somehow against their policy.

KRTirtho avatar Aug 15 '24 15:08 KRTirtho

This behaviour can be controlled through controlled state transitions although I will also investigate @XuanTung4195 's note above as this may help it to work more robustly.

ryanheise avatar Aug 15 '24 15:08 ryanheise

facing the same issue. the behavior is inconsistent. sometimes the notification gets removed but sometimes it just stays.

nateshmbhat avatar Sep 07 '24 08:09 nateshmbhat

I have the same problem. I wrote my own audio service on media3. The notification deletion works. It's all about the removeSession method, maybe it's worth going this way too. If necessary, I can send a code with how to use my service.

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        Log.d("PlaybackService", "onTaskRemoved")
        var condition = mediaSession?.player?.playWhenReady;
        if (mediaSession != null) {
            removeSession(mediaSession!!)
        }
        if (condition == null || !condition) {
            stopSelf()
        }
    }

    override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
        Log.d("PlaybackService", "onUpdateNotification")

        super.onUpdateNotification(session, startInForegroundRequired)
    }

    // Remember to release the player and media session in onDestroy
    @OptIn(UnstableApi::class)
    override fun onDestroy() {
        mediaSession?.run {
            Log.d("PlaybackService", "onDestroy")
            player.release()
            stopForeground(STOP_FOREGROUND_REMOVE)
            pauseAllPlayersAndStopSelf()
            if (mediaSession != null) removeSession(mediaSession!!)
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    // This example always accepts the connection request
    override fun onGetSession(
        controllerInfo: MediaSession.ControllerInfo
    ): MediaSession? = mediaSession
}

fuz12 avatar Oct 24 '24 09:10 fuz12

This package is documented quite badly...

It is not mentioned anywhere, and I spent around a week looking here and there. Finally, here is the solution to remove notification: you need to set processing state to idle.

Here is a code sample of my stop method:

  @override
  Future<void> stop() async {
    try {
      await _audioPlayer.stop();
      await _audioPlayer.seek(Duration.zero);
    } on Exception catch (_) {
    }

    // We want to remove notification when the player is stopped
    playbackState.add(
      playbackState.value.copyWith(
        playing: false,
        processingState: AudioProcessingState.idle,
      ),
    );
  }

Once you set your state to idle - the notification will be removed 😉

pro100svitlo avatar Mar 29 '25 13:03 pro100svitlo