Is your feature request related to a problem? Please describe.
On Android, when the app is closed (or crashes) while the audio is playing, the user may expect that the audio stops immediately.
In the latest version of the plugin, the AudioServiceBackground keeps running and the user needs to tap the stop button in the notification bar in order to make it happen.
Describe the solution you'd like
It would be nice if the start method had a flag such as persist: false to control the Isolate behavior in those conditions (app killed, closed or crashed).
Does Android offer this kind of control over the app states?
There are various ways to detect when an activity is destroyed (e.g. pressing the back button from the root route) and when a task is swiped up from the task manager using platform-side APIs. I'm assuming this is what you mean by closing the app?
Since the plugin was designed to continue running in the background, the current behaviour is as intended, as backing out of the activity is a common action when the user wants to multitask while continuing to listen to the audio in your app, but it has the side effect of destroying the activity. Here, even though the activity is destroyed, we want the audio to continue, and this is how mp3 players tend to work. When swiping up a task in the task manager, it is also the usual behaviour in mp3 players to just destroy the activity but still have the music playing in the background. However, in this case I can conceive of it being useful to have an option for this behaviour. So these two different ways of "closing the app" might need to have different options.
For crashing, I don't know if there is a reliable way to detect that, and you might want to address it by putting more try/catch blocks in your app.
But otherwise, this is a feature I could consider adding.
Yes, you're correct: crashing should not be listed here in the requirements and should be handled properly in the app logic.
Back to the feature request, a few comments:
I would add as another argument for the persistent: true|false flag: the UX consistency.
Android and iOS differ on how the audio is handled when the app is killed. Android keeps the background task running, while iOS kills it instantly.
Another point is that some (if not most) of the users are currently familiar with non-persistent audio players when killing the app using the Task Manager. Think about how Spotify and YouTube (premium) handle this situation. I'd agree that it is not necessarily a requirement for every project, but it is a pretty common scenario.
As a user, I would uninstall and downvote any app that keeps playing after I closed it.
@vinicius-animo sounds reasonable.
@volgin To my knowledge, audio_service implements the most common, expected behaviour for music players on iOS and Android respectively, so so my primary consideration would be to ensure that those standard behaviours continue to be possible (currently they're the only supported way), and then consider everything else as options.
@ryanheise Apple Music, Spotify and Amazon Prime Music stop playing immediately after an app is closed. I am not familiar with any music player that continues to play after it was closed. I am not sure if I understood you correctly, but this _is_ the most common, expected behavior.
@volgin iOS and Android do have different historical behaviours here, so what you describe as the most common, expected behaviour might be true on iOS, but historically it has not been true on Android where even today, Google's official Android music player "Google Play Music" continues to play music in the background after you destroy the activity or swiping away the task. So swiping away the task is really just a way to free up memory taken up by the activity. If you wanted to actually stop the music, you should instead press the "stop" button or the x button in the notification. This is common behaviour and dates back to the oldest versions of Android where the mechanism Android provided to allow apps to play music in the background was to create a separate background component called a "service" which was designed to continue running even after the UI component (the "activity") was destroyed. It was even advised in the developer documentation that this is how you should build a music player app, and the task APIs that would make it possible to emulate iOS behaviour weren't available until much later.
I would say this behaviour is still prevalent on Android today, particularly in music and podcast players where the background service just plays audio and can be fully detached from the user interface. But Android is rather lax on standards and doesn't really enforce U/X design guidelines in the same way Apple does, so there is also a bit of variety on Android, and that is why an option was being considered above. Such an option would give developers the freedom to make their own U/X choices, and even to be able to emulate the iOS behaviour on Android if they wanted to, or to go with the historical behaviours for the respective platforms. I believe the plugin's current behaviour does follow the expected behaviour on iOS, although please let me know if that is not the case. For the Android side, the option to emulate the iOS behaviour is actively being considered in this issue.
@vinicius-animo Regarding the proposed option(s) for Android, just to clarify what you mean by closing the app, this could mean two things:
I am inclined to only add an option for (2) for now because I don't see a compelling use case for an option for (1). On Android, if you back out of the main activity and the activity is destroyed, but then the service is also destroyed, I think the user would be quite annoyed if it were a music player. For (2), I can see both ways. If the user swipes away Google Play Music in the task manager, it frees up memory taken by the Google Play Music app user interface, but to stop the music you would just press the stop button. If you swipe away Spotify in the task manager, it frees up the UI memory but also stops the music. At least this should be provided as an option to the developer. But do you have a compelling use case for making an option for (1) or would you be satisfied to just have an option for (2)?
@ryanheise Thank you for pointing out Google Play Music - I never tried it before.
I think that app developers already have control over the back button behavior, and can close/stop the background service in their app logic, if they choose so.
Any updates on this issue?
To me, I think what @ryanheise mentioned as the second option would satisfy my needs as a developer.
I also tried to implement it with the existing API:
I have tried to add android:stopWithtask="true" inside the audio service xml tag in AndroidManifest.
The behaviour this produces is: After swiping the app from the task manager away, the service notification dismissed, however the audio is still playing because no information is given to flutter when the service is destroyed.
I think we can add the persist flag, and send stop action to flutter when persist == true in onTaskRemoved?
I might be able to do a PR to implement the feature. Is this still a wanted feature?
I also tried to implement it with the existing API:
I have tried to addandroid:stopWithtask="true"inside the audio service xml tag inAndroidManifest.
The behaviour this produces is: After swiping the app from the task manager away, the service notification dismissed, however the audio is still playing because no information is given to flutter when the service is destroyed.
I think we can add thepersistflag, and send stop action to flutter whenpersist == trueinonTaskRemoved?
That's correct, onTaskRemoved is the callback to handle swiping away the task.
I might be able to do a PR to implement the feature. Is this still a wanted feature?
Definitely, I think it makes sense to give users a choice for the Android behaviour. Pull requests are certainly welcome since I have quite a few high priority issues to work on first before I would get around to this one.
Since this is an Android-specific option, I would name the parameter something like androidStopOnRemoveTask with a default value of false (which is the default behaviour on Android). Looking at the current implementation of onTaskRemoved I am a bit suspicious about whether I handled the existing case correctly:
@Override
public void onTaskRemoved(Intent rootIntent) {
MediaControllerCompat controller = mediaSession.getController();
if (androidStopForegroundOnPause && controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PAUSED) {
stopSelf();
}
super.onTaskRemoved(rootIntent);
}
Basically that existing code is so that if the user wants it, the service will be shut down when swiping it away "while" in pause mode, which is another behaviour that some users want. However, simply calling stopSelf doesn't actually shut down the isolate and I don't think people noticed it simply because the audio was already paused. What really needs to happen to cause the isolate to shut down is this:
listener.onStop();
This will send a message to the isolate to gracefully stop.
@ryanheise In that case I will fix the old case as well. Does the following code make sense to you?
@Override
public void onTaskRemoved(Intent rootIntent) {
MediaControllerCompat controller = mediaSession.getController();
if (androidStopOnRemoveTask || (androidStopForegroundOnPause && controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PAUSED)) {
listener.onStop();
}
super.onTaskRemoved(rootIntent);
}
EDIT: stopSelf() turns out to not be required, since after notifying client with onStop(), the onStart method should complete and the rest will be handled by the stopped callback
Looks good to me. Yes, sorry I should have clarified in my previous comment that after listener.onStop(); triggers a graceful shutdown, that should eventually result in the service being stopped. But the true test is after trying out. If it works for you, I'd be happy to integrate the PR.
PR is ready to merge: https://github.com/ryanheise/audio_service/pull/165
Thanks, @Camerash ! I've merged your PR. @vinicius-animo and @volgin you can test this on the master branch, and turn on the feature by passing in androidStopOnRemoveTask: true into AudioService.start().
Works perfectly! Thanks @Camerash and @ryanheise.
Tomorrow I'll test the stability on iOS and post an update here.
Btw, I had to replace audioplayers with just_audio after doing some research and bumping into this issue.
Also to be tested on iOS. But so far, great plugin, @ryanheise :)
Update on iOS: works perfectly.
Thanks @vinicius-animo , glad to hear it!
Good life, fellows!!!
Thanks!!
How do I add this to my code?
I am using audio_service: ^0.7.1 and just_audio: ^0.1.4.
Great work!
But it doesn't stop playing after the user swipe up and kill the app.
How do I make that?
Thank you!
Just turn on the feature by passing in androidStopOnRemoveTask: true into AudioService.start()
If it is not working, try providing some of your code snippets?
This was removed? I'm using just_audio: ^0.2.2 and audio_service: ^0.11.0 and the only options i have in AudioService.start() with the android prefix are:
androidEnableQueue: ,
androidNotificationChannelName: ',
androidNotificationChannelDescription: ,
androidNotificationColor: ,
androidNotificationIcon: '',
androidStopForegroundOnPause: , (tried this but with no succes)
androidArtDownscaleSize: ,
androidNotificationClickStartsActivity: ,
androidNotificationOngoing: ,
androidResumeOnClick: ,
I tried to use the WidgetsBindingObserver and on the AppLifecycleState.detached: i called AudioService.stop(); but it dosent work too, it just leak.
Well, to anyone wondering, i just got it working:
On your BackgroundAudioTask simply put:
@override
void onTaskRemoved() {
// TODO: implement onTaskRemoved
onStop();
super.onTaskRemoved();
}
Its not needed anymore to use androidStopOnRemoveTask: true.
When I swipe away the task in the task manager on Android, it destroys the activity and the service continues to run independently:
I code:
AudioService.customEventStream.listen((event) async {
switch (event){
case "stop":
await audioPlayer.stop(); // not working
_playing = false;
break;
}
});
@override
Future
AudioServiceBackground.sendCustomEvent("stop");
await super.onStop();
}
void onTaskRemoved() {
onStop();
super.onTaskRemoved();
}
But onstop() only stops the service, not the audio player. Help me fix!!!
From the documentation for onStop:
You should implement this method to stop playing audio and dispose of any resources used.
But Audio Player is in Flutter UI, I stop audio:
AudioService.customEventStream.listen((event) async {
switch (event){
case "stop":
await audioPlayer.stop(); // not working
_playing = false;
break;
}
});
audioPlayer.stop() not working: because flutter UI is destroys?
Please show me how to stop audio player!
So you're not using this plugin in the way it was intended? From the README:
How does this plugin work?
You encapsulate your audio code in a background task which runs in a special isolate that continues to run when your UI is absent.
Please read the documentation in both instances to make sure you are using the plugin correctly.
Most helpful comment
Well, to anyone wondering, i just got it working:
On your
BackgroundAudioTasksimply put:Its not needed anymore to use
androidStopOnRemoveTask: true.