diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b37dcdc5c3..32e79a970b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -121,6 +121,15 @@ * Fix issue where `MediaMetadata` with just non-null `extras` is not transmitted between media controllers and sessions ([#1176](https://github.com/androidx/media/issues/1176)). + * Add `MediaSessionService.isPlaybackOngoing()` to let apps query whether + the service needs to be stopped in `onTaskRemoved()` + ([#1219](https://github.com/androidx/media/issues/1219)). + * Add `MediaSessionService.pauseAllPlayersAndStopSelf()` that conveniently + allows to pause playback of all sessions and call `stopSelf` to + terminate the lifecyce of the `MediaSessionService`. + * Override `MediaSessionService.onTaskRemoved(Intent)` to provide a safe + default implementation that keeps the service running in the foreground + if playback is ongoing or stops the service otherwise. * UI: * Fallback to include audio track language name if `Locale` cannot identify a display name diff --git a/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt index 3bd5440bdd..f34d864658 100644 --- a/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt +++ b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoPlaybackService.kt @@ -19,7 +19,6 @@ import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent -import android.content.Intent import android.content.pm.PackageManager import android.os.Build import androidx.annotation.OptIn @@ -90,13 +89,6 @@ open class DemoPlaybackService : MediaLibraryService() { return mediaLibrarySession } - override fun onTaskRemoved(rootIntent: Intent?) { - val player = mediaLibrarySession.player - if (!player.playWhenReady || player.mediaItemCount == 0) { - stopSelf() - } - } - // MediaSession.setSessionActivity // MediaSessionService.clearListener @OptIn(UnstableApi::class) @@ -166,7 +158,7 @@ open class DemoPlaybackService : MediaLibraryService() { NotificationChannel( CHANNEL_ID, getString(R.string.notification_channel_name), - NotificationManager.IMPORTANCE_DEFAULT + NotificationManager.IMPORTANCE_DEFAULT, ) notificationManagerCompat.createNotificationChannel(channel) } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java index beb6e71e8b..120a5a5501 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java @@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Util.postOrRun; +import android.app.Activity; import android.app.ForegroundServiceStartNotAllowedException; import android.app.Service; import android.content.ComponentName; @@ -469,6 +470,62 @@ public abstract class MediaSessionService extends Service { /* connectionHints= */ Bundle.EMPTY); } + /** + * Returns whether there is a session with ongoing playback that must be paused or stopped before + * being able to terminate the service by calling {@link #stopSelf()}. + */ + @UnstableApi + public boolean isPlaybackOngoing() { + return getMediaNotificationManager().isStartedInForeground(); + } + + /** + * Pauses the player of each session managed by the service and calls {@link #stopSelf()}. + * + *
This terminates the service lifecycle and triggers {@link #onDestroy()} that an app can
+ * override to release the sessions and other resources.
+ */
+ @UnstableApi
+ public void pauseAllPlayersAndStopSelf() {
+ List If {@linkplain #isPlaybackOngoing() playback is ongoing}, the service continues running in
+ * the foreground when the app is dismissed from the recent apps. Otherwise, the service is
+ * stopped by calling {@link #stopSelf()} which terminates the service lifecycle and triggers
+ * {@link #onDestroy()} that an app can override to release the sessions and other resources.
+ *
+ * An app can safely override this method without calling super to implement a different
+ * behaviour, for instance unconditionally calling {@link #pauseAllPlayersAndStopSelf()} to stop
+ * the service even when playing. However, if {@linkplain #isPlaybackOngoing() playback is not
+ * ongoing}, the service must be terminated otherwise the service will be crashed and restarted by
+ * the system.
+ *
+ * Note: The service can't
+ * be stopped until all media controllers have been unbound. Hence, an app needs to release
+ * all internal controllers that have connected to the service (for instance from an activity in
+ * {@link Activity#onStop()}). If an app allows external apps to connect a {@link MediaController}
+ * to the service, these controllers also need to be disconnected. In such a scenario of external
+ * bound clients, an app needs to override this method to release the session before calling
+ * {@link #stopSelf()}.
+ */
+ @Override
+ public void onTaskRemoved(@Nullable Intent rootIntent) {
+ if (!isPlaybackOngoing()) {
+ // The service needs to be stopped when playback is not ongoing and the service is not in the
+ // foreground.
+ stopSelf();
+ }
+ }
+
/**
* Called when the service is no longer used and is being removed.
*