Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ public static boolean postOrRun(HandlerWrapper handler, Runnable runnable) {
*/
@UnstableApi
public static <T> ListenableFuture<T> postOrRunWithCompletion(
Handler handler, Runnable runnable, T successValue) {
Handler handler, Runnable runnable, @Nullable T successValue) {
SettableFuture<T> outputFuture = SettableFuture.create();
postOrRun(
handler,
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,10 @@ public final android.media.session.MediaSession.Token getPlatformToken() {
return impl.getUri();
}

/* package */ @Nullable final Looper getBackgroundLooper() {
return impl.getBackgroundLooper();
}

/**
* A progress reporter to report progress for a custom command sent by a controller.
*
Expand Down Expand Up @@ -2450,7 +2454,7 @@ default void onError(int seq, SessionError sessionError) throws RemoteException
/**
* Listener for media session events.
*
* <p>All methods must be called on the main thread.
* <p>All methods can be called on any thread.
*/
/* package */ interface Listener {

Expand All @@ -2468,7 +2472,7 @@ default void onError(int seq, SessionError sessionError) throws RemoteException
* @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);
ListenableFuture<Boolean> onPlayRequested(MediaSession session);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
Expand Down Expand Up @@ -91,7 +92,6 @@
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.ref.WeakReference;
Expand Down Expand Up @@ -139,6 +139,7 @@
private final boolean isPeriodicPositionUpdateEnabled;
private final boolean useLegacySurfaceHandling;
private final ImmutableList<CommandButton> commandButtonsForMediaItems;
private final HandlerThread backgroundThread;

private PlayerInfo playerInfo;
private PlayerWrapper playerWrapper;
Expand Down Expand Up @@ -208,6 +209,9 @@ public MediaSessionImpl(

sessionStub = new MediaSessionStub(thisRef);

backgroundThread = new HandlerThread("MediaSessionImpl:bg");
backgroundThread.start();

mainHandler = new Handler(Looper.getMainLooper());
Looper applicationLooper = player.getApplicationLooper();
applicationHandler = new Handler(applicationLooper);
Expand All @@ -227,16 +231,16 @@ public MediaSessionImpl(
new MediaSessionLegacyStub(
/* session= */ thisRef,
sessionUri,
applicationHandler,
tokenExtras,
playIfSuppressed,
customLayout,
mediaButtonPreferences,
connectionResult.availableSessionCommands,
connectionResult.availablePlayerCommands,
sessionExtras);
sessionExtras,
backgroundThread.getLooper());

Token platformToken = sessionLegacyStub.getSessionCompat().getSessionToken().getToken();
Token platformToken = sessionLegacyStub.getSessionToken().getToken();
sessionToken =
new SessionToken(
Process.myUid(),
Expand Down Expand Up @@ -329,6 +333,7 @@ public void release() {
}
sessionLegacyStub.release();
sessionStub.release();
backgroundThread.quitSafely();
}

public PlayerWrapper getPlayerWrapper() {
Expand Down Expand Up @@ -357,6 +362,10 @@ public SessionToken getToken() {
return sessionToken;
}

public @Nullable Looper getBackgroundLooper() {
return backgroundThread.getLooper();
}

public List<ControllerInfo> getConnectedControllers() {
ImmutableList<ControllerInfo> media3Controllers =
sessionStub.getConnectedControllersManager().getConnectedControllers();
Expand Down Expand Up @@ -980,7 +989,7 @@ public void connectFromService(IMediaController caller, ControllerInfo controlle

@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding confusion by just using "Token"
public android.media.session.MediaSession.Token getPlatformToken() {
return sessionLegacyStub.getSessionCompat().getSessionToken().getToken();
return sessionLegacyStub.getSessionToken().getToken();
}

public void setLegacyControllerConnectionTimeoutMs(long timeoutMs) {
Expand Down Expand Up @@ -1049,8 +1058,7 @@ protected IBinder getLegacyBrowserServiceBinder() {
MediaSessionServiceLegacyStub legacyStub;
synchronized (lock) {
if (browserServiceLegacyStub == null) {
browserServiceLegacyStub =
createLegacyBrowserService(sessionLegacyStub.getSessionCompat().getSessionToken());
browserServiceLegacyStub = createLegacyBrowserService(sessionLegacyStub.getSessionToken());
}
legacyStub = browserServiceLegacyStub;
}
Expand Down Expand Up @@ -1122,32 +1130,16 @@ protected MediaSessionServiceLegacyStub getLegacyBrowserService() {
}

/* package */ void onNotificationRefreshRequired() {
postOrRun(
mainHandler,
() -> {
if (this.mediaSessionListener != null) {
this.mediaSessionListener.onNotificationRefreshRequired(instance);
}
});
if (this.mediaSessionListener != null) {
this.mediaSessionListener.onNotificationRefreshRequired(instance);
}
}

/* package */ boolean onPlayRequested() {
if (Looper.myLooper() != Looper.getMainLooper()) {
try {
return CallbackToFutureAdapter.<Boolean>getFuture(
completer -> {
mainHandler.post(() -> completer.set(onPlayRequested()));
return "onPlayRequested";
})
.get();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
}
}
/* package */ ListenableFuture<Boolean> onPlayRequested() {
if (this.mediaSessionListener != null) {
return this.mediaSessionListener.onPlayRequested(instance);
}
return true;
return Futures.immediateFuture(true);
}

/**
Expand All @@ -1158,84 +1150,91 @@ protected MediaSessionServiceLegacyStub getLegacyBrowserService() {
*
* @param controller The controller requesting to play.
*/
/* package */ void handleMediaControllerPlayRequest(
/* package */ ListenableFuture<SessionResult> handleMediaControllerPlayRequest(
ControllerInfo controller, boolean callOnPlayerInteractionFinished) {
if (!onPlayRequested()) {
// Request denied, e.g. due to missing foreground service abilities.
return;
}
boolean hasCurrentMediaItem =
playerWrapper.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
&& playerWrapper.getCurrentMediaItem() != null;
boolean canAddMediaItems =
playerWrapper.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)
|| playerWrapper.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS);
ControllerInfo controllerForRequest = resolveControllerInfoForCallback(controller);
Player.Commands playCommand =
new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build();
if (hasCurrentMediaItem || !canAddMediaItems) {
// No playback resumption needed or possible.
if (!hasCurrentMediaItem) {
Log.w(
TAG,
"Play requested without current MediaItem, but playback resumption prevented by"
+ " missing available commands");
}
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
}
} else {
@Nullable
ListenableFuture<MediaItemsWithStartPosition> future =
checkNotNull(
callback.onPlaybackResumption(
instance, controllerForRequest, /* isForPlayback= */ true),
"Callback.onPlaybackResumption must return a non-null future");
Futures.addCallback(
future,
new FutureCallback<MediaItemsWithStartPosition>() {
@Override
public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
callWithControllerForCurrentRequestSet(
controllerForRequest,
() -> {
MediaUtils.setMediaItemsWithStartIndexAndPosition(
playerWrapper, mediaItemsWithStartPosition);
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
}
})
.run();
return Futures.transformAsync(
onPlayRequested(),
playRequested -> {
if (!playRequested) {
// Request denied, e.g. due to missing foreground service abilities.
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN));
}
boolean hasCurrentMediaItem =
playerWrapper.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
&& playerWrapper.getCurrentMediaItem() != null;
boolean canAddMediaItems =
playerWrapper.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)
|| playerWrapper.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS);
ControllerInfo controllerForRequest = resolveControllerInfoForCallback(controller);
Player.Commands playCommand =
new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build();
if (hasCurrentMediaItem || !canAddMediaItems) {
// No playback resumption needed or possible.
if (!hasCurrentMediaItem) {
Log.w(
TAG,
"Play requested without current MediaItem, but playback resumption prevented by"
+ " missing available commands");
}

@Override
public void onFailure(Throwable t) {
if (t instanceof UnsupportedOperationException) {
Log.w(
TAG,
"UnsupportedOperationException: Make sure to implement"
+ " MediaSession.Callback.onPlaybackResumption() if you add a"
+ " media button receiver to your manifest or if you implement the recent"
+ " media item contract with your MediaLibraryService.",
t);
} else {
Log.e(
TAG,
"Failure calling MediaSession.Callback.onPlaybackResumption(): "
+ t.getMessage(),
t);
}
// Play as requested even if playback resumption fails.
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
}
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
}
},
this::postOrRunOnApplicationHandler);
}
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
} else {
ListenableFuture<SessionResult> future =
Futures.transform(
checkNotNull(
callback.onPlaybackResumption(
instance, controllerForRequest, /* isForPlayback= */ true),
"Callback.onPlaybackResumption must return a non-null future"),
mediaItemsWithStartPosition -> {
callWithControllerForCurrentRequestSet(
controllerForRequest,
() -> {
MediaUtils.setMediaItemsWithStartIndexAndPosition(
playerWrapper, mediaItemsWithStartPosition);
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(
controllerForRequest, playCommand);
}
})
.run();
return new SessionResult(SessionResult.RESULT_SUCCESS);
},
this::postOrRunOnApplicationHandler);
return Futures.catching(
future,
Throwable.class,
t -> {
if (t instanceof UnsupportedOperationException) {
Log.w(
TAG,
"UnsupportedOperationException: Make sure to implement"
+ " MediaSession.Callback.onPlaybackResumption() if you add a media"
+ " button receiver to your manifest or if you implement the recent"
+ " media item contract with your MediaLibraryService.",
t);
} else {
Log.e(
TAG,
"Failure calling MediaSession.Callback.onPlaybackResumption(): "
+ t.getMessage(),
t);
}
// Play as requested even if playback resumption fails.
Util.handlePlayButtonAction(playerWrapper);
if (callOnPlayerInteractionFinished) {
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
}
return new SessionResult(SessionResult.RESULT_SUCCESS);
},
this::postOrRunOnApplicationHandler
);
}
},
this::postOrRunOnApplicationHandler);
}

/* package */ void triggerPlayerInfoUpdate() {
Expand Down Expand Up @@ -1515,7 +1514,7 @@ private void handleAvailablePlayerCommandsChanged(Player.Commands availableComma
sessionLegacyStub.onSkipToNext();
return true;
} else if (callerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) {
sessionLegacyStub.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
sessionLegacyStub.getControllerCompat().dispatchMediaButtonEvent(keyEvent);
return true;
}
// This is an unhandled framework event. Return false to let the framework resolve by calling
Expand Down
Loading