Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation about media3 , Android 13 and dismissible foreground notification #211

Closed
pawaom opened this issue Nov 23, 2022 · 14 comments
Closed
Assignees
Labels

Comments

@pawaom
Copy link

pawaom commented Nov 23, 2022

Documentation about media3 , Android 13 and dismissible foreground notification

I recently came across this video about migration to Android 13

https://youtu.be/wBx3-ZObxY8?t=158

The new android 13 will be available soon and the new feature that it has is dismissible foreground notification , this is a critical feature for background music player apps . Although it has been mentioned the mediasession notifications are exempt from the new notification permissions there are few doubts that we have

  1. will the notification be dismissible even when the song is being played
  2. how will it be handled when the song is stopped
  3. few other which we came across similar to these

#205
#175

A proper documentation will be helpful

PS: the primary objective of foreground service was to indicate a service is running, with the help of notification as mentioned here

https://developer.android.com/guide/components/foreground-services

Foreground services perform operations that are noticeable to the user.

Foreground services show a [status bar notification] so that users are actively aware that your app is performing a task in the foreground and is consuming system resources

so if the notification is dismissible how different does it become from a background service and if the service is "tied" to the notification how do we handle the closing of our service and the app, (should we use some thing similar to this )
https://stackoverflow.com/questions/6330200/how-to-quit-android-application-programmatically

or any other standard that android will prescribe.

@marcbaechinger
Copy link
Contributor

If you are using MediaSessionService or MediaLibraryService from the Media3 library in its default configuration, then the service always creates a media session and posts a media style notification either by using startForeground(int, Notification) (when playing) or an ongoing notification with the NotificationManagerCompat (when paused).

The idea of the Media3 library is to smooth out the differences regarding API versions. So strictly speaking it should just work for you in the default configuration and you don't need to care about API levels yourself. If it doesn't work then please file a bug.

So a first step is using Media3 on an Android 13 device and see how this works. You can use the session demo app for this if you haven't migrated your app to Media3 yet.

has is dismissible foreground notification , this is a critical feature for background music player apps

Like you say, there is a mini player for each active media session. The mini player can't be dismissed.

will the notification be dismissible even when the song is being played

The mini player can't be dismissed. It is removed when you remove the notification with the session attached to it.

how will it be handled when the song is stopped

When playback is paused, the service is taken off the foreground and a notification is posted with the notification manager. The mini player is still not dismissible

so if the notification is dismissible how different does it become from a background service

There is a mini player that is not dismissible.

how do we handle the closing of our service

Not sure I understand and how this is different on Android 13 but you can call for instance stopSelf() to close the service. Given you have overriden onDestroy() and clean all your resources there, the player is stopped and released which makes the media session remove the notification by default. This also removes the mini player.

and the app

You can override service.onTaskRemoved(intent).

@galacticappster04
Copy link

When playback is paused, the service is taken off the foreground and a notification is posted with the notification manager. The mini player is still not dismissible

I just kind of give up of restoring the state of my app from dead state with this dangling notification (e.g. When pausing a playback, killing the app, then going to play from notification).

So I decided might be best that if I paused a playback, and when user dismiss the app, it should also kill that notification player.

How do I override this behavior and just dismiss the notification?

@marcbaechinger
Copy link
Contributor

How are these two use cases related?

Even if you do not implement playback resumption from BT you can keep your app playing in the background when the users exits the app.

I'm not trying to convince you for one or the other UX option. My point is that stopping the service when the user dismisses the app is not related to support playback resumption from BT..

From the PlaybackService of the demo app:

  override fun onTaskRemoved(rootIntent: Intent?) {
    if (!player.playWhenReady) {
      stopSelf()
    }
  }

Please also pay some attention to this comment.

@pramod-knidal
Copy link

@marcbaechinger – We aim to provide the following UX for our users:

  • Only dismissable notifications should be shown. When a user explicitly dismisses the notification, the playback should be paused or stopped.
  • If the user kills the app from recent apps, all notifications should be removed.

Currently, I'm facing challenges with achieving the first requirement. Based on your comments and observations of the app's behavior, it seems that achieving this may not be possible. I'm curious to understand the impact of setting setOngoing(false) and why it doesn't make the notification dismissable. Is there any way to create a non-ongoing notification for media3 library users without custom implementation?

Regarding the solution you provided with stopSelf() and mediaSession.release(), it helped me achieve the second requirement. However, if the app is reopened immediately (within 5 seconds of being killed), the app fails to play the media. I'm encountering errors stating "mediaController is not connected," which suggests that the previous mediaController instance remains in memory after killing the app. Would you recommend having a single mediaController per media item or a single mediaController per app? The latter approach has been working fine until now. We are experiencing these issues after updating the targetSdk.

@pramod-knidal
Copy link

Upon conducting further testing, I have discovered that the "mediaController is not connected" issue occurs when the back button is pressed repeatedly to close all activities. It appears that in such cases, the onTaskRemoved method of the service is invoked, while the application remains in the memory.

@marcbaechinger
Copy link
Contributor

Based on your comments and observations of the app's behavior, it seems that achieving this may not be possible.

This is based on a foreground service requiring a notification when running in the foreground.

@pramod-knidal
Copy link

Based on your comments and observations of the app's behavior, it seems that achieving this may not be possible.

This is based on a foreground service requiring a notification when running in the foreground.

Shouldn't users have the ability to dismiss the notification and forcefully terminate the app from which the notification originated?

I believe that displaying persistent notifications may result in users uninstalling our app and could have a negative impact on our brand reputation. Is there a version of MediaSessionService and MediaLibraryService that can be removed from the foreground when the user exits the app or selectively made foreground?

I wonder how WhatsApp & YouTube do it. Both do it well and they don't show any notification at all.

@marcbaechinger
Copy link
Contributor

Shouldn't users have the ability to dismiss the notification and forcefully terminate the app from which the notification originated?

Not by the library. I agree that an app should be able to do this if it really wants to.

With the current default implementation, I think you can do this by a custom action that is placed on the notification and tears down the service.

you can also override MediaSessioService.onUpdateNotification (see below).

Aside from that: Are you able to get an event from the OS notifying the the swipe gesture when the foeground notification is dismissed on all API levels? As far as I can tell this is not possible on the most recent API levels. If you find a system API that we can use, then it would make sense that we expose this API to apps. I think there isn't such an API that is available on all API levels though. If you can educate me about such an API being available, we can make this an enhancement to expose the API so you would be able to destroy the service yourself. I don't think we will implement the behavior described by you as a default.

I wonder how WhatsApp & YouTube do it.

These apps always have an activity in the foreground when they are playing.

The default implementation of MediaSessionService is currently mainly for background playback which requires the most difficult logic. We are aware that there are use cases that do not involve background playback that we do not cover yet.

However, if you have an app that doesn't do background playback then you can solve this without a foreground service but instead always be bound to the service. That's a valid approach and should be comparably easy to implement. If you want to do this, then you can override MediaSessioService.onUpdateNotification and implement your own logic that never puts the service into foreground and hence never posts a notification.

@pramod-knidal
Copy link

Thank you, @marcbaechinger, for providing a prompt and detailed explanation. I appreciate your suggestion to try overriding the onUpdateNotification() method, and I will give it a try.

@pramod-knidal
Copy link

@marcbaechinger, Isn't deleteIntent meant to be used for handling notification dismissals? Looks like it is available since API 1. Am I missing something?

https://developer.android.com/reference/android/app/Notification#deleteIntent

@JureSencar
Copy link

Another related issue is the race condition between the onUnbind() (in my case ~ 100 ms after mediaBrowser.release()) and onTaskRemoved(). If the onTaskRemoved() comes first and it calls stopSelf() then the onUnbind() never gets called and I'm stuck with a disconnected notification. Then I need to click on the notification to launch the app that restores the service.
A working workaround is to track the number of bound clients and call stopSelf() when the last one is unbound in case the onTaskRemoved() was called beforehand.

@marcbaechinger
Copy link
Contributor

Just as a disclaimer: With the current version of the library, the app is responsible to terminate the service once playback has been started. We are thinking about stopping the service from library side for future version, but for now, the app needs to decide whether to stop the service in onTaskRemoved or not.

There are two approaches of behavior in onTaskRemove:

Approach 1: Keep playing in the background when playing

  1. in onTaskRemoved, call stopSelf when session.player.getPlayWhenReady() == false (when not playing).
  2. Release session/player in onDestroy() that is called when stopSelf() is called when the service is not in the foreground.

Approach 2: Stop playback when app terminates

  1. Release session/player in onTaskRemoved
  2. Then, still in onTaskRemove(), call stopSelf to stop the service.

If the onTaskRemoved() comes first and it calls stopSelf() then the onUnbind() never gets called and I'm stuck with a disconnected notification.

That's interesting. Can you explain the relationship between onUnbind() not being called and a disconnected notification and on what API level you are seeing this?

In my mental model the notification is related to the player state rather than calls to onUnbind. This is specifically the case because onUnbind only is called in the context of a controller/browser connecting against a service. This is not necessarily the case. While counting calls to onUnbind/onUnbind tells how many clients have bound to the service, this is not the same as number of controller having connected to the session.

A better metric would probably be session.getConnectedControllers().size(), although I think it shouldn't be required.

My understanding is that in any case you can do a session.release()/player.release() in onTaskRemove() in case you want to stop the service there.

A working workaround is to track the number of bound clients and call stopSelf() when the last one
is unbound in case the onTaskRemoved() was called beforehand.

I can imagine this being true for some use cases on some API levels. I don't think this holds as a general statement though.

For instance, when an app wants to continue playback in the background on an API level below 29, then I think the situation that we want to keep the service running even when no controller is connected against the session/service is quite common. If we would stop the service in this situation, the user wouldn't be happy because we stop the playback when the last controller disconnect, which is for instance when the app goes into the background and releases the controller.

@JureSencar
Copy link

JureSencar commented Aug 22, 2023

Hi, thanks for response. I use Pixel 6a with Android 13.

Of course, I only try to stop the service and release the player if playWhenReady == false and onTaskRemoved() is called.

If onTaskRemoved() that calls stopSelf() is called before the last client manages to unbind then we get into state where notification is still present but it's broken. Usually this happens if there are multiple media sessions live, otherwise the onUnbind() outspeeds the onTaskRemoved() and everything is fine.

One way to experience this broken notification is to move releaseBrowser() from onStop() to onDestroy() in MainActivity of media-session demo app. It is quite flaky. The broken notification is in state where there is metadata, there is no timeline and play button gets stuck in playing mode and no audio is playing.

@marcbaechinger
Copy link
Contributor

We have updated the guide pages on developer.android.com. Specifically these three pages are extended and adjusted to the newest release:

I'm closing this issue which is about documentation.

@JureSencar If your case still is a problem, can you please file a new issue specifically around your case?

@androidx androidx locked and limited conversation to collaborators Jan 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants