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: #8046

#minor-release

PiperOrigin-RevId: 534945089
  • Loading branch information
tonihei committed May 25, 2023
1 parent d8e30c3 commit 6309b11
Show file tree
Hide file tree
Showing 10 changed files with 931 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,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 @@ -76,6 +76,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 @@ -41,7 +41,6 @@
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoSize;
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 @@ -2147,10 +2146,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 @@ -2169,10 +2165,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 @@ -2144,16 +2144,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 @@ -3185,6 +3212,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 6309b11

Please sign in to comment.