mediaItems;
+ /**
+ * Index to start playing at in {@link MediaItem} list.
+ *
+ * If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
+ * startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then the
+ * requested start is the default index and position. If only startIndex is {@link
+ * androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET}, then the requested start is the
+ * {@linkplain Player#getCurrentMediaItemIndex() current index} and {@linkplain
+ * Player#getContentPosition() position}.
+ */
+ public final int startIndex;
+ /**
+ * Position to start playing from in starting media item.
+ *
+ *
If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
+ * startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then the
+ * requested start is the default start index that takes into account whether {@link
+ * Player#getShuffleModeEnabled() shuffling is enabled} and the {@linkplain
+ * Timeline.Window#defaultPositionUs} default position}. If only startIndex is {@link
+ * androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET}, then the requested start is the
+ * {@linkplain Player#getCurrentMediaItemIndex() current index} and {@linkplain
+ * Player#getContentPosition() position}.
+ */
+ public final long startPositionMs;
+
+ /**
+ * Create an instance.
+ *
+ * @param mediaItems List of {@link MediaItem media items}.
+ * @param startIndex Index to start playing at in {@link MediaItem} list.
+ * @param startPositionMs Position to start playing from in starting media item.
+ */
+ public MediaItemsWithStartPosition(
+ List mediaItems, int startIndex, long startPositionMs) {
+ this.mediaItems = ImmutableList.copyOf(mediaItems);
+ this.startIndex = startIndex;
+ this.startPositionMs = startPositionMs;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MediaItemsWithStartPosition)) {
+ return false;
+ }
+
+ MediaItemsWithStartPosition other = (MediaItemsWithStartPosition) obj;
+
+ return mediaItems.equals(other.mediaItems)
+ && Util.areEqual(startIndex, other.startIndex)
+ && Util.areEqual(startPositionMs, other.startPositionMs);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mediaItems.hashCode();
+ result = 31 * result + startIndex;
+ result = 31 * result + Longs.hashCode(startPositionMs);
+ return result;
+ }
}
/**
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 180030adf1..9d32853015 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
@@ -69,6 +69,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaSession.ControllerCb;
import androidx.media3.session.MediaSession.ControllerInfo;
+import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
import androidx.media3.session.SequencedFutureManager.SequencedFuture;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
@@ -524,6 +525,13 @@ import org.checkerframework.checker.initialization.qual.Initialized;
"onAddMediaItems must return a non-null future");
}
+ protected ListenableFuture onSetMediaItemsOnHandler(
+ ControllerInfo controller, List mediaItems, int startIndex, long startPositionMs) {
+ return checkNotNull(
+ callback.onSetMediaItems(instance, controller, mediaItems, startIndex, startPositionMs),
+ "onSetMediaItems must return a non-null future");
+ }
+
protected boolean isReleased() {
synchronized (lock) {
return closed;
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 d49b5b1666..0c467ac81e 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
@@ -85,6 +85,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaSession.ControllerCb;
import androidx.media3.session.MediaSession.ControllerInfo;
+import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
import androidx.media3.session.SessionCommand.CommandCode;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
@@ -711,18 +712,26 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
dispatchSessionTaskWithPlayerCommand(
COMMAND_SET_MEDIA_ITEM,
controller -> {
- ListenableFuture> mediaItemsFuture =
- sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem));
+ ListenableFuture mediaItemsFuture =
+ sessionImpl.onSetMediaItemsOnHandler(
+ controller, ImmutableList.of(mediaItem), C.INDEX_UNSET, C.TIME_UNSET);
Futures.addCallback(
mediaItemsFuture,
- new FutureCallback>() {
+ new FutureCallback() {
@Override
- public void onSuccess(List mediaItems) {
+ public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
postOrRun(
sessionImpl.getApplicationHandler(),
() -> {
PlayerWrapper player = sessionImpl.getPlayerWrapper();
- player.setMediaItems(mediaItems);
+ if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
+ && mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
+ MediaUtils.setMediaItemsWithDefaultStartIndexAndPosition(
+ player, mediaItemsWithStartPosition);
+ } else {
+ MediaUtils.setMediaItemsWithSpecifiedStartIndexAndPosition(
+ player, mediaItemsWithStartPosition);
+ }
@Player.State int playbackState = player.getPlaybackState();
if (playbackState == Player.STATE_IDLE) {
player.prepareIfCommandAvailable();
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 6ae74ccbbf..ffd6e78621 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
@@ -63,6 +63,7 @@ import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import androidx.media.MediaSessionManager;
import androidx.media3.common.BundleListRetriever;
+import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.MediaMetadata;
@@ -79,6 +80,7 @@ import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
import androidx.media3.session.MediaSession.ControllerCb;
import androidx.media3.session.MediaSession.ControllerInfo;
+import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
import androidx.media3.session.SessionCommand.CommandCode;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
@@ -202,6 +204,30 @@ import java.util.concurrent.ExecutionException;
};
}
+ private static
+ SessionTask, K> handleMediaItemsWithStartPositionWhenReady(
+ SessionTask, K> mediaItemsTask,
+ MediaItemsWithStartPositionPlayerTask mediaItemPlayerTask) {
+ return (sessionImpl, controller, sequenceNumber) -> {
+ if (sessionImpl.isReleased()) {
+ return Futures.immediateFuture(
+ new SessionResult(SessionResult.RESULT_ERROR_SESSION_DISCONNECTED));
+ }
+ return transformFutureAsync(
+ mediaItemsTask.run(sessionImpl, controller, sequenceNumber),
+ mediaItemsWithStartPosition ->
+ postOrRunWithCompletion(
+ sessionImpl.getApplicationHandler(),
+ () -> {
+ if (!sessionImpl.isReleased()) {
+ mediaItemPlayerTask.run(
+ sessionImpl.getPlayerWrapper(), controller, mediaItemsWithStartPosition);
+ }
+ },
+ new SessionResult(SessionResult.RESULT_SUCCESS)));
+ };
+ }
+
private static void sendLibraryResult(
ControllerInfo controller, int sequenceNumber, LibraryResult> result) {
try {
@@ -851,26 +877,8 @@ import java.util.concurrent.ExecutionException;
@Override
public void setMediaItem(
@Nullable IMediaController caller, int sequenceNumber, @Nullable Bundle mediaItemBundle) {
- if (caller == null || mediaItemBundle == null) {
- return;
- }
- MediaItem mediaItem;
- try {
- mediaItem = MediaItem.CREATOR.fromBundle(mediaItemBundle);
- } catch (RuntimeException e) {
- Log.w(TAG, "Ignoring malformed Bundle for MediaItem", e);
- return;
- }
- queueSessionTaskWithPlayerCommand(
- caller,
- sequenceNumber,
- COMMAND_SET_MEDIA_ITEM,
- sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
- (sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem)),
- (playerWrapper, controller, mediaItems) ->
- playerWrapper.setMediaItems(mediaItems))));
+ setMediaItemWithResetPosition(
+ caller, sequenceNumber, mediaItemBundle, /* resetPosition= */ true);
}
@Override
@@ -894,11 +902,35 @@ import java.util.concurrent.ExecutionException;
sequenceNumber,
COMMAND_SET_MEDIA_ITEM,
sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
+ handleMediaItemsWithStartPositionWhenReady(
(sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem)),
- (player, controller, mediaItems) ->
- player.setMediaItems(mediaItems, /* startIndex= */ 0, startPositionMs))));
+ sessionImpl.onSetMediaItemsOnHandler(
+ controller,
+ ImmutableList.of(mediaItem),
+ /* startIndex= */ 0,
+ startPositionMs),
+ (player, controller, mediaItemsWithStartPosition) -> {
+ if (player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)) {
+ if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
+ && mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems, /* resetPosition= */ true);
+ } else {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems,
+ mediaItemsWithStartPosition.startIndex,
+ mediaItemsWithStartPosition.startPositionMs);
+ }
+ } else {
+ if (!mediaItemsWithStartPosition.mediaItems.isEmpty()) {
+ player.setMediaItem(
+ mediaItemsWithStartPosition.mediaItems.get(0),
+ mediaItemsWithStartPosition.startPositionMs);
+ } else {
+ player.clearMediaItems();
+ }
+ }
+ })));
}
@Override
@@ -922,11 +954,27 @@ import java.util.concurrent.ExecutionException;
sequenceNumber,
COMMAND_SET_MEDIA_ITEM,
sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
+ handleMediaItemsWithStartPositionWhenReady(
(sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem)),
- (player, controller, mediaItems) ->
- player.setMediaItems(mediaItems, resetPosition))));
+ sessionImpl.onSetMediaItemsOnHandler(
+ controller,
+ ImmutableList.of(mediaItem),
+ resetPosition
+ ? C.INDEX_UNSET
+ : sessionImpl.getPlayerWrapper().getCurrentMediaItemIndex(),
+ resetPosition
+ ? C.TIME_UNSET
+ : sessionImpl.getPlayerWrapper().getCurrentPosition()),
+ (player, controller, mediaItemsWithStartPosition) -> {
+ if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
+ && mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
+ MediaUtils.setMediaItemsWithDefaultStartIndexAndPosition(
+ player, mediaItemsWithStartPosition);
+ } else {
+ MediaUtils.setMediaItemsWithSpecifiedStartIndexAndPosition(
+ player, mediaItemsWithStartPosition);
+ }
+ })));
}
@Override
@@ -934,29 +982,8 @@ import java.util.concurrent.ExecutionException;
@Nullable IMediaController caller,
int sequenceNumber,
@Nullable IBinder mediaItemsRetriever) {
- if (caller == null || mediaItemsRetriever == null) {
- return;
- }
- List mediaItemList;
- try {
- mediaItemList =
- BundleableUtil.fromBundleList(
- MediaItem.CREATOR, BundleListRetriever.getList(mediaItemsRetriever));
- } catch (RuntimeException e) {
- Log.w(TAG, "Ignoring malformed Bundle for MediaItem", e);
- return;
- }
-
- queueSessionTaskWithPlayerCommand(
- caller,
- sequenceNumber,
- COMMAND_CHANGE_MEDIA_ITEMS,
- sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
- (sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, mediaItemList),
- (playerWrapper, controller, mediaItems) ->
- playerWrapper.setMediaItems(mediaItems))));
+ setMediaItemsWithResetPosition(
+ caller, sequenceNumber, mediaItemsRetriever, /* resetPosition= */ true);
}
@Override
@@ -982,11 +1009,29 @@ import java.util.concurrent.ExecutionException;
sequenceNumber,
COMMAND_CHANGE_MEDIA_ITEMS,
sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
+ handleMediaItemsWithStartPositionWhenReady(
(sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, mediaItemList),
- (player, controller, mediaItems) ->
- player.setMediaItems(mediaItems, resetPosition))));
+ sessionImpl.onSetMediaItemsOnHandler(
+ controller,
+ mediaItemList,
+ resetPosition
+ ? C.INDEX_UNSET
+ : sessionImpl.getPlayerWrapper().getCurrentMediaItemIndex(),
+ resetPosition
+ ? C.TIME_UNSET
+ : sessionImpl.getPlayerWrapper().getCurrentPosition()),
+ (player, controller, mediaItemsWithStartPosition) -> {
+ if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
+ && mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems, /* resetPosition= */ true);
+ } else {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems,
+ mediaItemsWithStartPosition.startIndex,
+ mediaItemsWithStartPosition.startPositionMs);
+ }
+ })));
}
@Override
@@ -1013,11 +1058,29 @@ import java.util.concurrent.ExecutionException;
sequenceNumber,
COMMAND_CHANGE_MEDIA_ITEMS,
sendSessionResultWhenReady(
- handleMediaItemsWhenReady(
+ handleMediaItemsWithStartPositionWhenReady(
(sessionImpl, controller, sequenceNum) ->
- sessionImpl.onAddMediaItemsOnHandler(controller, mediaItemList),
- (player, controller, mediaItems) ->
- player.setMediaItems(mediaItems, startIndex, startPositionMs))));
+ sessionImpl.onSetMediaItemsOnHandler(
+ controller,
+ mediaItemList,
+ (startIndex == C.INDEX_UNSET)
+ ? sessionImpl.getPlayerWrapper().getCurrentMediaItemIndex()
+ : startIndex,
+ (startIndex == C.INDEX_UNSET)
+ ? sessionImpl.getPlayerWrapper().getCurrentPosition()
+ : startPositionMs),
+ (player, controller, mediaItemsWithStartPosition) -> {
+ if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
+ && mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems, /* resetPosition= */ true);
+ } else {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems,
+ mediaItemsWithStartPosition.startIndex,
+ mediaItemsWithStartPosition.startPositionMs);
+ }
+ })));
}
@Override
@@ -1624,6 +1687,13 @@ import java.util.concurrent.ExecutionException;
void run(PlayerWrapper player, ControllerInfo controller);
}
+ private interface MediaItemsWithStartPositionPlayerTask {
+ void run(
+ PlayerWrapper player,
+ ControllerInfo controller,
+ MediaItemsWithStartPosition mediaItemsWithStartPosition);
+ }
+
/* package */ static final class Controller2Cb implements ControllerCb {
private final IMediaController iController;
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
index 0d61696904..6f3233b520 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
@@ -1368,5 +1368,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
: Util.constrainValue((int) ((bufferedPositionMs * 100) / durationMs), 0, 100);
}
+ public static void setMediaItemsWithDefaultStartIndexAndPosition(
+ PlayerWrapper player, MediaSession.MediaItemsWithStartPosition mediaItemsWithStartPosition) {
+ if (player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)) {
+ player.setMediaItems(mediaItemsWithStartPosition.mediaItems, /* resetPosition= */ true);
+ } else if (!mediaItemsWithStartPosition.mediaItems.isEmpty()) {
+ player.setMediaItem(mediaItemsWithStartPosition.mediaItems.get(0), /* resetPosition= */ true);
+ } else {
+ player.clearMediaItems();
+ }
+ }
+
+ public static void setMediaItemsWithSpecifiedStartIndexAndPosition(
+ PlayerWrapper player, MediaSession.MediaItemsWithStartPosition mediaItemsWithStartPosition) {
+ if (player.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)) {
+ player.setMediaItems(
+ mediaItemsWithStartPosition.mediaItems,
+ mediaItemsWithStartPosition.startIndex,
+ mediaItemsWithStartPosition.startPositionMs);
+ } else if (!mediaItemsWithStartPosition.mediaItems.isEmpty()) {
+ player.setMediaItem(
+ mediaItemsWithStartPosition.mediaItems.get(0),
+ mediaItemsWithStartPosition.startPositionMs);
+ } else {
+ player.clearMediaItems();
+ }
+ }
+
private MediaUtils() {}
}
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 1891ee001d..37ea204459 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
@@ -29,6 +29,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
+import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.Player;
@@ -368,14 +369,14 @@ public class MediaSessionCallbackTest {
controllerTestRule.createRemoteController(session.getToken());
controller.setMediaItem(mediaItem);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).containsExactly(mediaItem);
assertThat(player.mediaItems).containsExactly(updateMediaItemWithLocalConfiguration(mediaItem));
}
@Test
- public void onAddMediaItems_withSetMediaItemWithIndex() throws Exception {
+ public void onAddMediaItems_withSetMediaItemWithStartPosition() throws Exception {
MediaItem mediaItem = createMediaItem("mediaId");
AtomicReference> requestedMediaItems = new AtomicReference<>();
MediaSession.Callback callback =
@@ -452,7 +453,7 @@ public class MediaSessionCallbackTest {
controllerTestRule.createRemoteController(session.getToken());
controller.setMediaItems(mediaItems);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
assertThat(player.mediaItems)
@@ -461,7 +462,7 @@ public class MediaSessionCallbackTest {
}
@Test
- public void onAddMediaItems_withSetMediaItemsWithStartPosition() throws Exception {
+ public void onAddMediaItems_withSetMediaItemsWithStartIndex() throws Exception {
List mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
AtomicReference> requestedMediaItems = new AtomicReference<>();
MediaSession.Callback callback =
@@ -645,6 +646,170 @@ public class MediaSessionCallbackTest {
assertThat(player.index).isEqualTo(1);
}
+ @Test
+ public void onSetMediaItems_withSetMediaItemWithStartPosition_callsPlayerWithStartIndex()
+ throws Exception {
+ MediaItem mediaItem = createMediaItem("mediaId");
+ AtomicReference> requestedMediaItems = new AtomicReference<>();
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ requestedMediaItems.set(mediaItems);
+
+ return Futures.immediateFuture(
+ new MediaSession.MediaItemsWithStartPosition(
+ updateMediaItemsWithLocalConfiguration(mediaItems),
+ startIndex,
+ /* startPosition = testStartPosition * 2 */ 200));
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player).setCallback(callback).build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.setMediaItem(mediaItem, /* startPositionMs= */ 100);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
+
+ assertThat(requestedMediaItems.get()).containsExactly(mediaItem);
+ assertThat(player.mediaItems).containsExactly(updateMediaItemWithLocalConfiguration(mediaItem));
+ assertThat(player.startMediaItemIndex).isEqualTo(0);
+ assertThat(player.startPositionMs).isEqualTo(200);
+ }
+
+ @Test
+ public void onSetMediaItems_withSetMediaItemsWithStartIndex_callsPlayerWithStartIndex()
+ throws Exception {
+ List mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
+ AtomicReference> requestedMediaItems = new AtomicReference<>();
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ requestedMediaItems.set(mediaItems);
+
+ return Futures.immediateFuture(
+ new MediaSession.MediaItemsWithStartPosition(
+ updateMediaItemsWithLocalConfiguration(mediaItems),
+ startIndex,
+ /* startPosition= */ 200));
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player).setCallback(callback).build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 100);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
+
+ assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
+ assertThat(player.mediaItems)
+ .containsExactlyElementsIn(updateMediaItemsWithLocalConfiguration(mediaItems))
+ .inOrder();
+ assertThat(player.startMediaItemIndex).isEqualTo(1);
+ assertThat(player.startPositionMs).isEqualTo(200);
+ }
+
+ @Test
+ public void onSetMediaItems_withIndexPositionUnset_callsPlayerWithResetPosition()
+ throws Exception {
+ List mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
+ AtomicReference> requestedMediaItems = new AtomicReference<>();
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ requestedMediaItems.set(mediaItems);
+
+ return Futures.immediateFuture(
+ new MediaSession.MediaItemsWithStartPosition(
+ updateMediaItemsWithLocalConfiguration(mediaItems),
+ C.INDEX_UNSET,
+ C.TIME_UNSET));
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player).setCallback(callback).build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+
+ controller.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 100);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
+
+ assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
+ assertThat(player.mediaItems)
+ .containsExactlyElementsIn(updateMediaItemsWithLocalConfiguration(mediaItems))
+ .inOrder();
+ assertThat(player.resetPosition).isEqualTo(true);
+ }
+
+ @Test
+ public void onSetMediaItems_withStartIndexUnset_callsPlayerWithCurrentIndexAndPosition()
+ throws Exception {
+ List mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
+ AtomicReference> requestedMediaItems = new AtomicReference<>();
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ requestedMediaItems.set(mediaItems);
+
+ return Futures.immediateFuture(
+ new MediaSession.MediaItemsWithStartPosition(
+ updateMediaItemsWithLocalConfiguration(mediaItems),
+ startIndex,
+ startPositionMs));
+ }
+ };
+ MediaSession session =
+ sessionTestRule.ensureReleaseAfterTest(
+ new MediaSession.Builder(context, player).setCallback(callback).build());
+ RemoteMediaController controller =
+ controllerTestRule.createRemoteController(session.getToken());
+ controller.setMediaItems(mediaItems, true);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
+
+ // Model that player played to next item. Current media item index and position have changed
+ player.currentMediaItemIndex = 1;
+ player.currentPosition = 200;
+
+ // Re-set media items with start index and position as current index and position
+ controller.setMediaItems(mediaItems, C.INDEX_UNSET, /* startPosition= */ 0);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
+
+ assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
+ assertThat(player.mediaItems)
+ .containsExactlyElementsIn(updateMediaItemsWithLocalConfiguration(mediaItems))
+ .inOrder();
+ assertThat(player.startMediaItemIndex).isEqualTo(1);
+ assertThat(player.startPositionMs).isEqualTo(200);
+ }
+
@Test
public void onConnect() throws Exception {
AtomicReference connectionHints = new AtomicReference<>();
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 504c48ed63..fcdbe1ec5a 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
@@ -961,7 +961,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void prepareFromMediaUri() throws Exception {
+ public void prepareFromMediaUri_withOnAddMediaItems() throws Exception {
Uri mediaUri = Uri.parse("foo://bar");
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -988,7 +988,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().prepareFromUri(mediaUri, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
assertThat(requestedMediaItems.get().get(0).requestMetadata.mediaUri).isEqualTo(mediaUri);
@@ -997,7 +997,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void playFromMediaUri() throws Exception {
+ public void playFromMediaUri_withOnAddMediaItems() throws Exception {
Uri request = Uri.parse("foo://bar");
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -1024,7 +1024,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().playFromUri(request, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
@@ -1034,7 +1034,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void prepareFromMediaId() throws Exception {
+ public void prepareFromMediaId_withOnAddMediaItems() throws Exception {
String request = "media_id";
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -1061,7 +1061,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().prepareFromMediaId(request, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
assertThat(requestedMediaItems.get().get(0).mediaId).isEqualTo(request);
@@ -1070,7 +1070,53 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void playFromMediaId() throws Exception {
+ public void prepareFromMediaId_withOnSetMediaItems_callsPlayerWithStartIndex() throws Exception {
+ String request = "media_id";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ AtomicReference> requestedMediaItems = new AtomicReference<>();
+ MediaItem resolvedMediaItem = MediaItem.fromUri(TEST_URI);
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ requestedMediaItems.set(mediaItems);
+ return executorService.submit(
+ () ->
+ new MediaSession.MediaItemsWithStartPosition(
+ ImmutableList.of(resolvedMediaItem),
+ /* startIndex= */ 2,
+ /* startPositionMs= */ 100));
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("prepareFromMediaId")
+ .setCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().prepareFromMediaId(request, bundle);
+
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
+ assertThat(requestedMediaItems.get()).hasSize(1);
+ assertThat(requestedMediaItems.get().get(0).mediaId).isEqualTo(request);
+ TestUtils.equals(requestedMediaItems.get().get(0).requestMetadata.extras, bundle);
+ assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
+ assertThat(player.startMediaItemIndex).isEqualTo(2);
+ assertThat(player.startPositionMs).isEqualTo(100);
+ }
+
+ @Test
+ public void playFromMediaId_withOnAddMediaItems() throws Exception {
String mediaId = "media_id";
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -1097,7 +1143,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().playFromMediaId(mediaId, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
@@ -1107,7 +1153,49 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void prepareFromSearch() throws Exception {
+ public void playFromMediaId_withOnSetMediaItems_callsPlayerWithStartIndex() throws Exception {
+ String mediaId = "media_id";
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ MediaItem resolvedMediaItem = MediaItem.fromUri(TEST_URI);
+ MediaSession.Callback callback =
+ new MediaSession.Callback() {
+ @Override
+ public ListenableFuture onSetMediaItems(
+ MediaSession mediaSession,
+ ControllerInfo controller,
+ List mediaItems,
+ int startIndex,
+ long startPositionMs) {
+ return executorService.submit(
+ () ->
+ new MediaSession.MediaItemsWithStartPosition(
+ ImmutableList.of(resolvedMediaItem),
+ /* startIndex= */ 2,
+ /* startPositionMs= */ 100));
+ }
+ };
+ session =
+ new MediaSession.Builder(context, player)
+ .setId("playFromMediaId")
+ .setCallback(callback)
+ .build();
+ controller =
+ new RemoteMediaControllerCompat(
+ context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true);
+
+ controller.getTransportControls().playFromMediaId(mediaId, bundle);
+
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
+ assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
+ assertThat(player.startMediaItemIndex).isEqualTo(2);
+ assertThat(player.startPositionMs).isEqualTo(100);
+ }
+
+ @Test
+ public void prepareFromSearch_withOnAddMediaItems() throws Exception {
String query = "test_query";
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -1134,7 +1222,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().prepareFromSearch(query, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
assertThat(requestedMediaItems.get().get(0).requestMetadata.searchQuery).isEqualTo(query);
@@ -1143,7 +1231,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
}
@Test
- public void playFromSearch() throws Exception {
+ public void playFromSearch_withOnAddMediaItems() throws Exception {
String query = "test_query";
Bundle bundle = new Bundle();
bundle.putString("key", "value");
@@ -1170,7 +1258,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().playFromSearch(query, bundle);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(requestedMediaItems.get()).hasSize(1);
@@ -1204,7 +1292,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().prepareFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
}
@@ -1234,7 +1322,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().playFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
@@ -1268,7 +1356,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
controller.getTransportControls().playFromUri(Uri.parse("foo://bar"), Bundle.EMPTY);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse();
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse();
assertThat(player.mediaItems).containsExactly(resolvedMediaItem);
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java
index 2098f6ca29..6ebf428658 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java
@@ -300,7 +300,7 @@ public class MediaSessionPlayerTest {
controller.setMediaItem(item);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems).containsExactly(item);
assertThat(player.startPositionMs).isEqualTo(startPositionMs);
assertThat(player.resetPosition).isEqualTo(resetPosition);
@@ -316,7 +316,7 @@ public class MediaSessionPlayerTest {
controller.setMediaItem(item);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems).containsExactly(item);
assertThat(player.startPositionMs).isEqualTo(startPositionMs);
assertThat(player.resetPosition).isEqualTo(resetPosition);
@@ -332,7 +332,7 @@ public class MediaSessionPlayerTest {
controller.setMediaItem(item);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems).containsExactly(item);
assertThat(player.startPositionMs).isEqualTo(startPositionMs);
assertThat(player.resetPosition).isEqualTo(resetPosition);
@@ -344,9 +344,9 @@ public class MediaSessionPlayerTest {
controller.setMediaItems(items);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems).isEqualTo(items);
- assertThat(player.resetPosition).isFalse();
+ assertThat(player.resetPosition).isTrue();
}
@Test
@@ -382,7 +382,7 @@ public class MediaSessionPlayerTest {
controller.setMediaItems(list);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems.size()).isEqualTo(listSize);
for (int i = 0; i < listSize; i++) {
assertThat(player.mediaItems.get(i).mediaId).isEqualTo(list.get(i).mediaId);
@@ -395,7 +395,7 @@ public class MediaSessionPlayerTest {
// Make client app to generate a long list, and call setMediaItems() with it.
controller.createAndSetFakeMediaItems(listSize);
- player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS);
+ player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
assertThat(player.mediaItems).isNotNull();
assertThat(player.mediaItems.size()).isEqualTo(listSize);
for (int i = 0; i < listSize; i++) {
@@ -824,7 +824,7 @@ public class MediaSessionPlayerTest {
controller.setMediaItemsPreparePlayAddItemsSeek(initialItems, addedItems, /* seekIndex= */ 3);
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
boolean setMediaItemsCalledBeforePrepare =
- player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS);
+ player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS);
boolean addMediaItemsCalledBeforeSeek =
player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS);