Catch FgSStartNotAllowedException when playback resumes

This fix applies to Android 12 and above.

In this fix, the `MediaSessionService` will try to start in the foreground before the session playback resumes, if ForegroundServiceStartNotAllowedException is thrown, then the app can handle the exception with their customized implementation of MediaSessionService.Listener.onForegroundServiceStartNotAllowedException. If no exception thrown, the a media notification corresponding to paused state will be sent as the consequence of successfully starting in the foreground. And when the player actually resumes, another media notification corresponding to playing state will be sent.

PiperOrigin-RevId: 501803930
This commit is contained in:
tianyifeng 2023-01-13 11:25:35 +00:00 committed by Rohit Singh
parent d1e03a41ab
commit 0d0cd78626
6 changed files with 277 additions and 47 deletions

View File

@ -66,6 +66,7 @@ import java.util.concurrent.TimeoutException;
private int totalNotificationCount; private int totalNotificationCount;
@Nullable private MediaNotification mediaNotification; @Nullable private MediaNotification mediaNotification;
private boolean startedInForeground;
public MediaNotificationManager( public MediaNotificationManager(
MediaSessionService mediaSessionService, MediaSessionService mediaSessionService,
@ -80,6 +81,7 @@ import java.util.concurrent.TimeoutException;
startSelfIntent = new Intent(mediaSessionService, mediaSessionService.getClass()); startSelfIntent = new Intent(mediaSessionService, mediaSessionService.getClass());
controllerMap = new HashMap<>(); controllerMap = new HashMap<>();
customLayoutMap = new HashMap<>(); customLayoutMap = new HashMap<>();
startedInForeground = false;
} }
public void addSession(MediaSession session) { public void addSession(MediaSession session) {
@ -163,9 +165,14 @@ import java.util.concurrent.TimeoutException;
} }
} }
public void updateNotification(MediaSession session) { /**
if (!mediaSessionService.isSessionAdded(session) * Updates the notification.
|| !shouldShowNotification(session.getPlayer())) { *
* @param session A session that needs notification update.
* @param startInForegroundRequired Whether the service is required to start in the foreground.
*/
public void updateNotification(MediaSession session, boolean startInForegroundRequired) {
if (!mediaSessionService.isSessionAdded(session) || !shouldShowNotification(session)) {
maybeStopForegroundService(/* removeNotifications= */ true); maybeStopForegroundService(/* removeNotifications= */ true);
return; return;
} }
@ -179,18 +186,27 @@ import java.util.concurrent.TimeoutException;
MediaNotification mediaNotification = MediaNotification mediaNotification =
this.mediaNotificationProvider.createNotification( this.mediaNotificationProvider.createNotification(
session, checkStateNotNull(customLayoutMap.get(session)), actionFactory, callback); session, checkStateNotNull(customLayoutMap.get(session)), actionFactory, callback);
updateNotificationInternal(session, mediaNotification); updateNotificationInternal(session, mediaNotification, startInForegroundRequired);
}
public boolean isStartedInForeground() {
return startedInForeground;
} }
private void onNotificationUpdated( private void onNotificationUpdated(
int notificationSequence, MediaSession session, MediaNotification mediaNotification) { int notificationSequence, MediaSession session, MediaNotification mediaNotification) {
if (notificationSequence == totalNotificationCount) { if (notificationSequence == totalNotificationCount) {
updateNotificationInternal(session, mediaNotification); boolean startInForegroundRequired =
MediaSessionService.shouldRunInForeground(
session, /* startInForegroundWhenPaused= */ false);
updateNotificationInternal(session, mediaNotification, startInForegroundRequired);
} }
} }
private void updateNotificationInternal( private void updateNotificationInternal(
MediaSession session, MediaNotification mediaNotification) { MediaSession session,
MediaNotification mediaNotification,
boolean startInForegroundRequired) {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// Call Notification.MediaStyle#setMediaSession() indirectly. // Call Notification.MediaStyle#setMediaSession() indirectly.
android.media.session.MediaSession.Token fwkToken = android.media.session.MediaSession.Token fwkToken =
@ -199,17 +215,9 @@ import java.util.concurrent.TimeoutException;
mediaNotification.notification.extras.putParcelable( mediaNotification.notification.extras.putParcelable(
Notification.EXTRA_MEDIA_SESSION, fwkToken); Notification.EXTRA_MEDIA_SESSION, fwkToken);
} }
this.mediaNotification = mediaNotification; this.mediaNotification = mediaNotification;
Player player = session.getPlayer(); if (startInForegroundRequired) {
if (shouldRunInForeground(player)) { startForeground(mediaNotification);
ContextCompat.startForegroundService(mediaSessionService, startSelfIntent);
if (Util.SDK_INT >= 29) {
Api29.startForeground(mediaSessionService, mediaNotification);
} else {
mediaSessionService.startForeground(
mediaNotification.notificationId, mediaNotification.notification);
}
} else { } else {
maybeStopForegroundService(/* removeNotifications= */ false); maybeStopForegroundService(/* removeNotifications= */ false);
notificationManagerCompat.notify( notificationManagerCompat.notify(
@ -226,19 +234,12 @@ import java.util.concurrent.TimeoutException;
private void maybeStopForegroundService(boolean removeNotifications) { private void maybeStopForegroundService(boolean removeNotifications) {
List<MediaSession> sessions = mediaSessionService.getSessions(); List<MediaSession> sessions = mediaSessionService.getSessions();
for (int i = 0; i < sessions.size(); i++) { for (int i = 0; i < sessions.size(); i++) {
if (shouldRunInForeground(sessions.get(i).getPlayer())) { if (MediaSessionService.shouldRunInForeground(
sessions.get(i), /* startInForegroundWhenPaused= */ false)) {
return; return;
} }
} }
// To hide the notification on all API levels, we need to call both Service.stopForeground(true) stopForeground(removeNotifications);
// and notificationManagerCompat.cancel(notificationId).
if (Util.SDK_INT >= 24) {
Api24.stopForeground(mediaSessionService, removeNotifications);
} else {
// For pre-L devices, we must call Service.stopForeground(true) anyway as a workaround
// that prevents the media notification from being undismissable.
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
}
if (removeNotifications && mediaNotification != null) { if (removeNotifications && mediaNotification != null) {
notificationManagerCompat.cancel(mediaNotification.notificationId); notificationManagerCompat.cancel(mediaNotification.notificationId);
// Update the notification count so that if a pending notification callback arrives (e.g., a // Update the notification count so that if a pending notification callback arrives (e.g., a
@ -248,16 +249,11 @@ import java.util.concurrent.TimeoutException;
} }
} }
private static boolean shouldShowNotification(Player player) { private static boolean shouldShowNotification(MediaSession session) {
Player player = session.getPlayer();
return !player.getCurrentTimeline().isEmpty() && player.getPlaybackState() != Player.STATE_IDLE; return !player.getCurrentTimeline().isEmpty() && player.getPlaybackState() != Player.STATE_IDLE;
} }
private static boolean shouldRunInForeground(Player player) {
return player.getPlayWhenReady()
&& (player.getPlaybackState() == Player.STATE_READY
|| player.getPlaybackState() == Player.STATE_BUFFERING);
}
private static final class MediaControllerListener private static final class MediaControllerListener
implements MediaController.Listener, Player.Listener { implements MediaController.Listener, Player.Listener {
private final MediaSessionService mediaSessionService; private final MediaSessionService mediaSessionService;
@ -274,8 +270,9 @@ import java.util.concurrent.TimeoutException;
} }
public void onConnected() { public void onConnected() {
if (shouldShowNotification(session.getPlayer())) { if (shouldShowNotification(session)) {
mediaSessionService.onUpdateNotification(session); mediaSessionService.onUpdateNotificationInternal(
session, /* startInForegroundWhenPaused= */ false);
} }
} }
@ -283,7 +280,8 @@ import java.util.concurrent.TimeoutException;
public ListenableFuture<SessionResult> onSetCustomLayout( public ListenableFuture<SessionResult> onSetCustomLayout(
MediaController controller, List<CommandButton> layout) { MediaController controller, List<CommandButton> layout) {
customLayoutMap.put(session, ImmutableList.copyOf(layout)); customLayoutMap.put(session, ImmutableList.copyOf(layout));
mediaSessionService.onUpdateNotification(session); mediaSessionService.onUpdateNotificationInternal(
session, /* startInForegroundWhenPaused= */ false);
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
} }
@ -296,7 +294,8 @@ import java.util.concurrent.TimeoutException;
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAY_WHEN_READY_CHANGED,
Player.EVENT_MEDIA_METADATA_CHANGED, Player.EVENT_MEDIA_METADATA_CHANGED,
Player.EVENT_TIMELINE_CHANGED)) { Player.EVENT_TIMELINE_CHANGED)) {
mediaSessionService.onUpdateNotification(session); mediaSessionService.onUpdateNotificationInternal(
session, /* startInForegroundWhenPaused= */ false);
} }
} }
@ -304,10 +303,35 @@ import java.util.concurrent.TimeoutException;
public void onDisconnected(MediaController controller) { public void onDisconnected(MediaController controller) {
mediaSessionService.removeSession(session); mediaSessionService.removeSession(session);
// We may need to hide the notification. // We may need to hide the notification.
mediaSessionService.onUpdateNotification(session); mediaSessionService.onUpdateNotificationInternal(
session, /* startInForegroundWhenPaused= */ false);
} }
} }
private void startForeground(MediaNotification mediaNotification) {
ContextCompat.startForegroundService(mediaSessionService, startSelfIntent);
if (Util.SDK_INT >= 29) {
Api29.startForeground(mediaSessionService, mediaNotification);
} else {
mediaSessionService.startForeground(
mediaNotification.notificationId, mediaNotification.notification);
}
startedInForeground = true;
}
private void stopForeground(boolean removeNotifications) {
// To hide the notification on all API levels, we need to call both Service.stopForeground(true)
// and notificationManagerCompat.cancel(notificationId).
if (Util.SDK_INT >= 24) {
Api24.stopForeground(mediaSessionService, removeNotifications);
} else {
// For pre-L devices, we must call Service.stopForeground(true) anyway as a workaround
// that prevents the media notification from being undismissable.
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
}
startedInForeground = false;
}
@RequiresApi(24) @RequiresApi(24)
private static class Api24 { private static class Api24 {

View File

@ -878,10 +878,15 @@ public class MediaSession {
} }
/** Sets the {@linkplain Listener listener}. */ /** Sets the {@linkplain Listener listener}. */
/* package */ void setListener(@Nullable Listener listener) { /* package */ void setListener(Listener listener) {
impl.setMediaSessionListener(listener); impl.setMediaSessionListener(listener);
} }
/** Clears the {@linkplain Listener listener}. */
/* package */ void clearListener() {
impl.clearMediaSessionListener();
}
private Uri getUri() { private Uri getUri() {
return impl.getUri(); return impl.getUri();
} }
@ -1273,6 +1278,15 @@ public class MediaSession {
* @param session The media session for which the notification requires to be refreshed. * @param session The media session for which the notification requires to be refreshed.
*/ */
void onNotificationRefreshRequired(MediaSession session); void onNotificationRefreshRequired(MediaSession session);
/**
* Called when the {@linkplain MediaSession session} receives the play command and requests from
* the listener on whether the media can be played.
*
* @param session The media session which requests if the media can be played.
* @return True if the media can be played, false otherwise.
*/
boolean onPlayRequested(MediaSession session);
} }
/** /**

View File

@ -580,16 +580,27 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
} }
/* package */ void setMediaSessionListener(@Nullable MediaSession.Listener listener) { /* package */ void setMediaSessionListener(MediaSession.Listener listener) {
this.mediaSessionListener = listener; this.mediaSessionListener = listener;
} }
/* package */ void clearMediaSessionListener() {
this.mediaSessionListener = null;
}
/* package */ void onNotificationRefreshRequired() { /* package */ void onNotificationRefreshRequired() {
if (this.mediaSessionListener != null) { if (this.mediaSessionListener != null) {
this.mediaSessionListener.onNotificationRefreshRequired(instance); this.mediaSessionListener.onNotificationRefreshRequired(instance);
} }
} }
/* package */ boolean onPlayRequested() {
if (this.mediaSessionListener != null) {
return this.mediaSessionListener.onPlayRequested(instance);
}
return true;
}
private void dispatchRemoteControllerTaskToLegacyStub(RemoteControllerTask task) { private void dispatchRemoteControllerTaskToLegacyStub(RemoteControllerTask task) {
try { try {
task.run(sessionLegacyStub.getControllerLegacyCbForBroadcast(), /* seq= */ 0); task.run(sessionLegacyStub.getControllerLegacyCbForBroadcast(), /* seq= */ 0);

View File

@ -313,7 +313,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
playerWrapper.seekTo( playerWrapper.seekTo(
playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET); playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET);
} }
playerWrapper.play(); if (sessionImpl.onPlayRequested()) {
playerWrapper.play();
}
}, },
sessionCompat.getCurrentControllerInfo()); sessionCompat.getCurrentControllerInfo());
} }

View File

@ -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.ForegroundServiceStartNotAllowedException;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -32,13 +33,17 @@ import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.DoNotInline;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.collection.ArrayMap; import androidx.collection.ArrayMap;
import androidx.media.MediaBrowserServiceCompat; import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager; import androidx.media.MediaSessionManager;
import androidx.media3.common.Player;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaSession.ControllerInfo; import androidx.media3.session.MediaSession.ControllerInfo;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@ -134,6 +139,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/ */
public abstract class MediaSessionService extends Service { public abstract class MediaSessionService extends Service {
/**
* Listener for {@link MediaSessionService}.
*
* <p>The methods will be called on the main thread.
*/
@UnstableApi
public interface Listener {
/**
* Called when the service fails to start in the foreground and a {@link
* ForegroundServiceStartNotAllowedException} is thrown on Android 12 or later.
*/
@RequiresApi(31)
default void onForegroundServiceStartNotAllowedException() {}
}
/** The action for {@link Intent} filter that must be declared by the service. */ /** The action for {@link Intent} filter that must be declared by the service. */
public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService"; public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
@ -158,11 +178,19 @@ public abstract class MediaSessionService extends Service {
@GuardedBy("lock") @GuardedBy("lock")
private @MonotonicNonNull DefaultActionFactory actionFactory; private @MonotonicNonNull DefaultActionFactory actionFactory;
@GuardedBy("lock")
@Nullable
private Listener listener;
@GuardedBy("lock")
private boolean defaultMethodCalled;
/** Creates a service. */ /** Creates a service. */
public MediaSessionService() { public MediaSessionService() {
lock = new Object(); lock = new Object();
mainHandler = new Handler(Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper());
sessions = new ArrayMap<>(); sessions = new ArrayMap<>();
defaultMethodCalled = false;
} }
/** /**
@ -239,7 +267,7 @@ public abstract class MediaSessionService extends Service {
// TODO(b/191644474): Check whether the session is registered to multiple services. // TODO(b/191644474): Check whether the session is registered to multiple services.
MediaNotificationManager notificationManager = getMediaNotificationManager(); MediaNotificationManager notificationManager = getMediaNotificationManager();
postOrRun(mainHandler, () -> notificationManager.addSession(session)); postOrRun(mainHandler, () -> notificationManager.addSession(session));
session.setListener(this::onUpdateNotification); session.setListener(new MediaSessionListener());
} }
} }
@ -259,7 +287,7 @@ public abstract class MediaSessionService extends Service {
} }
MediaNotificationManager notificationManager = getMediaNotificationManager(); MediaNotificationManager notificationManager = getMediaNotificationManager();
postOrRun(mainHandler, () -> notificationManager.removeSession(session)); postOrRun(mainHandler, () -> notificationManager.removeSession(session));
session.setListener(null); session.clearListener();
} }
/** /**
@ -282,6 +310,22 @@ public abstract class MediaSessionService extends Service {
} }
} }
/** Sets the {@linkplain Listener listener}. */
@UnstableApi
public final void setListener(Listener listener) {
synchronized (lock) {
this.listener = listener;
}
}
/** Clears the {@linkplain Listener listener}. */
@UnstableApi
public final void clearListener() {
synchronized (lock) {
this.listener = null;
}
}
/** /**
* Called when a component is about to bind to the service. * Called when a component is about to bind to the service.
* *
@ -395,8 +439,10 @@ public abstract class MediaSessionService extends Service {
* <p>Override this method to create your own notification and customize the foreground handling * <p>Override this method to create your own notification and customize the foreground handling
* of your service. * of your service.
* *
* <p>The default implementation will present a default notification or the notification provided * <p>At most one of {@link #onUpdateNotification(MediaSession, boolean)} and this method should
* by the {@link MediaNotification.Provider} that is {@link * be overridden. If neither of the two methods is overridden, the default implementation will
* present a default notification or the notification provided by the {@link
* MediaNotification.Provider} that is {@link
* #setMediaNotificationProvider(MediaNotification.Provider) set} by the app. Further, the service * #setMediaNotificationProvider(MediaNotification.Provider) set} by the app. Further, the service
* is started in the <a * is started in the <a
* href="https://developer.android.com/guide/components/foreground-services">foreground</a> when * href="https://developer.android.com/guide/components/foreground-services">foreground</a> when
@ -408,7 +454,42 @@ public abstract class MediaSessionService extends Service {
* @param session A session that needs notification update. * @param session A session that needs notification update.
*/ */
public void onUpdateNotification(MediaSession session) { public void onUpdateNotification(MediaSession session) {
getMediaNotificationManager().updateNotification(session); setDefaultMethodCalled(true);
}
/**
* Called when a notification needs to be updated. Override this method to show or cancel your own
* notifications.
*
* <p>This method is called whenever the service has detected a change that requires to show,
* update or cancel a notification with a flag {@code startInForegroundRequired} suggested by the
* service whether starting in the foreground is required. The method will be called on the
* application thread of the app that the service belongs to.
*
* <p>Override this method to create your own notification and customize the foreground handling
* of your service.
*
* <p>At most one of {@link #onUpdateNotification(MediaSession)} and this method should be
* overridden. If neither of the two methods is overridden, the default implementation will
* present a default notification or the notification provided by the {@link
* MediaNotification.Provider} that is {@link
* #setMediaNotificationProvider(MediaNotification.Provider) set} by the app. Further, the service
* is started in the <a
* href="https://developer.android.com/guide/components/foreground-services">foreground</a> when
* playback is ongoing and put back into background otherwise.
*
* <p>Apps targeting {@code SDK_INT >= 28} must request the permission, {@link
* android.Manifest.permission#FOREGROUND_SERVICE}.
*
* @param session A session that needs notification update.
* @param startInForegroundRequired Whether the service is required to start in the foreground.
*/
@UnstableApi
public void onUpdateNotification(MediaSession session, boolean startInForegroundRequired) {
onUpdateNotification(session);
if (isDefaultMethodCalled()) {
getMediaNotificationManager().updateNotification(session, startInForegroundRequired);
}
} }
/** /**
@ -431,6 +512,31 @@ public abstract class MediaSessionService extends Service {
} }
} }
/* package */ boolean onUpdateNotificationInternal(
MediaSession session, boolean startInForegroundWhenPaused) {
try {
boolean startInForegroundRequired =
shouldRunInForeground(session, startInForegroundWhenPaused);
onUpdateNotification(session, startInForegroundRequired);
} catch (/* ForegroundServiceStartNotAllowedException */ IllegalStateException e) {
if ((Util.SDK_INT >= 31) && Api31.instanceOfForegroundServiceStartNotAllowedException(e)) {
Log.e(TAG, "Failed to start foreground", e);
onForegroundServiceStartNotAllowedException();
return false;
}
throw e;
}
return true;
}
/* package */ static boolean shouldRunInForeground(
MediaSession session, boolean startInForegroundWhenPaused) {
Player player = session.getPlayer();
return (player.getPlayWhenReady() || startInForegroundWhenPaused)
&& (player.getPlaybackState() == Player.STATE_READY
|| player.getPlaybackState() == Player.STATE_BUFFERING);
}
private MediaNotificationManager getMediaNotificationManager() { private MediaNotificationManager getMediaNotificationManager() {
synchronized (lock) { synchronized (lock) {
if (mediaNotificationManager == null) { if (mediaNotificationManager == null) {
@ -455,6 +561,57 @@ public abstract class MediaSessionService extends Service {
} }
} }
@Nullable
private Listener getListener() {
synchronized (lock) {
return this.listener;
}
}
private boolean isDefaultMethodCalled() {
synchronized (lock) {
return this.defaultMethodCalled;
}
}
private void setDefaultMethodCalled(boolean defaultMethodCalled) {
synchronized (lock) {
this.defaultMethodCalled = defaultMethodCalled;
}
}
@RequiresApi(31)
private void onForegroundServiceStartNotAllowedException() {
mainHandler.post(
() -> {
@Nullable MediaSessionService.Listener serviceListener = getListener();
if (serviceListener != null) {
serviceListener.onForegroundServiceStartNotAllowedException();
}
});
}
private final class MediaSessionListener implements MediaSession.Listener {
@Override
public void onNotificationRefreshRequired(MediaSession session) {
MediaSessionService.this.onUpdateNotificationInternal(
session, /* startInForegroundWhenPaused= */ false);
}
@Override
public boolean onPlayRequested(MediaSession session) {
if (Util.SDK_INT < 31 || Util.SDK_INT >= 33) {
return true;
}
// Check if service can start foreground successfully on Android 12 and 12L.
if (!getMediaNotificationManager().isStartedInForeground()) {
return onUpdateNotificationInternal(session, /* startInForegroundWhenPaused= */ true);
}
return true;
}
}
private static final class MediaSessionServiceStub extends IMediaSessionService.Stub { private static final class MediaSessionServiceStub extends IMediaSessionService.Stub {
private final WeakReference<MediaSessionService> serviceReference; private final WeakReference<MediaSessionService> serviceReference;
@ -575,4 +732,13 @@ public abstract class MediaSessionService extends Service {
} }
} }
} }
@RequiresApi(31)
private static final class Api31 {
@DoNotInline
public static boolean instanceOfForegroundServiceStartNotAllowedException(
IllegalStateException e) {
return e instanceof ForegroundServiceStartNotAllowedException;
}
}
} }

View File

@ -611,7 +611,20 @@ import java.util.concurrent.ExecutionException;
return; return;
} }
queueSessionTaskWithPlayerCommand( queueSessionTaskWithPlayerCommand(
caller, sequenceNumber, COMMAND_PLAY_PAUSE, sendSessionResultSuccess(Player::play)); caller,
sequenceNumber,
COMMAND_PLAY_PAUSE,
sendSessionResultSuccess(
player -> {
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
if (sessionImpl == null || sessionImpl.isReleased()) {
return;
}
if (sessionImpl.onPlayRequested()) {
player.play();
}
}));
} }
@Override @Override