Add missing command checks to playback resumption flow

Player methods shouldn't be called if they are not available and the
entry point to the playback resumption flow only checks
COMMAND_PLAY_PAUSE.

#minor-release

PiperOrigin-RevId: 574834148
This commit is contained in:
tonihei 2023-10-19 05:36:49 -07:00 committed by Copybara-Service
parent 5f80a47081
commit bfd1a2724c
6 changed files with 317 additions and 137 deletions

View File

@ -1423,11 +1423,19 @@ public class MediaSession {
}
/**
* Returns the last recent playlist of the player with which the player should be prepared when
* playback resumption from a media button receiver or the System UI notification is requested.
* Returns the playlist with which the player should be prepared when a controller requests to
* play without a current {@link MediaItem}.
*
* <p>This happens, for example, if <a
* href="https://developer.android.com/guide/topics/media/session/mediasession#resumption">playback
* resumption</a> is requested from a media button receiver or the System UI notification.
*
* <p>The method will only be called if the {@link Player} has {@link
* Player#COMMAND_GET_CURRENT_MEDIA_ITEM} and either {@link Player#COMMAND_SET_MEDIA_ITEM} or
* {@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 is a short
* @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.
* @return The {@linkplain MediaItemsWithStartPosition playlist} to resume playback with.
*/

View File

@ -25,6 +25,8 @@ import static android.view.KeyEvent.KEYCODE_MEDIA_REWIND;
import static android.view.KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD;
import static android.view.KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD;
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.SDK_INT;
@ -33,7 +35,6 @@ import static androidx.media3.session.MediaSessionStub.UNKNOWN_SEQUENCE_NUMBER;
import static androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED;
import static androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN;
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
import static java.lang.Math.min;
import android.app.PendingIntent;
import android.content.ComponentName;
@ -55,10 +56,8 @@ import androidx.annotation.CheckResult;
import androidx.annotation.FloatRange;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.core.os.ExecutorCompat;
import androidx.media.MediaBrowserServiceCompat;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaLibraryInfo;
@ -87,14 +86,12 @@ 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 com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -815,44 +812,47 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
/**
* Attempts to prepare and play for playback resumption.
* Handles a play request from a media controller.
*
* <p>If playlist data for playback resumption can be successfully obtained, the media items are
* set and the player is prepared. {@link Player#play()} is called regardless of success or
* failure of playback resumption.
* <p>Attempts to prepare and play for playback resumption if the playlist is empty. {@link
* Player#play()} is called regardless of success or failure of playback resumption.
*
* @param controller The controller requesting playback resumption.
* @param player The player to setup for playback resumption.
* @param controller The controller requesting to play.
*/
/* package */ void prepareAndPlayForPlaybackResumption(ControllerInfo controller, Player player) {
verifyApplicationThread();
/* package */ void handleMediaControllerPlayRequest(ControllerInfo controller) {
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);
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);
} else {
@Nullable
ListenableFuture<MediaItemsWithStartPosition> future =
checkNotNull(
callback.onPlaybackResumption(instance, resolveControllerInfoForCallback(controller)),
"Callback.onPlaybackResumption must return a non-null future");
// Use a direct executor when an immediate future is returned to execute the player setup in the
// caller's looper event on the application thread.
Executor executor =
future.isDone()
? MoreExecutors.directExecutor()
: ExecutorCompat.create(getApplicationHandler());
Futures.addCallback(
future,
new FutureCallback<MediaItemsWithStartPosition>() {
@Override
public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
ImmutableList<MediaItem> mediaItems = mediaItemsWithStartPosition.mediaItems;
player.setMediaItems(
mediaItems,
mediaItemsWithStartPosition.startIndex != C.INDEX_UNSET
? min(mediaItems.size() - 1, mediaItemsWithStartPosition.startIndex)
: 0,
mediaItemsWithStartPosition.startPositionMs);
if (player.getPlaybackState() == Player.STATE_IDLE) {
player.prepare();
}
player.play();
MediaUtils.setMediaItemsWithStartIndexAndPosition(
playerWrapper, mediaItemsWithStartPosition);
Util.handlePlayButtonAction(playerWrapper);
}
@Override
@ -868,14 +868,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} else {
Log.e(
TAG,
"Failure calling MediaSession.Callback.onPlaybackResumption(): " + t.getMessage(),
"Failure calling MediaSession.Callback.onPlaybackResumption(): "
+ t.getMessage(),
t);
}
// Play as requested either way.
Util.handlePlayButtonAction(player);
// Play as requested even if playback resumption fails.
Util.handlePlayButtonAction(playerWrapper);
}
},
executor);
this::postOrRunOnApplicationHandler);
}
}
private void setAvailableFrameworkControllerCommands(
@ -1147,6 +1149,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return true;
}
private void postOrRunOnApplicationHandler(Runnable runnable) {
Util.postOrRun(getApplicationHandler(), runnable);
}
/* @FunctionalInterface */
interface RemoteControllerTask {

View File

@ -411,18 +411,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
public void onPlay() {
dispatchSessionTaskWithPlayerCommand(
COMMAND_PLAY_PAUSE,
controller -> {
if (sessionImpl.onPlayRequested()) {
PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper();
if (playerWrapper.getMediaItemCount() == 0) {
// The player is in IDLE or ENDED state and has no media items in the playlist yet.
// Handle the play command as a playback resumption command to try resume playback.
sessionImpl.prepareAndPlayForPlaybackResumption(controller, playerWrapper);
} else {
Util.handlePlayButtonAction(playerWrapper);
}
}
},
sessionImpl::handleMediaControllerPlayRequest,
sessionCompat.getCurrentControllerInfo());
}

View File

@ -719,16 +719,7 @@ import java.util.concurrent.ExecutionException;
if (impl == null || impl.isReleased()) {
return;
}
if (impl.onPlayRequested()) {
if (player.getMediaItemCount() == 0) {
// The player is in IDLE or ENDED state and has no media items in the playlist
// yet. Handle the play command as a playback resumption command to try resume
// playback.
impl.prepareAndPlayForPlaybackResumption(controller, player);
} else {
Util.handlePlayButtonAction(player);
}
}
impl.handleMediaControllerPlayRequest(controller);
}));
}

View File

@ -1073,8 +1073,7 @@ public class MediaSessionCallbackTest {
}
@Test
public void onPlay_withEmptyTimelinePlaybackResumptionOn_callsOnGetPlaybackResumptionPlaylist()
throws Exception {
public void onPlay_withEmptyTimeline_callsOnGetPlaybackResumptionPlaylist() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
MediaSession.Callback callback =
new MediaSession.Callback() {
@ -1093,8 +1092,8 @@ public class MediaSessionCallbackTest {
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isTrue();
@ -1104,18 +1103,110 @@ public class MediaSessionCallbackTest {
}
@Test
public void onPlay_withEmptyTimelineCallbackFailure_callsHandlePlayButtonAction()
public void
onPlay_withEmptyTimelineWithoutCommandGetCurrentMediaItem_doesNotTriggerPlaybackResumption()
throws Exception {
player.startMediaItemIndex = 7;
player.startPositionMs = 321L;
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
.build();
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
RemoteMediaController controller =
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
assertThat(player.mediaItems).isEmpty();
}
@Test
public void
onPlay_withEmptyTimelineWithoutCommandSetOrChangeMediaItems_doesNotTriggerPlaybackResumption()
throws Exception {
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.removeAll(Player.COMMAND_SET_MEDIA_ITEM, Player.COMMAND_CHANGE_MEDIA_ITEMS)
.build();
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(new MediaSession.Builder(context, player).build());
RemoteMediaController controller =
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
assertThat(player.mediaItems).isEmpty();
}
@Test
public void onPlay_withEmptyTimelineWithoutCommandChangeMediaItems_setsSingleItem()
throws Exception {
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_CHANGE_MEDIA_ITEMS)
.build();
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
MediaSession mediaSession, ControllerInfo controller) {
return Futures.immediateFuture(
new MediaSession.MediaItemsWithStartPosition(
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 123L));
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player).setCallback(callback).build());
RemoteMediaController controller =
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEM_WITH_START_POSITION))
.isTrue();
assertThat(player.startMediaItemIndex).isEqualTo(0);
assertThat(player.startPositionMs).isEqualTo(123L);
assertThat(player.mediaItems).containsExactly(mediaItems.get(0));
}
@Test
public void
onPlay_withEmptyTimelinePlaybackResumptionCallbackFailure_callsHandlePlayButtonAction()
throws Exception {
player.startMediaItemIndex = 7;
player.startPositionMs = 321L;
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
MediaSession mediaSession, ControllerInfo controller) {
return Futures.immediateFailedFuture(new UnsupportedOperationException());
}
};
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player).setCallback(callback).build());
RemoteMediaController controller =
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
@ -1150,8 +1241,8 @@ public class MediaSessionCallbackTest {
remoteControllerTestRule.createRemoteController(session.getToken());
controller.play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
@ -1188,6 +1279,7 @@ public class MediaSessionCallbackTest {
remoteControllerTestRule.createRemoteController(
session.getToken(), /* waitForConnection= */ false, testConnectionHints);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(TestUtils.equals(testConnectionHints, connectionHints.get())).isTrue();
}
@ -1213,7 +1305,9 @@ public class MediaSessionCallbackTest {
.build());
RemoteMediaController controller =
remoteControllerTestRule.createRemoteController(session.getToken());
controller.release();
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}

View File

@ -155,11 +155,13 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
.build();
// Make onDisconnected() to be called immediately after the connection.
session.setLegacyControllerConnectionTimeoutMs(0);
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
// Invoke any command for session to recognize the controller compat.
controller.getTransportControls().seekTo(111);
assertThat(disconnectedLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
}
@ -221,8 +223,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@ -240,9 +242,9 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@ -261,8 +263,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
}
@ -280,9 +282,9 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
}
@ -305,8 +307,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().play();
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
}
@ -323,7 +325,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().pause();
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
}
@ -339,7 +340,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().stop();
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
}
@ -355,7 +355,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().prepare();
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
}
@ -369,11 +368,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
long seekPosition = 12125L;
controller.getTransportControls().seekTo(seekPosition);
controller.getTransportControls().seekTo(seekPosition);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO, TIMEOUT_MS);
assertThat(player.seekPositionMs).isEqualTo(seekPosition);
}
@ -387,11 +386,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
float testSpeed = 2.0f;
controller.getTransportControls().setPlaybackSpeed(testSpeed);
controller.getTransportControls().setPlaybackSpeed(testSpeed);
player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAYBACK_SPEED, TIMEOUT_MS);
assertThat(player.playbackParameters.speed).isEqualTo(testSpeed);
}
@ -425,15 +424,15 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
player.timeline = MediaTestUtils.createTimeline(mediaItems);
player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
});
// Prepare an item to add.
String mediaId = "newMediaItemId";
Uri mediaUri = Uri.parse("https://test.test");
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder().setMediaId(mediaId).setMediaUri(mediaUri).build();
controller.addQueueItem(desc);
controller.addQueueItem(desc);
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
assertThat(requestedMediaItems.get().get(0).mediaId).isEqualTo(mediaId);
assertThat(requestedMediaItems.get().get(0).requestMetadata.mediaUri).isEqualTo(mediaUri);
@ -471,16 +470,16 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
player.timeline = MediaTestUtils.createTimeline(mediaItems);
player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
});
// Prepare an item to add.
int testIndex = 1;
String mediaId = "media_id";
Uri mediaUri = Uri.parse("https://test.test");
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder().setMediaId(mediaId).setMediaUri(mediaUri).build();
controller.addQueueItem(desc, testIndex);
controller.addQueueItem(desc, testIndex);
player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS_WITH_INDEX, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
assertThat(requestedMediaItems.get().get(0).mediaId).isEqualTo(mediaId);
assertThat(requestedMediaItems.get().get(0).requestMetadata.mediaUri).isEqualTo(mediaUri);
@ -507,15 +506,15 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
player.timeline = new PlaylistTimeline(mediaItems);
player.notifyTimelineChanged(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
});
// Select an item to remove.
int targetIndex = 3;
MediaItem targetItem = mediaItems.get(targetIndex);
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder().setMediaId(targetItem.mediaId).build();
controller.removeQueueItem(desc);
controller.removeQueueItem(desc);
player.awaitMethodCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEM, TIMEOUT_MS);
assertThat(player.index).isEqualTo(targetIndex);
}
@ -531,7 +530,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToPrevious();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
}
@ -553,7 +551,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToPrevious();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM, TIMEOUT_MS);
}
@ -569,7 +566,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToNext();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
}
@ -588,7 +584,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
controller.getTransportControls().skipToNext();
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT_MEDIA_ITEM, TIMEOUT_MS);
}
@ -613,9 +608,9 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
List<QueueItem> queue = session.getSessionCompat().getController().getQueue();
int targetIndex = 3;
controller.getTransportControls().skipToQueueItem(queue.get(targetIndex).getQueueId());
player.awaitMethodCalled(
MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS);
assertThat(player.seekMediaItemIndex).isEqualTo(targetIndex);
}
@ -643,8 +638,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
@ -655,20 +650,121 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
@Test
public void
dispatchMediaButtonEvent_playWithEmptyTimelineCallbackFailure_callsHandlePlayButtonAction()
dispatchMediaButtonEvent_playWithEmptyTimelineWithoutCommandGetCurrentMediaItem_doesNotTriggerPlaybackResumption()
throws Exception {
player.mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
player.startMediaItemIndex = 1;
player.startPositionMs = 321L;
session = new MediaSession.Builder(context, player).setId("sendMediaButtonEvent").build();
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
.build();
session = new MediaSession.Builder(context, player).setId("dispatchMediaButtonEvent").build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
assertThat(player.mediaItems).isEmpty();
}
@Test
public void
dispatchMediaButtonEvent_playWithEmptyTimelineWithoutCommandSetOrChangeMediaItems_doesNotTriggerPlaybackResumption()
throws Exception {
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.removeAll(Player.COMMAND_SET_MEDIA_ITEM, Player.COMMAND_CHANGE_MEDIA_ITEMS)
.build();
session = new MediaSession.Builder(context, player).setId("dispatchMediaButtonEvent").build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
assertThat(player.mediaItems).isEmpty();
}
@Test
public void
dispatchMediaButtonEvent_playWithEmptyTimelineWithoutCommandChangeMediaItems_setsSingleItem()
throws Exception {
player.commands =
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_CHANGE_MEDIA_ITEMS)
.build();
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
MediaSession mediaSession, ControllerInfo controller) {
return Futures.immediateFuture(
new MediaSession.MediaItemsWithStartPosition(
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 123L));
}
};
session =
new MediaSession.Builder(context, player)
.setCallback(callback)
.setId("dispatchMediaButtonEvent")
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEM_WITH_START_POSITION))
.isTrue();
assertThat(player.startMediaItemIndex).isEqualTo(0);
assertThat(player.startPositionMs).isEqualTo(123L);
assertThat(player.mediaItems).containsExactly(mediaItems.get(0));
}
@Test
public void
dispatchMediaButtonEvent_playWithEmptyTimelineCallbackFailure_callsHandlePlayButtonAction()
throws Exception {
player.mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
player.startMediaItemIndex = 1;
player.startPositionMs = 321L;
MediaSession.Callback callback =
new MediaSession.Callback() {
@Override
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
MediaSession mediaSession, ControllerInfo controller) {
return Futures.immediateFailedFuture(new UnsupportedOperationException());
}
};
session =
new MediaSession.Builder(context, player)
.setCallback(callback)
.setId("sendMediaButtonEvent")
.build();
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
@ -707,8 +803,8 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
session.getSessionCompat().getController().dispatchMediaButtonEvent(keyEvent);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX))
.isFalse();
@ -727,11 +823,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
@PlaybackStateCompat.ShuffleMode int testShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_GROUP;
controller.getTransportControls().setShuffleMode(testShuffleMode);
controller.getTransportControls().setShuffleMode(testShuffleMode);
player.awaitMethodCalled(MockPlayer.METHOD_SET_SHUFFLE_MODE, TIMEOUT_MS);
assertThat(player.shuffleModeEnabled).isTrue();
}
@ -745,11 +841,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller =
new RemoteMediaControllerCompat(
context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
int testRepeatMode = Player.REPEAT_MODE_ALL;
controller.getTransportControls().setRepeatMode(testRepeatMode);
controller.getTransportControls().setRepeatMode(testRepeatMode);
player.awaitMethodCalled(MockPlayer.METHOD_SET_REPEAT_MODE, TIMEOUT_MS);
assertThat(player.repeatMode).isEqualTo(testRepeatMode);
}
@ -777,11 +873,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
remotePlayer.deviceVolume = 23;
session.setPlayer(remotePlayer);
});
int targetVolume = 50;
controller.setVolumeTo(targetVolume, /* flags= */ 0);
controller.setVolumeTo(targetVolume, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_SET_DEVICE_VOLUME, TIMEOUT_MS);
assertThat(remotePlayer.deviceVolume).isEqualTo(targetVolume);
}
@ -805,11 +901,11 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
remotePlayer.deviceVolume = 23;
session.setPlayer(remotePlayer);
});
int targetVolume = 50;
controller.setVolumeTo(targetVolume, /* flags= */ 0);
controller.setVolumeTo(targetVolume, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_SET_DEVICE_VOLUME_WITH_FLAGS, TIMEOUT_MS);
assertThat(remotePlayer.deviceVolume).isEqualTo(targetVolume);
}
@ -839,7 +935,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
});
controller.adjustVolume(AudioManager.ADJUST_RAISE, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_INCREASE_DEVICE_VOLUME, TIMEOUT_MS);
}
@ -864,7 +959,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
});
controller.adjustVolume(AudioManager.ADJUST_RAISE, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_INCREASE_DEVICE_VOLUME_WITH_FLAGS, TIMEOUT_MS);
}
@ -894,7 +988,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
});
controller.adjustVolume(AudioManager.ADJUST_LOWER, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_DECREASE_DEVICE_VOLUME, TIMEOUT_MS);
}
@ -920,7 +1013,6 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
});
controller.adjustVolume(AudioManager.ADJUST_LOWER, /* flags= */ 0);
remotePlayer.awaitMethodCalled(MockPlayer.METHOD_DECREASE_DEVICE_VOLUME_WITH_FLAGS, TIMEOUT_MS);
}