Skip to content

Commit

Permalink
Implement Player.replaceMediaItem(s)
Browse files Browse the repository at this point in the history
This change moves the default logic into the actual Player
implementations, but does not introduce any behavior changes compared
to addMediaItems+removeMediaItems except to make the updates "atomic"
in ExoPlayerImpl, SimpleBasePlayer and MediaController. It also
provides backwards compatbility for cases where Players don't support
the operation.

Issue: google/ExoPlayer#8046

#minor-release

PiperOrigin-RevId: 534945089
(cherry picked from commit 2c07468)
  • Loading branch information
tonihei authored and tof-tof committed May 26, 2023
1 parent 638dee4 commit f2edc0b
Show file tree
Hide file tree
Showing 26 changed files with 1,974 additions and 58 deletions.
6 changes: 4 additions & 2 deletions api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -751,8 +751,8 @@ package androidx.media3.common {
method public void removeListener(androidx.media3.common.Player.Listener);
method public void removeMediaItem(int);
method public void removeMediaItems(int, int);
method public default void replaceMediaItem(int, androidx.media3.common.MediaItem);
method public default void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
method public void replaceMediaItem(int, androidx.media3.common.MediaItem);
method public void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
method public void seekBack();
method public void seekForward();
method public void seekTo(long);
Expand Down Expand Up @@ -1603,6 +1603,8 @@ package androidx.media3.session {
method public final void removeListener(androidx.media3.common.Player.Listener);
method public final void removeMediaItem(int);
method public final void removeMediaItems(int, int);
method public final void replaceMediaItem(int, androidx.media3.common.MediaItem);
method public final void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
method public final void seekBack();
method public final void seekForward();
method public final void seekTo(long);
Expand Down
12 changes: 12 additions & 0 deletions libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
moveMediaItemsInternal(uids, fromIndex, newIndex);
}

@Override
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
int playlistSize = currentTimeline.getWindowCount();
if (fromIndex > playlistSize) {
return;
}
toIndex = min(toIndex, playlistSize);
addMediaItems(toIndex, mediaItems);
removeMediaItems(fromIndex, toIndex);
}

@Override
public void removeMediaItems(int fromIndex, int toIndex) {
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,38 @@ public void clearMediaItems_callsRemoteMediaClient() {
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
}

@Test
public void replaceMediaItems_callsRemoteMediaClient() {
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
// Add two items.
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
String uri = "http://www.google.com/video3";
MediaItem anotherMediaItem =
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build();
ImmutableList<MediaItem> newPlaylist = ImmutableList.of(mediaItems.get(0), anotherMediaItem);

// Replace item at position 1.
castPlayer.replaceMediaItems(
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of(anotherMediaItem));
updateTimeLine(
newPlaylist,
/* mediaQueueItemIds= */ new int[] {mediaQueueItemIds[0], 123},
/* currentItemId= */ 123);

verify(mockRemoteMediaClient, times(2))
.queueInsertItems(queueItemsArgumentCaptor.capture(), anyInt(), any());
verify(mockRemoteMediaClient).queueRemoveItems(new int[] {2}, /* customData= */ null);
assertThat(queueItemsArgumentCaptor.getAllValues().get(1)[0])
.isEqualTo(mediaItemConverter.toMediaQueueItem(anotherMediaItem));
Timeline.Window currentWindow =
castPlayer
.getCurrentTimeline()
.getWindow(castPlayer.getCurrentMediaItemIndex(), new Timeline.Window());
assertThat(currentWindow.uid).isEqualTo(123);
assertThat(currentWindow.mediaItem).isEqualTo(anotherMediaItem);
}

@SuppressWarnings("ConstantConditions")
@Test
public void addMediaItems_fillsTimeline() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public final void moveMediaItem(int currentIndex, int newIndex) {
}
}

@Override
public final void replaceMediaItem(int index, MediaItem mediaItem) {
replaceMediaItems(
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
}

@Override
public final void removeMediaItem(int index) {
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -2158,10 +2157,7 @@ default void onMetadata(Metadata metadata) {}
* of the playlist, the request is ignored.
* @param mediaItem The new {@link MediaItem}.
*/
default void replaceMediaItem(int index, MediaItem mediaItem) {
replaceMediaItems(
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
}
void replaceMediaItem(int index, MediaItem mediaItem);

/**
* Replaces the media items at the given range of the playlist.
Expand All @@ -2180,10 +2176,7 @@ default void replaceMediaItem(int index, MediaItem mediaItem) {
* larger than the size of the playlist, items up to the end of the playlist are replaced.
* @param mediaItems The {@linkplain MediaItem media items} to replace the range with.
*/
default void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
addMediaItems(toIndex, mediaItems);
removeMediaItems(fromIndex, toIndex);
}
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);

/**
* Removes the media item at the given index of the playlist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2141,16 +2141,43 @@ public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
});
}

@Override
public final void replaceMediaItem(int index, MediaItem mediaItem) {
replaceMediaItems(
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
}

@Override
public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
addMediaItems(toIndex, mediaItems);
removeMediaItems(fromIndex, toIndex);
verifyApplicationThreadAndInitState();
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
State state = this.state;
int playlistSize = state.playlist.size();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || fromIndex > playlistSize) {
return;
}
int correctedToIndex = min(toIndex, playlistSize);
updateStateForPendingOperation(
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
for (int i = 0; i < mediaItems.size(); i++) {
placeholderPlaylist.add(
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
}
State updatedState;
if (!state.playlist.isEmpty()) {
updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period);
} else {
// Handle initial position update when these are the first items added to the playlist.
updatedState =
getStateWithNewPlaylistAndPosition(
state,
placeholderPlaylist,
state.currentMediaItemIndex,
state.contentPositionMsSupplier.get());
}
if (fromIndex < correctedToIndex) {
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period);
} else {
return updatedState;
}
});
}

@Override
Expand Down Expand Up @@ -3182,6 +3209,27 @@ protected ListenableFuture<?> handleMoveMediaItems(int fromIndex, int toIndex, i
throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS");
}

/**
* Handles calls to {@link Player#replaceMediaItem} and {@link Player#replaceMediaItems}.
*
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
*
* @param fromIndex The start index of the items to replace. The index is in the range 0 &lt;=
* {@code fromIndex} &lt; {@link #getMediaItemCount()}.
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
* range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link #getMediaItemCount()}.
* @param mediaItems The media items to replace the specified range with.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ForOverride
protected ListenableFuture<?> handleReplaceMediaItems(
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
ListenableFuture<?> addFuture = handleAddMediaItems(toIndex, mediaItems);
ListenableFuture<?> removeFuture = handleRemoveMediaItems(fromIndex, toIndex);
return Util.transformFutureAsync(addFuture, unused -> removeFuture);
}

/**
* Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
*
Expand Down
Loading

0 comments on commit f2edc0b

Please sign in to comment.