diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
index d4a8009856..ba653118d7 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
@@ -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}.
+ *
+ *
This happens, for example, if playback
+ * resumption is requested from a media button receiver or the System UI notification.
+ *
+ *
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.
*/
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
index 4fe78f23da..79253148b6 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
@@ -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,67 +812,72 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
/**
- * Attempts to prepare and play for playback resumption.
+ * Handles a play request from a media controller.
*
- *
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.
+ *
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();
- @Nullable
- ListenableFuture 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() {
- @Override
- public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
- ImmutableList 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();
+ /* 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 future =
+ checkNotNull(
+ callback.onPlaybackResumption(instance, resolveControllerInfoForCallback(controller)),
+ "Callback.onPlaybackResumption must return a non-null future");
+ Futures.addCallback(
+ future,
+ new FutureCallback() {
+ @Override
+ public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
+ MediaUtils.setMediaItemsWithStartIndexAndPosition(
+ playerWrapper, mediaItemsWithStartPosition);
+ Util.handlePlayButtonAction(playerWrapper);
}
- player.play();
- }
- @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);
+ @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);
}
- // Play as requested either way.
- Util.handlePlayButtonAction(player);
- }
- },
- 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 {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
index d9e177dc8a..b63abdad8d 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
@@ -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());
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
index 6f449112c2..bc6be4088b 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
@@ -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);
}));
}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
index 192ea93ba6..7c9446b188 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
@@ -1073,8 +1073,7 @@ public class MediaSessionCallbackTest {
}
@Test
- public void onPlay_withEmptyTimelinePlaybackResumptionOn_callsOnGetPlaybackResumptionPlaylist()
- throws Exception {
+ public void onPlay_withEmptyTimeline_callsOnGetPlaybackResumptionPlaylist() throws Exception {
List 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()
- throws Exception {
- player.startMediaItemIndex = 7;
- player.startPositionMs = 321L;
+ public void
+ onPlay_withEmptyTimelineWithoutCommandGetCurrentMediaItem_doesNotTriggerPlaybackResumption()
+ throws Exception {
+ 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 mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture 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 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();
}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java
index 484ccb9f0a..65ebd75a8b 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java
@@ -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 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 mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture 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 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);
}