Add MediaSession.Callback.onPlayerInteractionFinished
This callback is useful for advanced use cases that care about which Player calls are batched together. It can be implemented by triggering this new callback every time a batch of Player interactions from a controller finished executing. PiperOrigin-RevId: 642189491
This commit is contained in:
parent
32d7516237
commit
acb6a89ff6
@ -18,6 +18,9 @@
|
||||
* Muxers:
|
||||
* IMA extension:
|
||||
* Session:
|
||||
* Add `MediaSession.Callback.onPlayerInteractionFinished` to inform
|
||||
sessions when a series of player interactions from a specific controller
|
||||
finished.
|
||||
* UI:
|
||||
* Add customisation of various icons in `PlayerControlView` through xml
|
||||
attributes to allow different drawables per `PlayerView` instance,
|
||||
|
@ -24,6 +24,7 @@ import androidx.collection.ArrayMap;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.lang.ref.WeakReference;
|
||||
@ -233,10 +234,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
}
|
||||
}
|
||||
|
||||
public void addToCommandQueue(ControllerInfo controllerInfo, AsyncCommand asyncCommand) {
|
||||
public void addToCommandQueue(
|
||||
ControllerInfo controllerInfo, @Player.Command int command, AsyncCommand asyncCommand) {
|
||||
synchronized (lock) {
|
||||
@Nullable ConnectedControllerRecord<T> info = controllerRecords.get(controllerInfo);
|
||||
if (info != null) {
|
||||
info.commandQueuePlayerCommands =
|
||||
info.commandQueuePlayerCommands.buildUpon().add(command).build();
|
||||
info.commandQueue.add(asyncCommand);
|
||||
}
|
||||
}
|
||||
@ -245,7 +249,21 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
public void flushCommandQueue(ControllerInfo controllerInfo) {
|
||||
synchronized (lock) {
|
||||
@Nullable ConnectedControllerRecord<T> info = controllerRecords.get(controllerInfo);
|
||||
if (info == null || info.commandQueueIsFlushing || info.commandQueue.isEmpty()) {
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
Player.Commands commandQueuePlayerCommands = info.commandQueuePlayerCommands;
|
||||
info.commandQueuePlayerCommands = Player.Commands.EMPTY;
|
||||
info.commandQueue.add(
|
||||
() -> {
|
||||
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
|
||||
if (sessionImpl != null) {
|
||||
sessionImpl.onPlayerInteractionFinishedOnHandler(
|
||||
controllerInfo, commandQueuePlayerCommands);
|
||||
}
|
||||
return Futures.immediateVoidFuture();
|
||||
});
|
||||
if (info.commandQueueIsFlushing) {
|
||||
return;
|
||||
}
|
||||
info.commandQueueIsFlushing = true;
|
||||
@ -299,6 +317,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
public SessionCommands sessionCommands;
|
||||
public Player.Commands playerCommands;
|
||||
public boolean commandQueueIsFlushing;
|
||||
public Player.Commands commandQueuePlayerCommands;
|
||||
|
||||
public ConnectedControllerRecord(
|
||||
T controllerKey,
|
||||
@ -310,6 +329,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
this.sessionCommands = sessionCommands;
|
||||
this.playerCommands = playerCommands;
|
||||
this.commandQueue = new ArrayDeque<>();
|
||||
this.commandQueuePlayerCommands = Player.Commands.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
}
|
||||
|
||||
private void dispatchRemoteSessionTaskWithPlayerCommandAndWaitForFuture(RemoteSessionTask task) {
|
||||
// Do not send a flush command queue message as we are actively waiting for task.
|
||||
flushCommandQueueHandler.sendFlushCommandQueueMessage();
|
||||
ListenableFuture<SessionResult> future =
|
||||
dispatchRemoteSessionTask(iSession, task, /* addToPendingMaskingOperations= */ true);
|
||||
try {
|
||||
|
@ -1300,7 +1300,7 @@ public class MediaSession {
|
||||
* callback returns quickly to avoid blocking the main thread for a long period of time.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @return The {@link ConnectionResult}.
|
||||
*/
|
||||
default ConnectionResult onConnect(MediaSession session, ControllerInfo controller) {
|
||||
@ -1316,7 +1316,7 @@ public class MediaSession {
|
||||
* isn't connected yet in {@link #onConnect}.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
*/
|
||||
default void onPostConnect(MediaSession session, ControllerInfo controller) {}
|
||||
|
||||
@ -1328,7 +1328,7 @@ public class MediaSession {
|
||||
* controller APIs.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
*/
|
||||
default void onDisconnected(MediaSession session, ControllerInfo controller) {}
|
||||
|
||||
@ -1356,7 +1356,7 @@ public class MediaSession {
|
||||
* Futures#immediateFuture(Object)}.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @param mediaId The media id.
|
||||
* @param rating The new rating from the controller.
|
||||
* @see SessionCommand#COMMAND_CODE_SESSION_SET_RATING
|
||||
@ -1379,7 +1379,7 @@ public class MediaSession {
|
||||
* Futures#immediateFuture(Object)}.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @param rating The new rating from the controller.
|
||||
* @see SessionCommand#COMMAND_CODE_SESSION_SET_RATING
|
||||
*/
|
||||
@ -1407,7 +1407,7 @@ public class MediaSession {
|
||||
* Futures#immediateFuture(Object)}.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @param customCommand The custom command.
|
||||
* @param args A {@link Bundle} for additional arguments. May be empty.
|
||||
* @return The result of handling the custom command.
|
||||
@ -1469,7 +1469,7 @@ public class MediaSession {
|
||||
* as appropriate once the {@link MediaItem} has been resolved.
|
||||
*
|
||||
* @param mediaSession The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @param mediaItems The list of requested {@link MediaItem media items}.
|
||||
* @return A {@link ListenableFuture} for the list of resolved {@link MediaItem media items}
|
||||
* that are playable by the underlying {@link Player}.
|
||||
@ -1535,7 +1535,7 @@ public class MediaSession {
|
||||
* as appropriate once the {@link MediaItem} has been resolved.
|
||||
*
|
||||
* @param mediaSession The session for this event.
|
||||
* @param controller The controller information.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
* @param mediaItems The list of requested {@linkplain MediaItem media items}.
|
||||
* @param startIndex The start index in the {@link MediaItem} list from which to start playing,
|
||||
* or {@link C#INDEX_UNSET C.INDEX_UNSET} to start playing from the default index in the
|
||||
@ -1578,8 +1578,9 @@ public class MediaSession {
|
||||
* {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} available.
|
||||
*
|
||||
* @param mediaSession The media session for which playback resumption is requested.
|
||||
* @param controller The controller that requests the playback resumption. This may be a short
|
||||
* living controller created only for issuing a play command for resuming playback.
|
||||
* @param controller The {@linkplain ControllerInfo controller} that requests the playback
|
||||
* resumption. This may be a short living controller created only for issuing a play command
|
||||
* for resuming playback.
|
||||
* @return The {@linkplain MediaItemsWithStartPosition playlist} to resume playback with.
|
||||
*/
|
||||
@UnstableApi
|
||||
@ -1604,7 +1605,8 @@ public class MediaSession {
|
||||
* to your session.
|
||||
*
|
||||
* @param session The session that received the media button event.
|
||||
* @param controllerInfo The controller to which the media button event is attributed to.
|
||||
* @param controllerInfo The {@linkplain ControllerInfo controller} to which the media button
|
||||
* event is attributed to.
|
||||
* @param intent The media button intent.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
@ -1613,6 +1615,27 @@ public class MediaSession {
|
||||
MediaSession session, ControllerInfo controllerInfo, Intent intent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after all concurrent interactions with {@linkplain MediaSession#getPlayer() the
|
||||
* session player} from a controller have finished.
|
||||
*
|
||||
* <p>A controller may call multiple {@link Player} methods within a single {@link Looper}
|
||||
* message. Those {@link Player} method calls are batched together and once finished, this
|
||||
* callback is called to signal that no further {@link Player} interactions coming from this
|
||||
* controller are expected for now.
|
||||
*
|
||||
* <p>Apps can use this callback if they need to trigger different logic depending on whether
|
||||
* certain methods are called together, for example just {@link Player#setMediaItems}, or {@link
|
||||
* Player#setMediaItems} and {@link Player#play} together.
|
||||
*
|
||||
* @param session The {@link MediaSession} that received the {@link Player} calls.
|
||||
* @param controllerInfo The {@linkplain ControllerInfo controller} sending the calls.
|
||||
* @param playerCommands The set of {@link Player.Commands} used to send these calls.
|
||||
*/
|
||||
@UnstableApi
|
||||
default void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {}
|
||||
}
|
||||
|
||||
/** Representation of a list of {@linkplain MediaItem media items} and where to start playing. */
|
||||
|
@ -748,6 +748,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
"Callback.onSetMediaItems must return a non-null future");
|
||||
}
|
||||
|
||||
protected void onPlayerInteractionFinishedOnHandler(
|
||||
ControllerInfo controller, Player.Commands playerCommands) {
|
||||
callback.onPlayerInteractionFinished(
|
||||
instance, resolveControllerInfoForCallback(controller), playerCommands);
|
||||
}
|
||||
|
||||
public void connectFromService(IMediaController caller, ControllerInfo controllerInfo) {
|
||||
sessionStub.connect(caller, controllerInfo);
|
||||
}
|
||||
@ -888,7 +894,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
*
|
||||
* @param controller The controller requesting to play.
|
||||
*/
|
||||
/* package */ void handleMediaControllerPlayRequest(ControllerInfo controller) {
|
||||
/* package */ void handleMediaControllerPlayRequest(
|
||||
ControllerInfo controller, boolean callOnPlayerInteractionFinished) {
|
||||
if (!onPlayRequested()) {
|
||||
// Request denied, e.g. due to missing foreground service abilities.
|
||||
return;
|
||||
@ -899,6 +906,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
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) {
|
||||
@ -908,20 +918,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
+ " missing available commands");
|
||||
}
|
||||
Util.handlePlayButtonAction(playerWrapper);
|
||||
if (callOnPlayerInteractionFinished) {
|
||||
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
|
||||
}
|
||||
} else {
|
||||
@Nullable
|
||||
ListenableFuture<MediaItemsWithStartPosition> future =
|
||||
checkNotNull(
|
||||
callback.onPlaybackResumption(instance, resolveControllerInfoForCallback(controller)),
|
||||
callback.onPlaybackResumption(instance, controllerForRequest),
|
||||
"Callback.onPlaybackResumption must return a non-null future");
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<MediaItemsWithStartPosition>() {
|
||||
@Override
|
||||
public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
|
||||
MediaUtils.setMediaItemsWithStartIndexAndPosition(
|
||||
playerWrapper, mediaItemsWithStartPosition);
|
||||
Util.handlePlayButtonAction(playerWrapper);
|
||||
callWithControllerForCurrentRequestSet(
|
||||
controllerForRequest,
|
||||
() -> {
|
||||
MediaUtils.setMediaItemsWithStartIndexAndPosition(
|
||||
playerWrapper, mediaItemsWithStartPosition);
|
||||
Util.handlePlayButtonAction(playerWrapper);
|
||||
if (callOnPlayerInteractionFinished) {
|
||||
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -943,6 +964,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
// Play as requested even if playback resumption fails.
|
||||
Util.handlePlayButtonAction(playerWrapper);
|
||||
if (callOnPlayerInteractionFinished) {
|
||||
onPlayerInteractionFinishedOnHandler(controllerForRequest, playCommand);
|
||||
}
|
||||
}
|
||||
},
|
||||
this::postOrRunOnApplicationHandler);
|
||||
|
@ -341,7 +341,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
controller ->
|
||||
Util.handlePlayPauseButtonAction(
|
||||
sessionImpl.getPlayerWrapper(), sessionImpl.shouldPlayIfSuppressed()),
|
||||
remoteUserInfo);
|
||||
remoteUserInfo,
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -349,7 +350,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_PREPARE,
|
||||
controller -> sessionImpl.getPlayerWrapper().prepare(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -379,8 +381,11 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
public void onPlay() {
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_PLAY_PAUSE,
|
||||
sessionImpl::handleMediaControllerPlayRequest,
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
controller ->
|
||||
sessionImpl.handleMediaControllerPlayRequest(
|
||||
controller, /* callOnPlayerInteractionFinished= */ true),
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -411,7 +416,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_PLAY_PAUSE,
|
||||
controller -> Util.handlePauseButtonAction(sessionImpl.getPlayerWrapper()),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -419,7 +425,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_STOP,
|
||||
controller -> sessionImpl.getPlayerWrapper().stop(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -427,7 +434,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekTo(pos),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -436,12 +444,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_TO_NEXT,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekToNext(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
} else {
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekToNextMediaItem(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,12 +461,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_TO_PREVIOUS,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekToPrevious(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
} else {
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekToPreviousMediaItem(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,7 +480,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SET_SPEED_AND_PITCH,
|
||||
controller -> sessionImpl.getPlayerWrapper().setPlaybackSpeed(speed),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -484,7 +497,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
// see: {@link MediaUtils#convertToQueueItem}.
|
||||
playerWrapper.seekToDefaultPosition((int) queueId);
|
||||
},
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -492,7 +506,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_FORWARD,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekForward(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -500,7 +515,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
dispatchSessionTaskWithPlayerCommand(
|
||||
COMMAND_SEEK_BACK,
|
||||
controller -> sessionImpl.getPlayerWrapper().seekBack(),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -543,7 +559,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
.getPlayerWrapper()
|
||||
.setRepeatMode(
|
||||
LegacyConversions.convertToRepeatMode(playbackStateCompatRepeatMode)),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -554,7 +571,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
sessionImpl
|
||||
.getPlayerWrapper()
|
||||
.setShuffleModeEnabled(LegacyConversions.convertToShuffleModeEnabled(shuffleMode)),
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -595,7 +613,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
}
|
||||
},
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ true);
|
||||
}
|
||||
|
||||
public ControllerCb getControllerLegacyCbForBroadcast() {
|
||||
@ -611,7 +630,10 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
private void dispatchSessionTaskWithPlayerCommand(
|
||||
@Player.Command int command, SessionTask task, @Nullable RemoteUserInfo remoteUserInfo) {
|
||||
@Player.Command int command,
|
||||
SessionTask task,
|
||||
@Nullable RemoteUserInfo remoteUserInfo,
|
||||
boolean callOnPlayerInteractionFinished) {
|
||||
if (sessionImpl.isReleased()) {
|
||||
return;
|
||||
}
|
||||
@ -673,6 +695,10 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
})
|
||||
.run();
|
||||
if (callOnPlayerInteractionFinished) {
|
||||
sessionImpl.onPlayerInteractionFinishedOnHandler(
|
||||
controller, new Player.Commands.Builder().add(command).build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -829,6 +855,12 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
if (play) {
|
||||
player.playIfCommandAvailable();
|
||||
}
|
||||
sessionImpl.onPlayerInteractionFinishedOnHandler(
|
||||
controller,
|
||||
new Player.Commands.Builder()
|
||||
.addAll(COMMAND_SET_MEDIA_ITEM, COMMAND_PREPARE)
|
||||
.addIf(COMMAND_PLAY_PAUSE, play)
|
||||
.build());
|
||||
}));
|
||||
}
|
||||
|
||||
@ -839,7 +871,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
},
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ false);
|
||||
}
|
||||
|
||||
private void handleOnAddQueueItem(@Nullable MediaDescriptionCompat description, int index) {
|
||||
@ -872,6 +905,11 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
} else {
|
||||
sessionImpl.getPlayerWrapper().addMediaItems(index, mediaItems);
|
||||
}
|
||||
sessionImpl.onPlayerInteractionFinishedOnHandler(
|
||||
controller,
|
||||
new Player.Commands.Builder()
|
||||
.add(COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.build());
|
||||
}));
|
||||
}
|
||||
|
||||
@ -882,7 +920,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
},
|
||||
sessionCompat.getCurrentControllerInfo());
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
/* callOnPlayerInteractionFinished= */ false);
|
||||
}
|
||||
|
||||
private static void sendCustomCommandResultWhenReady(
|
||||
|
@ -327,13 +327,18 @@ import java.util.concurrent.ExecutionException;
|
||||
return;
|
||||
}
|
||||
if (command == COMMAND_SET_VIDEO_SURFACE) {
|
||||
// Call surface changes immediately to ensure they are handled within the calling
|
||||
// methods stack. Also add a placeholder task to the regular command queue for proper
|
||||
// task tracking (e.g. to send onPlayerInteractionFinished).
|
||||
sessionImpl
|
||||
.callWithControllerForCurrentRequestSet(
|
||||
controller, () -> task.run(sessionImpl, controller, sequenceNumber))
|
||||
.run();
|
||||
connectedControllersManager.addToCommandQueue(
|
||||
controller, command, Futures::immediateVoidFuture);
|
||||
} else {
|
||||
connectedControllersManager.addToCommandQueue(
|
||||
controller, () -> task.run(sessionImpl, controller, sequenceNumber));
|
||||
controller, command, () -> task.run(sessionImpl, controller, sequenceNumber));
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
@ -724,7 +729,8 @@ import java.util.concurrent.ExecutionException;
|
||||
if (impl == null || impl.isReleased()) {
|
||||
return;
|
||||
}
|
||||
impl.handleMediaControllerPlayRequest(controller);
|
||||
impl.handleMediaControllerPlayRequest(
|
||||
controller, /* callOnPlayerInteractionFinished= */ false);
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
|
||||
import static androidx.media3.session.MediaTestUtils.createMediaItem;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE;
|
||||
import static androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED;
|
||||
@ -38,6 +39,7 @@ import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Rating;
|
||||
import androidx.media3.common.StarRating;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.session.MediaSession.ConnectionResult.AcceptedResultBuilder;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
@ -1553,6 +1555,119 @@ public class MediaSessionCallbackTest {
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withSingleControllerCall_calledWithMatchingCommand()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setPlayWhenReady(true);
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAY_WHEN_READY)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withMultipleControllerCalls_calledWithMatchingCommands()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.setMediaItemsPreparePlayAddItemsSeek(
|
||||
ImmutableList.of(MediaItem.fromUri("https://uri1")),
|
||||
ImmutableList.of(MediaItem.fromUri("https://uri2")),
|
||||
/* seekIndex= */ 1);
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX))
|
||||
.isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(
|
||||
new Player.Commands.Builder()
|
||||
.addAll(
|
||||
Player.COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
Player.COMMAND_PREPARE,
|
||||
Player.COMMAND_PLAY_PAUSE,
|
||||
Player.COMMAND_SEEK_TO_MEDIA_ITEM)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withPlaybackResumption_calledWithMatchingCommands()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
MediaTestUtils.createMediaItems(2),
|
||||
/* startIndex= */ 1,
|
||||
/* startPositionMs= */ 123L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController controller =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
controller.play();
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(new Player.Commands.Builder().add(COMMAND_PLAY_PAUSE).build());
|
||||
}
|
||||
|
||||
private void postToPlayerAndSync(TestHandler.TestRunnable r) {
|
||||
try {
|
||||
playerThreadTestRule.getHandler().postAndSync(r);
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.session;
|
||||
import static androidx.media.MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
|
||||
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
|
||||
import static androidx.media3.common.Player.COMMAND_PREPARE;
|
||||
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
|
||||
import static androidx.media3.common.Player.STATE_ENDED;
|
||||
import static androidx.media3.common.Player.STATE_IDLE;
|
||||
import static androidx.media3.common.Player.STATE_READY;
|
||||
@ -39,6 +40,7 @@ import android.os.Bundle;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.RatingCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.view.KeyEvent;
|
||||
@ -51,6 +53,7 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Rating;
|
||||
import androidx.media3.common.StarRating;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
@ -255,7 +258,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
public void play_whileIdleWithoutPrepareCommandAvailable_callsJustPlay() throws Exception {
|
||||
player.playbackState = STATE_IDLE;
|
||||
player.commands =
|
||||
new Player.Commands.Builder().addAllCommands().remove(Player.COMMAND_PREPARE).build();
|
||||
new Player.Commands.Builder().addAllCommands().remove(COMMAND_PREPARE).build();
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("play")
|
||||
@ -776,7 +779,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
player.commands =
|
||||
new Player.Commands.Builder()
|
||||
.addAllCommands()
|
||||
.removeAll(Player.COMMAND_SET_MEDIA_ITEM, Player.COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.removeAll(COMMAND_SET_MEDIA_ITEM, Player.COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.build();
|
||||
session = new MediaSession.Builder(context, player).setId("dispatchMediaButtonEvent").build();
|
||||
controller =
|
||||
@ -1979,6 +1982,212 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withSimpleControllerCall_calledWithMatchingCommand()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new TestSessionCallback() {
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onPlayerInteractionFinished_simpleCall")
|
||||
.setCallback(callback)
|
||||
.build();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
MediaSessionCompat.Token.fromToken(session.getPlatformToken()),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
controller.getTransportControls().setPlaybackSpeed(2f);
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAYBACK_SPEED)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(new Player.Commands.Builder().add(Player.COMMAND_SET_SPEED_AND_PITCH).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withPlaybackResumption_calledWithMatchingCommand()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new TestSessionCallback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
MediaTestUtils.createMediaItems(2),
|
||||
/* startIndex= */ 1,
|
||||
/* startPositionMs= */ 123L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onPlayerInteractionFinished_playbackResumption")
|
||||
.setCallback(callback)
|
||||
.build();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
MediaSessionCompat.Token.fromToken(session.getPlatformToken()),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
controller.getTransportControls().play();
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(new Player.Commands.Builder().add(COMMAND_PLAY_PAUSE).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withPlayFromUri_calledWithMatchingCommands()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new TestSessionCallback() {
|
||||
@Override
|
||||
public ListenableFuture<List<MediaItem>> onAddMediaItems(
|
||||
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
|
||||
return Futures.immediateFuture(MediaTestUtils.createMediaItems(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onPlayerInteractionFinished_playFromUri")
|
||||
.setCallback(callback)
|
||||
.build();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
MediaSessionCompat.Token.fromToken(session.getPlatformToken()),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
controller.getTransportControls().playFromUri(Uri.parse("https://uri"), Bundle.EMPTY);
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(
|
||||
new Player.Commands.Builder()
|
||||
.addAll(COMMAND_SET_MEDIA_ITEM, COMMAND_PREPARE, COMMAND_PLAY_PAUSE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withPrepareFromUri_calledWithMatchingCommands()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new TestSessionCallback() {
|
||||
@Override
|
||||
public ListenableFuture<List<MediaItem>> onAddMediaItems(
|
||||
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
|
||||
return Futures.immediateFuture(MediaTestUtils.createMediaItems(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onPlayerInteractionFinished_prepareFromUri")
|
||||
.setCallback(callback)
|
||||
.build();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
MediaSessionCompat.Token.fromToken(session.getPlatformToken()),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
controller.getTransportControls().prepareFromUri(Uri.parse("https://uri"), Bundle.EMPTY);
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION))
|
||||
.isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(
|
||||
new Player.Commands.Builder().addAll(COMMAND_SET_MEDIA_ITEM, COMMAND_PREPARE).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPlayerInteractionFinished_withAddQueueItem_calledWithMatchingCommand()
|
||||
throws Exception {
|
||||
AtomicReference<Player.Commands> onPlayerInteractionFinishedCommands = new AtomicReference<>();
|
||||
ConditionVariable onPlayerInteractionFinishedCalled = new ConditionVariable();
|
||||
MediaSession.Callback callback =
|
||||
new TestSessionCallback() {
|
||||
@Override
|
||||
public ListenableFuture<List<MediaItem>> onAddMediaItems(
|
||||
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
|
||||
return Futures.immediateFuture(MediaTestUtils.createMediaItems(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInteractionFinished(
|
||||
MediaSession session, ControllerInfo controllerInfo, Player.Commands playerCommands) {
|
||||
onPlayerInteractionFinishedCommands.set(playerCommands);
|
||||
onPlayerInteractionFinishedCalled.open();
|
||||
}
|
||||
};
|
||||
session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("onPlayerInteractionFinished_prepareFromUri")
|
||||
.setCallback(callback)
|
||||
.build();
|
||||
controller =
|
||||
new RemoteMediaControllerCompat(
|
||||
context,
|
||||
MediaSessionCompat.Token.fromToken(session.getPlatformToken()),
|
||||
/* waitForConnection= */ true);
|
||||
|
||||
controller.addQueueItem(new MediaDescriptionCompat.Builder().setMediaId("id").build());
|
||||
assertThat(onPlayerInteractionFinishedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS)).isTrue();
|
||||
assertThat(onPlayerInteractionFinishedCommands.get())
|
||||
.isEqualTo(new Player.Commands.Builder().add(Player.COMMAND_CHANGE_MEDIA_ITEMS).build());
|
||||
}
|
||||
|
||||
private static class TestSessionCallback implements MediaSession.Callback {
|
||||
|
||||
@Override
|
||||
|
@ -371,8 +371,10 @@ public class RemoteMediaController {
|
||||
throws RemoteException {
|
||||
binder.setMediaItemsPreparePlayAddItemsSeek(
|
||||
controllerId,
|
||||
BundleCollectionUtil.toBundleList(initialMediaItems, MediaItem::toBundle),
|
||||
BundleCollectionUtil.toBundleList(addedMediaItems, MediaItem::toBundle),
|
||||
BundleCollectionUtil.toBundleList(
|
||||
initialMediaItems, MediaItem::toBundleIncludeLocalConfiguration),
|
||||
BundleCollectionUtil.toBundleList(
|
||||
addedMediaItems, MediaItem::toBundleIncludeLocalConfiguration),
|
||||
seekIndex);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user