Add isPlaybackOngoing and stopMediaSessionService
This API additions help an app to implement the lifecycle of a MediaSessionService properly and in consistency with the `MediaSessionService` being in the foreground or not. Not properly implementing `onTaskRemoved` is the main reason for crashes and confusion. This change provides `MediaSessionService` with a default implementation that avoids crashes of the service. This default implementation uses the new API provided with this change just as an app can do. Issue: androidx/media#1219 PiperOrigin-RevId: 621874838
This commit is contained in:
parent
f9ed303bf1
commit
617f9898c3
@ -121,6 +121,15 @@
|
|||||||
* Fix issue where `MediaMetadata` with just non-null `extras` is not
|
* Fix issue where `MediaMetadata` with just non-null `extras` is not
|
||||||
transmitted between media controllers and sessions
|
transmitted between media controllers and sessions
|
||||||
([#1176](https://github.com/androidx/media/issues/1176)).
|
([#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:
|
* UI:
|
||||||
* Fallback to include audio track language name if `Locale` cannot
|
* Fallback to include audio track language name if `Locale` cannot
|
||||||
identify a display name
|
identify a display name
|
||||||
|
@ -19,7 +19,6 @@ import android.Manifest
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
@ -90,13 +89,6 @@ open class DemoPlaybackService : MediaLibraryService() {
|
|||||||
return mediaLibrarySession
|
return mediaLibrarySession
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
||||||
val player = mediaLibrarySession.player
|
|
||||||
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
|
||||||
stopSelf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MediaSession.setSessionActivity
|
// MediaSession.setSessionActivity
|
||||||
// MediaSessionService.clearListener
|
// MediaSessionService.clearListener
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
@ -166,7 +158,7 @@ open class DemoPlaybackService : MediaLibraryService() {
|
|||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
getString(R.string.notification_channel_name),
|
getString(R.string.notification_channel_name),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT,
|
||||||
)
|
)
|
||||||
notificationManagerCompat.createNotificationChannel(channel)
|
notificationManagerCompat.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
@ -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.Assertions.checkStateNotNull;
|
||||||
import static androidx.media3.common.util.Util.postOrRun;
|
import static androidx.media3.common.util.Util.postOrRun;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.ForegroundServiceStartNotAllowedException;
|
import android.app.ForegroundServiceStartNotAllowedException;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@ -469,6 +470,62 @@ public abstract class MediaSessionService extends Service {
|
|||||||
/* connectionHints= */ Bundle.EMPTY);
|
/* 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()}.
|
||||||
|
*
|
||||||
|
* <p>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<MediaSession> sessionList = getSessions();
|
||||||
|
for (int i = 0; i < sessionList.size(); i++) {
|
||||||
|
sessionList.get(i).getPlayer().setPlayWhenReady(false);
|
||||||
|
}
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>Note: The service <a
|
||||||
|
* href="https://developer.android.com/develop/background-work/services/bound-services#Lifecycle">can't
|
||||||
|
* be stopped</a> 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.
|
* Called when the service is no longer used and is being removed.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user