Commit 6a9cb2cb by tonihei Committed by Tofunmi Adigun-Hameed

Implement Player.replaceMediaItem(s)

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 6309b11792b05306d004747af35d67f32a352782)
parent 4a125467
...@@ -320,6 +320,18 @@ public final class CastPlayer extends BasePlayer { ...@@ -320,6 +320,18 @@ public final class CastPlayer extends BasePlayer {
} }
@Override @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) { public void removeMediaItems(int fromIndex, int toIndex) {
checkArgument(fromIndex >= 0 && toIndex >= fromIndex); checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
int playlistSize = currentTimeline.getWindowCount(); int playlistSize = currentTimeline.getWindowCount();
......
...@@ -699,6 +699,38 @@ public class CastPlayerTest { ...@@ -699,6 +699,38 @@ public class CastPlayerTest {
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null); .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") @SuppressWarnings("ConstantConditions")
@Test @Test
public void addMediaItems_fillsTimeline() { public void addMediaItems_fillsTimeline() {
......
...@@ -77,6 +77,12 @@ public abstract class BasePlayer implements Player { ...@@ -77,6 +77,12 @@ public abstract class BasePlayer implements Player {
} }
@Override @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) { public final void removeMediaItem(int index) {
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1); removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
} }
......
...@@ -41,7 +41,6 @@ import com.google.android.exoplayer2.util.Size; ...@@ -41,7 +41,6 @@ import com.google.android.exoplayer2.util.Size;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -2147,10 +2146,7 @@ public interface Player { ...@@ -2147,10 +2146,7 @@ public interface Player {
* of the playlist, the request is ignored. * of the playlist, the request is ignored.
* @param mediaItem The new {@link MediaItem}. * @param mediaItem The new {@link MediaItem}.
*/ */
default void replaceMediaItem(int index, MediaItem mediaItem) { void replaceMediaItem(int index, MediaItem mediaItem);
replaceMediaItems(
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
}
/** /**
* Replaces the media items at the given range of the playlist. * Replaces the media items at the given range of the playlist.
...@@ -2169,10 +2165,7 @@ public interface Player { ...@@ -2169,10 +2165,7 @@ public interface Player {
* larger than the size of the playlist, items up to the end of the playlist are replaced. * 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. * @param mediaItems The {@linkplain MediaItem media items} to replace the range with.
*/ */
default void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) { void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);
addMediaItems(toIndex, mediaItems);
removeMediaItems(fromIndex, toIndex);
}
/** /**
* Removes the media item at the given index of the playlist. * Removes the media item at the given index of the playlist.
......
...@@ -2145,15 +2145,42 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -2145,15 +2145,42 @@ public abstract class SimpleBasePlayer extends BasePlayer {
} }
@Override @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) { public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
addMediaItems(toIndex, mediaItems); verifyApplicationThreadAndInitState();
removeMediaItems(fromIndex, toIndex); 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 @Override
...@@ -3186,6 +3213,27 @@ public abstract class SimpleBasePlayer extends BasePlayer { ...@@ -3186,6 +3213,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
} }
/** /**
* 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}. * Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
* *
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. * <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
......
...@@ -722,6 +722,38 @@ import java.util.concurrent.TimeoutException; ...@@ -722,6 +722,38 @@ import java.util.concurrent.TimeoutException;
} }
@Override @Override
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
verifyApplicationThread();
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
int playlistSize = mediaSourceHolderSnapshots.size();
if (fromIndex > playlistSize) {
// Do nothing.
return;
}
toIndex = min(toIndex, playlistSize);
List<MediaSource> mediaSources = createMediaSources(mediaItems);
if (mediaSourceHolderSnapshots.isEmpty()) {
// Handle initial items in a playlist as a set operation to ensure state changes and initial
// position are updated correctly.
setMediaSources(mediaSources, /* resetPosition= */ maskingWindowIndex == C.INDEX_UNSET);
return;
}
PlaybackInfo newPlaybackInfo = addMediaSourcesInternal(playbackInfo, toIndex, mediaSources);
newPlaybackInfo = removeMediaItemsInternal(newPlaybackInfo, fromIndex, toIndex);
boolean positionDiscontinuity =
!newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid);
updatePlaybackInfo(
newPlaybackInfo,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
positionDiscontinuity,
DISCONTINUITY_REASON_REMOVE,
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
/* ignored */ C.INDEX_UNSET,
/* repeatCurrentMediaItem= */ false);
}
@Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
verifyApplicationThread(); verifyApplicationThread();
this.shuffleOrder = shuffleOrder; this.shuffleOrder = shuffleOrder;
......
...@@ -934,6 +934,12 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -934,6 +934,12 @@ public class SimpleExoPlayer extends BasePlayer
} }
@Override @Override
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
blockUntilConstructorFinished();
player.replaceMediaItems(fromIndex, toIndex, mediaItems);
}
@Override
public void removeMediaItems(int fromIndex, int toIndex) { public void removeMediaItems(int fromIndex, int toIndex) {
blockUntilConstructorFinished(); blockUntilConstructorFinished();
player.removeMediaItems(fromIndex, toIndex); player.removeMediaItems(fromIndex, toIndex);
......
...@@ -101,6 +101,11 @@ public class StubPlayer extends BasePlayer { ...@@ -101,6 +101,11 @@ public class StubPlayer extends BasePlayer {
} }
@Override @Override
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
throw new UnsupportedOperationException();
}
@Override
public void removeMediaItems(int fromIndex, int toIndex) { public void removeMediaItems(int fromIndex, int toIndex) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment