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

After the ~2 minute timeout for background services, the notification is removed #672

Open
svenoaks opened this issue Sep 23, 2023 · 9 comments
Assignees
Labels

Comments

@svenoaks
Copy link

General way to replicate with demo-session:

  1. Play some music.
  2. Navigate to home screen.
  3. Pause music through the notification.
  4. After ~2 minutes, onDestroy() of the PlaybackService is called and the notification is removed.

After that, the user has no way to resume playback besides going back into the app, the notification is removed and app no longer responds to play command from bluetooth headset.

Seeing how other apps like YouTube Music persist the notification and continue to respond to media buttons, is there a general recommended way to mimic this behavior with Media3?

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Sep 24, 2023

It's a design decision to take the service off the foreground when the player is paused. When in this state and the user does not resume playback within a given duration of time, the system will stop the idle service. This is working as intended and helps the system be in a healthy state instead having several services running silently in the background.

After that, the user has no way to resume playback besides going back into the app,

Your app can implement playback resumption. Then the system places a notification that allows the user to press the play button on that notification to restart your service and get back to the app. WIth playback resumption a user can also use the BT headset to resume.

This is how Android designs this for not having various idle services running and giving the user a way to resume playback after an app has terminated.

general recommended way to mimic this behavior with Media3?

You can override MediaSessionService.updateNotification() and handle the entire foreground management on your own.

@svenoaks
Copy link
Author

It's a design decision to take the service off the foreground when the player is paused. When in this state and the user does not resume playback within a given duration of time, the system will stop the idle service. This is working as intended and helps the system be in a healthy state instead having several services running silently in the background.

Couldn't it post the notification without keeping the service alive at all? As you said we could override MediaSessionService.updateNotification(), but I feel like keeping the notification would be what most media playing apps would want by default. At least, that's what most of the apps I use do, and mine did before changing to Media3

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Sep 25, 2023

Couldn't it post the notification without keeping the service alive at all?

Only when the library knows that the app supports restarting without crashing, which is the case when an app ops-in to playback resumption.

Generally when not running, it's unclear what to do with controls like next/previous, seek and the like when the service is not running. Because, when the service gets started when not running, it needs to be started in the foreground which requires the service to start playback. That's only the case for the play button.

So from the library point of view, an app can implement playback resumption to get exactly that, a notification with just a play button. That's the system way and it has been designed like this for a reason.

With Media3 it's two simple steps:

  1. Opt-in to playback resumption by adding androidx.media3.session.MediaButtonReceiver to the manifest.
  2. Implement Callback.onPlaybackResumption().

That's for the reason that the library knows that playback resumption is supported by the app. If that's not the case and some actor tries to do playback resumption, you land on #167. No one wants to be there.

Media3 doesn't post such a notification at the end because there is an API for this that removes uncertainties. Media3 offers the System UI playback resumption with a play button which is easy to implement and designed to work well together with system restrictions regarding starting a foreground service.

that's what most of the apps I use do, and mine did before changing to Media3

And, an app like yours is ultimately free to ignore all I've said here and what the library intents to do.

An app can post it's own notification at any time. You can for instance do this in onDestroy() of your service (you could even reuse DefaultMediaNotificationProvider. Just to store the last notification provided by it and re-post that when the app terminates. As mentioned, it's unclear how you'd respond to the buttons different than play without crashing, but technically this is possible. I think I don't need to explain in detail though that we don't fancy giving too much support in case an app posts such a notification and then runs into problems with service restrictions imposed by the system. :)

@DimitarStoyanoff
Copy link

So from the library point of view, an app can implement playback resumption to get exactly that, a notification with just a play button.

Not sure if this is intended, but this notification doesn't show with the two steps completed with my MediaSessionService. It does show when I use MediaLibraryService instead. Thought I would add this as a note. I read a lot of issue comments and did not find this mentioned anywhere.

@marcbaechinger
Copy link
Contributor

@DimitarStoyanoff Thanks for your comment!

That's correct. System UI uses onGetRoot() and onGetChildren() of the MediaLibraryService (or equivalent methods in the legacy MediaBrowserServiceCompat) to ask whether to post the resumption notification. The MediaSessionService does not have these domain methods and hence does not support this notification.

I will make this clear in the documentation.

@marcbaechinger
Copy link
Contributor

I updated the docs and added a note in the 'Playback resumption' section of the DAC page about 'Background playback with a MediaSessionService'. The note says that the playback resumption notification is only available for a 'MediaLibraryService`.

@DimitarStoyanoff
Copy link

Thank you, @marcbaechinger, for your quick responses and for all the work you put into this project.

@svenoaks
Copy link
Author

svenoaks commented Dec 28, 2023

Since I still have this problem even after implementing playback resumption with a MediaSessionService, I'm wondering why the library requires a MediaLibraryService and not just a MediaSessionService to display a notification with a play button, since playback resumption callback already requires a playlist to play for the play button.

Is it possible to make this happen the notification also appear with MediaSessionService, and if not what is the minimum amount needed to implement in MediaLibraryService to make this happen.

Also why does MediaSessionService with playback resumption implemented show the notification with play button on API 30, but not API 34? (haven't check the exact API range where it works / doesn't work). For example here's a screenshot of the working notification on API 30 emulator, it also works on a Samsung A50 with Android 11:
Screenshot_20231228_110234

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Dec 29, 2023

Thanks for your report!

Since I still have this problem even after implementing playback resumption

As mentioned in a comment above, the notification is removed when the system destroys the service after about 2 minutes of not being in the foreground which is when the player is paused. This is working as intended.

Is it possible to make this happen the notification also appear with MediaSessionService
and if not what is the minimum amount needed to implement in MediaLibraryService to make this happen.

Under the hood, MediaLibraryService offers the legacy API of the MediaBrowserService that System UI uses to detect whether an app wants to show a resumption notification.

From your report I assume that you are advertising your MediaSessionService in the manifest as a MediaSessionService and MediaBrowserService:

    <service
        android:name=".PlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
      </intent-filter>
    </service>

I just noticed that the demo app is doing this as well. While this apparently works, I think it confusing to do so in the demo app. I'll send a CL to change this from MediaSessionService to MediaLibraryService.

Regarding your question, I guess that you are having a service that extends from MediaSessionService. Then as above the manifest declares the MediaSessionService and MediaBrowserService. Please correct me if I'm wrong.

I think you kind of discovered a loop-hole in the implementation or, in a more positive way, you discovered the feature that we actually can offer a resumption notification for a MediaSessionService as well. Thank you! :)

Lemme explain: System UI picks the MediaBrowserSerrvice declaration in your manifest and Media3 returns your MediaSessionService. System UI then tries the legacy service contract to figure out whether we want a resumption notification. Because playback resumption is enabled, Media3 responds to System UI under the hood, even if the service doesn't have the MediaLibrarySession.Callback implemented properly (which is correct for a MediaSessionService). Media3 does this here and here. Like you mention above, there is actually no need to implement MediaLibrarySession.Callback.onGetLibraryRoot() and MediaLibrarySession.Callback.onGetChildren(). Media3 responds to the calls from System UI that gets all the information about what to display in the resumption notification from the metadata of MediaSessionCompat which is provided for the library and the session service in the same way.

While this is unexpected and confusing, I think we should keep this behaviour because it can easily be removed from a MediaSessionService by not declaring MediaBrowserService in the manifest. Unlike MediaLibraryService where removing the declaration would mean that for instance Android Auto strops working, for a MediaSessionService this works to prevent System UI doing a resumption notification by still allowing resumption over for instance Bluetooth.

So for this purpose, I think you can either remove MediaBrowserService from the manifest if you wish the MediaSessionService doesn't have the System UI resumption notification. I understand though you would actually want this to happen, so as you discovered this works.

MediaSessionService with playback resumption implemented show the notification with play button on API 30, but not API 34?

Guess the first part is explained above. I'm not sure why it doesn`t work on 34 without further information.

Can you repro this with the demo app?

I think since API 33 there is need for some additional permission regarding notifications that may have an impact. I assume you got this already as else you would possibly see some warnings from the system. The demo app has this here in the manifest and here in the MainActivivty as well as here.

If you can repro that the notification is not shown with the notification permission in place, can you please make a bug report from your app that is made just after you started playback (aka when the first playback notification is displayed by System UI). You can also filter logcat for MediaResumeListener (when looking at all logs, not only mine). The interesting bit is whether you see

2023-12-29 12:54:23.247   877-877   MediaResumeListener     com.android.systemui                 D  Checking for service component for androidx.media3.demo.session
2023-12-29 12:54:23.252   877-958   MediaResumeListener     com.android.systemui                 D  Testing if we can connect to ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}
2023-12-29 12:54:23.762   877-958   MediaResumeListener     com.android.systemui                 E  Cannot resume with ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}

or

2023-12-29 12:52:14.017   877-877   MediaResumeListener     com.android.systemui                 D  Checking for service component for androidx.media3.demo.session
2023-12-29 12:52:14.018   877-958   MediaResumeListener     com.android.systemui                 D  Testing if we can connect to ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}
2023-12-29 12:52:14.518   877-958   MediaResumeListener     com.android.systemui                 D  Connected to ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}
2023-12-29 12:52:14.578   877-958   MediaResumeListener     com.android.systemui                 D  Can get resumable media for 0 from ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}

copybara-service bot pushed a commit that referenced this issue Jan 3, 2024
The `PlaybackService` of the demo app is declared
as `androidx.media3.session.MediaSessionService` instead
of `androidx.media3.session.MediaLibraryService`. While
this technically works, its confusing to do that in the
demo app.

Generally, apps that declare the legacy
`android.media.browse.MediaBrowserService` should also
declare `androidx.media3.session.MediaLibraryService`
and the demo app should reflect this common case.

Issue: #672
PiperOrigin-RevId: 595320994
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants