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
This commit is contained in:
parent
cf0334d793
commit
2c07468908
6
api.txt
6
api.txt
@ -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);
|
||||
@ -1607,6 +1607,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);
|
||||
|
@ -321,6 +321,18 @@ public final class CastPlayer extends BasePlayer {
|
||||
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);
|
||||
|
@ -699,6 +699,38 @@ public class CastPlayerTest {
|
||||
.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() {
|
||||
|
@ -78,6 +78,12 @@ public abstract class BasePlayer implements Player {
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@ -37,7 +37,6 @@ import androidx.media3.common.util.Size;
|
||||
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;
|
||||
@ -2158,10 +2157,7 @@ public interface Player {
|
||||
* 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.
|
||||
@ -2180,10 +2176,7 @@ public interface Player {
|
||||
* 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.
|
||||
|
@ -2141,16 +2141,43 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
@ -3182,6 +3209,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
||||
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 <=
|
||||
* {@code fromIndex} < {@link #getMediaItemCount()}.
|
||||
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
|
||||
* range {@code fromIndex} < {@code toIndex} <= {@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}.
|
||||
*
|
||||
|
@ -6868,6 +6868,559 @@ public class SimpleBasePlayerTest {
|
||||
assertThat(callForwarded.get()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_immediateHandling_updatesStateAndInformsListeners() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build()))
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build()))
|
||||
.build();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
private State playerState = state;
|
||||
|
||||
@Override
|
||||
protected State getState() {
|
||||
return playerState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
playerState = updatedState;
|
||||
return Futures.immediateVoidFuture();
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 2,
|
||||
ImmutableList.of(
|
||||
new MediaItem.Builder().setMediaId("3").build(),
|
||||
new MediaItem.Builder().setMediaId("4").build(),
|
||||
new MediaItem.Builder().setMediaId("2").build()));
|
||||
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_asyncHandlingNotReplacingCurrentItem_usesPlaceholderStateAndInformsListeners() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build()))
|
||||
.setCurrentMediaItemIndex(2)
|
||||
.setPlaybackState(Player.STATE_READY)
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 5).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build()))
|
||||
.setCurrentMediaItemIndex(3)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return future.isDone() ? updatedState : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 2,
|
||||
ImmutableList.of(
|
||||
new MediaItem.Builder().setMediaId("4").build(),
|
||||
new MediaItem.Builder().setMediaId("5").build()));
|
||||
|
||||
// Verify placeholder state and listener calls.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(3);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(4);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(1);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("4");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("5");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 3, window);
|
||||
assertThat(window.uid).isEqualTo(3);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(3);
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItem_asyncHandlingReplacingCurrentItem_usesPlaceholderStateAndInformsListeners() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build()))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_READY)
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 4).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build()))
|
||||
.setCurrentMediaItemIndex(2)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return future.isDone() ? updatedState : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItem(/* index= */ 1, new MediaItem.Builder().setMediaId("4").build());
|
||||
|
||||
// Verify placeholder state and listener calls.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(3);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(1);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("4");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window);
|
||||
assertThat(window.uid).isEqualTo(3);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
new MediaItem.Builder().setMediaId("4").build(),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(2);
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_asyncHandlingReplacingCurrentItemWithEmptyListAndSubsequentItem_usesPlaceholderStateAndInformsListeners() {
|
||||
MediaItem testMediaItem = new MediaItem.Builder().setMediaId("3").build();
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3)
|
||||
.setMediaItem(testMediaItem)
|
||||
.build()))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_READY)
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 3).build()))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return future.isDone() ? updatedState : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
|
||||
// Verify placeholder state and listener calls.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(1);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.uid).isEqualTo(3);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(testMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_asyncHandlingReplacingCurrentItemWithEmptyListAndNoSubsequentItem_usesPlaceholderStateAndInformsListeners() {
|
||||
MediaItem testMediaItem = new MediaItem.Builder().setMediaId("1").build();
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1)
|
||||
.setMediaItem(testMediaItem)
|
||||
.build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build()))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_READY)
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build()))
|
||||
.setCurrentMediaItemIndex(0)
|
||||
.setPlaybackState(Player.STATE_ENDED)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return future.isDone() ? updatedState : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
|
||||
// Verify placeholder state and listener calls.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(1);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(1);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(testMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPlaybackStateChanged(Player.STATE_ENDED);
|
||||
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_ENDED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0);
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_asyncHandlingFromPreparedEmpty_usesPlaceholderStateAndInformsListeners() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(ImmutableList.of())
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_ENDED)
|
||||
.build();
|
||||
State updatedState =
|
||||
state
|
||||
.buildUpon()
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build()))
|
||||
.setPlaybackState(Player.STATE_BUFFERING)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return future.isDone() ? updatedState : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 0,
|
||||
/* toIndex= */ 0,
|
||||
ImmutableList.of(
|
||||
new MediaItem.Builder().setMediaId("1").build(),
|
||||
new MediaItem.Builder().setMediaId("2").build()));
|
||||
|
||||
// Verify placeholder state and listener calls.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("1");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("2");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
new MediaItem.Builder().setMediaId("2").build(),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING);
|
||||
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline()).isEqualTo(updatedState.timeline);
|
||||
verify(listener)
|
||||
.onTimelineChanged(updatedState.timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_asyncHandlingFromEmptyToEmpty_usesPlaceholderStateAndInformsListeners() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(ImmutableList.of())
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_ENDED)
|
||||
.build();
|
||||
SettableFuture<?> future = SettableFuture.create();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
return future;
|
||||
}
|
||||
};
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addListener(listener);
|
||||
|
||||
player.replaceMediaItems(/* fromIndex= */ 0, /* toIndex= */ 0, ImmutableList.of());
|
||||
|
||||
// Verify placeholder state is a no-op and no listeners are called.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().isEmpty()).isTrue();
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
future.set(null);
|
||||
|
||||
// Verify actual state update is equally a no-op.
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
verifyNoMoreInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItem_withoutAvailableCommand_isNotForwarded() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(
|
||||
new Commands.Builder()
|
||||
.addAllCommands()
|
||||
.remove(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build()))
|
||||
.build();
|
||||
AtomicBoolean callForwarded = new AtomicBoolean();
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
callForwarded.set(true);
|
||||
return Futures.immediateVoidFuture();
|
||||
}
|
||||
};
|
||||
|
||||
player.replaceMediaItem(/* index= */ 0, new MediaItem.Builder().setMediaId("id").build());
|
||||
|
||||
assertThat(callForwarded.get()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_withInvalidToIndex_replacesToEndOfPlaylist() {
|
||||
State state =
|
||||
new State.Builder()
|
||||
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||
.setPlaylist(
|
||||
ImmutableList.of(
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2).build()))
|
||||
.build();
|
||||
AtomicInteger fromIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET);
|
||||
AtomicInteger toIndexInHandleMethod = new AtomicInteger(C.INDEX_UNSET);
|
||||
SimpleBasePlayer player =
|
||||
new SimpleBasePlayer(Looper.myLooper()) {
|
||||
@Override
|
||||
protected State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
fromIndexInHandleMethod.set(fromIndex);
|
||||
toIndexInHandleMethod.set(toIndex);
|
||||
return SettableFuture.create();
|
||||
}
|
||||
};
|
||||
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 5000,
|
||||
ImmutableList.of(new MediaItem.Builder().setMediaId("id").build()));
|
||||
|
||||
assertThat(fromIndexInHandleMethod.get()).isEqualTo(1);
|
||||
assertThat(toIndexInHandleMethod.get()).isEqualTo(2);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(1);
|
||||
assertThat(window.isPlaceholder).isFalse();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.mediaId).isEqualTo("id");
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||
@Test
|
||||
public void seekTo_immediateHandling_updatesStateAndInformsListeners() {
|
||||
|
@ -732,6 +732,38 @@ import java.util.concurrent.TimeoutException;
|
||||
/* repeatCurrentMediaItem= */ false);
|
||||
}
|
||||
|
||||
@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) {
|
||||
verifyApplicationThread();
|
||||
|
@ -944,6 +944,12 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
player.moveMediaItems(fromIndex, toIndex, newIndex);
|
||||
}
|
||||
|
||||
@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) {
|
||||
blockUntilConstructorFinished();
|
||||
|
@ -85,6 +85,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.Context;
|
||||
@ -12651,6 +12652,232 @@ public final class ExoPlayerTest {
|
||||
eventsInOrder.verify(mockListener).onPlayerError(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_notReplacingCurrentItem_correctMasking() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addMediaSources(
|
||||
ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource()));
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 2);
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 2,
|
||||
ImmutableList.of(
|
||||
MediaItem.fromUri("test://test.uri"), MediaItem.fromUri("test://test2.uri")));
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(3);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(4);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test2.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItem_correctMasking() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addMediaSources(
|
||||
ImmutableList.of(
|
||||
createFakeMediaSource("1"), createFakeMediaSource("2"), createFakeMediaSource("3")));
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 1);
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 2,
|
||||
ImmutableList.of(
|
||||
MediaItem.fromUri("test://test.uri"), MediaItem.fromUri("test://test2.uri")));
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(4);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 2, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test2.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
MediaItem.fromUri("test://test.uri"),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onTracksChanged(Tracks.EMPTY);
|
||||
verify(listener).onAvailableCommandsChanged(any());
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItemWithEmptyListAndSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addMediaSources(
|
||||
ImmutableList.of(
|
||||
createFakeMediaSource("1"), createFakeMediaSource("2"), createFakeMediaSource("3")));
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 1);
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
createFakeMediaSource("3").getMediaItem(),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onTracksChanged(Tracks.EMPTY);
|
||||
verify(listener).onAvailableCommandsChanged(any());
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_replacingCurrentItemWithEmptyListAndNoSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addMediaSources(
|
||||
ImmutableList.of(createFakeMediaSource("1"), createFakeMediaSource("2")));
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 1);
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(1);
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_REMOVE));
|
||||
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_REMOVE);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
createFakeMediaSource("1").getMediaItem(),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onTracksChanged(Tracks.EMPTY);
|
||||
verify(listener).onAvailableCommandsChanged(any());
|
||||
verify(listener).onPlaybackStateChanged(Player.STATE_ENDED);
|
||||
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_ENDED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing deprecated listener call.
|
||||
@Test
|
||||
public void replaceMediaItems_fromPreparedEmpty_correctMasking() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.prepare();
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 1);
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 0,
|
||||
/* toIndex= */ 0,
|
||||
ImmutableList.of(
|
||||
MediaItem.fromUri("test://test.uri"), MediaItem.fromUri("test://test2.uri")));
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test2.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener)
|
||||
.onMediaItemTransition(
|
||||
MediaItem.fromUri("test://test2.uri"),
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
verify(listener).onAvailableCommandsChanged(any());
|
||||
verify(listener).onPlaybackStateChanged(Player.STATE_BUFFERING);
|
||||
verify(listener).onPlayerStateChanged(/* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_fromEmptyToEmpty_doesNothing() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.prepare();
|
||||
player.seekToDefaultPosition(/* mediaItemIndex= */ 1);
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(/* fromIndex= */ 0, /* toIndex= */ 0, ImmutableList.of());
|
||||
|
||||
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||
assertThat(player.getCurrentTimeline().isEmpty()).isTrue();
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_withInvalidToIndex_correctMasking() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
Listener listener = mock(Listener.class);
|
||||
player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()));
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.addListener(listener);
|
||||
player.replaceMediaItems(
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 5000,
|
||||
ImmutableList.of(MediaItem.fromUri("test://test.uri")));
|
||||
|
||||
assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(2);
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
player.getCurrentTimeline().getWindow(/* windowIndex= */ 1, window);
|
||||
assertThat(window.mediaItem.localConfiguration.uri).isEqualTo(Uri.parse("test://test.uri"));
|
||||
assertThat(window.isPlaceholder).isTrue();
|
||||
verify(listener)
|
||||
.onTimelineChanged(
|
||||
player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
verifyNoMoreInteractions(listener);
|
||||
player.release();
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||
|
@ -83,6 +83,8 @@ oneway interface IMediaSession {
|
||||
IMediaController caller, int seq, int currentIndex, int newIndex) = 3021;
|
||||
void moveMediaItems(
|
||||
IMediaController caller, int seq, int fromIndex, int toIndex, int newIndex) = 3022;
|
||||
void replaceMediaItem(IMediaController caller, int seq, int index, in Bundle mediaItemBundle) = 3054;
|
||||
void replaceMediaItems(IMediaController caller, int seq, int fromIndex, int toIndex, IBinder mediaItems) = 3055;
|
||||
void play(IMediaController caller, int seq) = 3023;
|
||||
void pause(IMediaController caller, int seq) = 3024;
|
||||
void prepare(IMediaController caller, int seq) = 3025;
|
||||
@ -118,7 +120,7 @@ oneway interface IMediaSession {
|
||||
void setRatingWithMediaId(
|
||||
IMediaController caller, int seq, String mediaId, in Bundle rating) = 3048;
|
||||
void setRating(IMediaController caller, int seq, in Bundle rating) = 3049;
|
||||
// Next Id for MediaSession: 3054
|
||||
// Next Id for MediaSession: 3056
|
||||
|
||||
void getLibraryRoot(IMediaController caller, int seq, in Bundle libraryParams) = 4000;
|
||||
void getItem(IMediaController caller, int seq, String mediaId) = 4001;
|
||||
|
@ -1071,12 +1071,6 @@ public class MediaController implements Player {
|
||||
impl.addMediaItem(index, mediaItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically add items.
|
||||
*/
|
||||
@Override
|
||||
public final void addMediaItems(List<MediaItem> mediaItems) {
|
||||
verifyApplicationThread();
|
||||
@ -1087,12 +1081,6 @@ public class MediaController implements Player {
|
||||
impl.addMediaItems(mediaItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically add items.
|
||||
*/
|
||||
@Override
|
||||
public final void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||
verifyApplicationThread();
|
||||
@ -1113,12 +1101,6 @@ public class MediaController implements Player {
|
||||
impl.removeMediaItem(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically remove items.
|
||||
*/
|
||||
@Override
|
||||
public final void removeMediaItems(int fromIndex, int toIndex) {
|
||||
verifyApplicationThread();
|
||||
@ -1129,12 +1111,6 @@ public class MediaController implements Player {
|
||||
impl.removeMediaItems(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically clear items.
|
||||
*/
|
||||
@Override
|
||||
public final void clearMediaItems() {
|
||||
verifyApplicationThread();
|
||||
@ -1145,12 +1121,6 @@ public class MediaController implements Player {
|
||||
impl.clearMediaItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically move items.
|
||||
*/
|
||||
@Override
|
||||
public final void moveMediaItem(int currentIndex, int newIndex) {
|
||||
verifyApplicationThread();
|
||||
@ -1161,12 +1131,6 @@ public class MediaController implements Player {
|
||||
impl.moveMediaItem(currentIndex, newIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Interoperability: When connected to {@link
|
||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically move items.
|
||||
*/
|
||||
@Override
|
||||
public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
||||
verifyApplicationThread();
|
||||
@ -1177,6 +1141,26 @@ public class MediaController implements Player {
|
||||
impl.moveMediaItems(fromIndex, toIndex, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||
verifyApplicationThread();
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "The controller is not connected. Ignoring replaceMediaItem().");
|
||||
return;
|
||||
}
|
||||
impl.replaceMediaItem(index, mediaItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
verifyApplicationThread();
|
||||
if (!isConnected()) {
|
||||
Log.w(TAG, "The controller is not connected. Ignoring replaceMediaItems().");
|
||||
return;
|
||||
}
|
||||
impl.replaceMediaItems(fromIndex, toIndex, mediaItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||
*/
|
||||
@ -2034,6 +2018,10 @@ public class MediaController implements Player {
|
||||
|
||||
void moveMediaItems(int fromIndex, int toIndex, int newIndex);
|
||||
|
||||
void replaceMediaItem(int index, MediaItem mediaItem);
|
||||
|
||||
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);
|
||||
|
||||
int getCurrentPeriodIndex();
|
||||
|
||||
int getCurrentMediaItemIndex();
|
||||
|
@ -1183,6 +1183,79 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
moveMediaItemsInternal(fromIndex, toIndex, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||
if (!isPlayerCommandAvailable(Player.COMMAND_CHANGE_MEDIA_ITEMS)) {
|
||||
return;
|
||||
}
|
||||
checkArgument(index >= 0);
|
||||
|
||||
dispatchRemoteSessionTaskWithPlayerCommand(
|
||||
(iSession, seq) -> {
|
||||
if (checkNotNull(connectedToken).getInterfaceVersion() >= 2) {
|
||||
iSession.replaceMediaItem(controllerStub, seq, index, mediaItem.toBundle());
|
||||
} else {
|
||||
iSession.addMediaItemWithIndex(controllerStub, seq, index + 1, mediaItem.toBundle());
|
||||
iSession.removeMediaItem(controllerStub, seq, index);
|
||||
}
|
||||
});
|
||||
replaceMediaItemsInternal(
|
||||
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
if (!isPlayerCommandAvailable(Player.COMMAND_CHANGE_MEDIA_ITEMS)) {
|
||||
return;
|
||||
}
|
||||
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||
|
||||
dispatchRemoteSessionTaskWithPlayerCommand(
|
||||
(iSession, seq) -> {
|
||||
IBinder mediaItemsBundleBinder =
|
||||
new BundleListRetriever(BundleableUtil.toBundleList(mediaItems));
|
||||
if (checkNotNull(connectedToken).getInterfaceVersion() >= 2) {
|
||||
iSession.replaceMediaItems(
|
||||
controllerStub, seq, fromIndex, toIndex, mediaItemsBundleBinder);
|
||||
} else {
|
||||
iSession.addMediaItemsWithIndex(controllerStub, seq, toIndex, mediaItemsBundleBinder);
|
||||
iSession.removeMediaItems(controllerStub, seq, fromIndex, toIndex);
|
||||
}
|
||||
});
|
||||
replaceMediaItemsInternal(fromIndex, toIndex, mediaItems);
|
||||
}
|
||||
|
||||
private void replaceMediaItemsInternal(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
int playlistSize = playerInfo.timeline.getWindowCount();
|
||||
if (fromIndex > playlistSize) {
|
||||
return;
|
||||
}
|
||||
if (playerInfo.timeline.isEmpty()) {
|
||||
// Handle initial items in a playlist as a set operation to ensure state changes and initial
|
||||
// position are updated correctly.
|
||||
setMediaItemsInternal(
|
||||
mediaItems,
|
||||
/* startIndex= */ C.INDEX_UNSET,
|
||||
/* startPositionMs= */ C.TIME_UNSET,
|
||||
/* resetToDefaultPosition= */ false);
|
||||
return;
|
||||
}
|
||||
toIndex = min(toIndex, playlistSize);
|
||||
PlayerInfo newPlayerInfo = maskPlaybackInfoForAddedItems(playerInfo, toIndex, mediaItems);
|
||||
newPlayerInfo = maskPlayerInfoForRemovedItems(newPlayerInfo, fromIndex, toIndex);
|
||||
boolean replacedCurrentItem =
|
||||
playerInfo.sessionPositionInfo.positionInfo.mediaItemIndex >= fromIndex
|
||||
&& playerInfo.sessionPositionInfo.positionInfo.mediaItemIndex < toIndex;
|
||||
updatePlayerInfo(
|
||||
newPlayerInfo,
|
||||
/* timelineChangeReason= */ Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||
/* ignored */ Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
||||
/* positionDiscontinuity= */ replacedCurrentItem,
|
||||
Player.DISCONTINUITY_REASON_REMOVE,
|
||||
/* mediaItemTransition= */ replacedCurrentItem,
|
||||
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
return playerInfo.sessionPositionInfo.positionInfo.periodIndex;
|
||||
|
@ -849,6 +849,25 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||
replaceMediaItems(
|
||||
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||
QueueTimeline queueTimeline = (QueueTimeline) controllerInfo.playerInfo.timeline;
|
||||
int size = queueTimeline.getWindowCount();
|
||||
if (fromIndex > size) {
|
||||
return;
|
||||
}
|
||||
toIndex = min(toIndex, size);
|
||||
addMediaItems(toIndex, mediaItems);
|
||||
removeMediaItems(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
return getCurrentMediaItemIndex();
|
||||
|
@ -113,7 +113,7 @@ import java.util.concurrent.ExecutionException;
|
||||
private static final String TAG = "MediaSessionStub";
|
||||
|
||||
/** The version of the IMediaSession interface. */
|
||||
public static final int VERSION_INT = 1;
|
||||
public static final int VERSION_INT = 2;
|
||||
|
||||
private final WeakReference<MediaSessionImpl> sessionImpl;
|
||||
private final MediaSessionManager sessionManager;
|
||||
@ -1270,6 +1270,74 @@ import java.util.concurrent.ExecutionException;
|
||||
sendSessionResultSuccess(player -> player.moveMediaItems(fromIndex, toIndex, newIndex)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(
|
||||
IMediaController caller, int sequenceNumber, int index, 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_CHANGE_MEDIA_ITEMS,
|
||||
sendSessionResultWhenReady(
|
||||
handleMediaItemsWhenReady(
|
||||
(sessionImpl, controller, sequenceNum) ->
|
||||
sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem)),
|
||||
(player, controller, mediaItems) -> {
|
||||
if (mediaItems.size() == 1) {
|
||||
player.replaceMediaItem(
|
||||
maybeCorrectMediaItemIndex(controller, player, index), mediaItems.get(0));
|
||||
} else {
|
||||
player.replaceMediaItems(
|
||||
maybeCorrectMediaItemIndex(controller, player, index),
|
||||
maybeCorrectMediaItemIndex(controller, player, index + 1),
|
||||
mediaItems);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(
|
||||
IMediaController caller,
|
||||
int sequenceNumber,
|
||||
int fromIndex,
|
||||
int toIndex,
|
||||
IBinder mediaItemsRetriever) {
|
||||
if (caller == null || mediaItemsRetriever == null) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<MediaItem> mediaItems;
|
||||
try {
|
||||
mediaItems =
|
||||
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, mediaItems),
|
||||
(player, controller, items) ->
|
||||
player.replaceMediaItems(
|
||||
maybeCorrectMediaItemIndex(controller, player, fromIndex),
|
||||
maybeCorrectMediaItemIndex(controller, player, toIndex),
|
||||
items))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToPreviousMediaItem(@Nullable IMediaController caller, int sequenceNumber) {
|
||||
if (caller == null) {
|
||||
|
@ -484,6 +484,18 @@ import java.util.List;
|
||||
super.moveMediaItems(fromIndex, toIndex, newIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||
verifyApplicationThread();
|
||||
super.replaceMediaItem(index, mediaItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
verifyApplicationThread();
|
||||
super.replaceMediaItems(fromIndex, toIndex, mediaItems);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
|
@ -62,6 +62,8 @@ interface IRemoteMediaController {
|
||||
void clearMediaItems(String controllerId);
|
||||
void moveMediaItem(String controllerId, int currentIndex, int newIndex);
|
||||
void moveMediaItems(String controllerId, int fromIndex, int toIndex, int newIndex);
|
||||
void replaceMediaItem(String controllerId, int index, in Bundle mediaItem);
|
||||
void replaceMediaItems(String controllerId, int fromIndex, int toIndex, in List<Bundle> mediaItems);
|
||||
void seekToPreviousMediaItem(String controllerId);
|
||||
void seekToNextMediaItem(String controllerId);
|
||||
void seekToPrevious(String controllerId);
|
||||
|
@ -3240,6 +3240,334 @@ public class MediaControllerStateMaskingTest {
|
||||
assertThat(itemsAfterMove).containsExactly(items.get(1), items.get(0)).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_notReplacingCurrentItem_correctMasking() throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(MediaTestUtils.createTimeline(3))
|
||||
.setCurrentMediaItemIndex(2)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 2, createMediaItems(2));
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(4);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(3);
|
||||
assertThat(getEventsAsList(onEventsRef.get())).containsExactly(Player.EVENT_TIMELINE_CHANGED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItem_correctMasking() throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(MediaTestUtils.createTimeline(3))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItem(/* index= */ 1, createMediaItems(1).get(0));
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(3);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(getEventsAsList(onEventsRef.get()))
|
||||
.containsExactly(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItemWithEmptyListAndSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(MediaTestUtils.createTimeline(3))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(2);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(getEventsAsList(onEventsRef.get()))
|
||||
.containsExactly(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_replacingCurrentItemWithEmptyListAndNoSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(MediaTestUtils.createTimeline(2))
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_BUFFERING)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicInteger playbackStateRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
playbackStateRef.set(controller.getPlaybackState());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(1);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(0);
|
||||
assertThat(playbackStateRef.get()).isEqualTo(Player.STATE_ENDED);
|
||||
assertThat(getEventsAsList(onEventsRef.get()))
|
||||
.containsExactly(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION,
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_fromPreparedEmpty_correctMasking() throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(Timeline.EMPTY)
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_ENDED)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicInteger playbackStateRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 0, /* toIndex= */ 0, createMediaItems(2));
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
playbackStateRef.set(controller.getPlaybackState());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(2);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(playbackStateRef.get()).isEqualTo(Player.STATE_BUFFERING);
|
||||
assertThat(getEventsAsList(onEventsRef.get()))
|
||||
.containsExactly(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION,
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_fromEmptyToEmpty_correctMasking() throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(Timeline.EMPTY)
|
||||
.setCurrentMediaItemIndex(1)
|
||||
.setPlaybackState(Player.STATE_ENDED)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicInteger playbackStateRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 0, /* toIndex= */ 0, ImmutableList.of());
|
||||
newTimelineRef.set(controller.getCurrentTimeline());
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
playbackStateRef.set(controller.getPlaybackState());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().isEmpty()).isTrue();
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(playbackStateRef.get()).isEqualTo(Player.STATE_ENDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_withInvalidToIndex_correctMasking() throws Exception {
|
||||
Bundle playerConfig =
|
||||
new RemoteMediaSession.MockPlayerConfigBuilder()
|
||||
.setTimeline(MediaTestUtils.createTimeline(3))
|
||||
.setCurrentMediaItemIndex(2)
|
||||
.build();
|
||||
remoteSession.setPlayer(playerConfig);
|
||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> newTimelineRef = new AtomicReference<>();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
newTimelineRef.set(timeline);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 5000, createMediaItems(2));
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(newTimelineRef.get().getWindowCount()).isEqualTo(3);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(getEventsAsList(onEventsRef.get()))
|
||||
.containsExactly(
|
||||
Player.EVENT_TIMELINE_CHANGED,
|
||||
Player.EVENT_MEDIA_ITEM_TRANSITION,
|
||||
Player.EVENT_POSITION_DISCONTINUITY);
|
||||
}
|
||||
|
||||
private void assertMoveMediaItems(
|
||||
int initialMediaItemCount,
|
||||
int initialMediaItemIndex,
|
||||
|
@ -1307,4 +1307,380 @@ public class MediaControllerStateMaskingWithMediaSessionCompatTest {
|
||||
|
||||
assertThat(itemsAfterMove).containsExactly(items.get(1), items.get(0)).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_notReplacingCurrentItem_correctMasking() throws Exception {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
|
||||
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
|
||||
long testPosition = 200L;
|
||||
int initialMediaItemIndex = 2;
|
||||
MediaItem testCurrentMediaItem = mediaItems.get(initialMediaItemIndex);
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, testPosition, /* playbackSpeed= */ 1.0f)
|
||||
.setActiveQueueItemId(queue.get(initialMediaItemIndex).getQueueId())
|
||||
.build());
|
||||
session.setQueue(queue);
|
||||
List<MediaItem> newMediaItems = MediaTestUtils.createMediaItems("A", "B");
|
||||
List<MediaItem> expectedMediaItems = new ArrayList<>();
|
||||
expectedMediaItems.add(mediaItems.get(0));
|
||||
expectedMediaItems.addAll(newMediaItems);
|
||||
expectedMediaItems.add(mediaItems.get(2));
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(3); // 2x onTimelineChanged + onEvents
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, newMediaItems);
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), expectedMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(3);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(testCurrentMediaItem);
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), expectedMediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItem_correctMasking() throws Exception {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
|
||||
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
|
||||
long testPosition = 200L;
|
||||
int initialMediaItemIndex = 1;
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, testPosition, /* playbackSpeed= */ 1.0f)
|
||||
.setActiveQueueItemId(queue.get(initialMediaItemIndex).getQueueId())
|
||||
.build());
|
||||
session.setQueue(queue);
|
||||
List<MediaItem> newMediaItems = MediaTestUtils.createMediaItems("A", "B");
|
||||
List<MediaItem> expectedMediaItems = new ArrayList<>();
|
||||
expectedMediaItems.add(mediaItems.get(0));
|
||||
expectedMediaItems.addAll(newMediaItems);
|
||||
expectedMediaItems.add(mediaItems.get(2));
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(3); // 2x onTimelineChanged + onEvents
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, newMediaItems);
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), expectedMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(newMediaItems.get(0));
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), expectedMediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_replacingCurrentItemWithEmptyListAndSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
|
||||
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
|
||||
long testPosition = 200L;
|
||||
int initialMediaItemIndex = 1;
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, testPosition, /* playbackSpeed= */ 1.0f)
|
||||
.setActiveQueueItemId(queue.get(initialMediaItemIndex).getQueueId())
|
||||
.build());
|
||||
session.setQueue(queue);
|
||||
List<MediaItem> expectedMediaItems = new ArrayList<>();
|
||||
expectedMediaItems.add(mediaItems.get(0));
|
||||
expectedMediaItems.add(mediaItems.get(2));
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), expectedMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(expectedMediaItems.get(1));
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), expectedMediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
replaceMediaItems_replacingCurrentItemWithEmptyListAndNoSubsequentItem_correctMasking()
|
||||
throws Exception {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b");
|
||||
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
|
||||
long testPosition = 200L;
|
||||
int initialMediaItemIndex = 1;
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, testPosition, /* playbackSpeed= */ 1.0f)
|
||||
.setActiveQueueItemId(queue.get(initialMediaItemIndex).getQueueId())
|
||||
.build());
|
||||
session.setQueue(queue);
|
||||
List<MediaItem> expectedMediaItems = new ArrayList<>();
|
||||
expectedMediaItems.add(mediaItems.get(0));
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(
|
||||
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of());
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), expectedMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(0);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(expectedMediaItems.get(0));
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), expectedMediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_fromEmpty_correctMasking() throws Exception {
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(
|
||||
PlaybackStateCompat.STATE_STOPPED, /* position= */ 0, /* playbackSpeed= */ 1.0f)
|
||||
.build());
|
||||
session.setQueue(ImmutableList.of());
|
||||
List<MediaItem> newMediaItems = MediaTestUtils.createMediaItems("A", "B");
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(/* fromIndex= */ 0, /* toIndex= */ 0, newMediaItems);
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), newMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(0);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(newMediaItems.get(0));
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), newMediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems_withInvalidToIndex_correctMasking() throws Exception {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
|
||||
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
|
||||
long testPosition = 200L;
|
||||
int initialMediaItemIndex = 1;
|
||||
session.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, testPosition, /* playbackSpeed= */ 1.0f)
|
||||
.setActiveQueueItemId(queue.get(initialMediaItemIndex).getQueueId())
|
||||
.build());
|
||||
session.setQueue(queue);
|
||||
List<MediaItem> newMediaItems = MediaTestUtils.createMediaItems("A", "B");
|
||||
List<MediaItem> expectedMediaItems = new ArrayList<>();
|
||||
expectedMediaItems.add(mediaItems.get(0));
|
||||
expectedMediaItems.addAll(newMediaItems);
|
||||
Events expectedEvents =
|
||||
new Events(new FlagSet.Builder().addAll(EVENT_TIMELINE_CHANGED).build());
|
||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
|
||||
AtomicInteger timelineChangedReasonRef = new AtomicInteger();
|
||||
AtomicReference<Player.Events> onEventsRef = new AtomicReference<>();
|
||||
Player.Listener listener =
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(
|
||||
Timeline timeline, @Player.TimelineChangeReason int reason) {
|
||||
timelineFromParamRef.set(timeline);
|
||||
timelineChangedReasonRef.set(reason);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
onEventsRef.set(events);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
|
||||
AtomicInteger currentMediaItemIndexRef = new AtomicInteger();
|
||||
AtomicReference<MediaItem> currentMediaItemRef = new AtomicReference<>();
|
||||
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
|
||||
|
||||
threadTestRule
|
||||
.getHandler()
|
||||
.postAndSync(
|
||||
() -> {
|
||||
controller.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 5000, newMediaItems);
|
||||
currentMediaItemIndexRef.set(controller.getCurrentMediaItemIndex());
|
||||
currentMediaItemRef.set(controller.getCurrentMediaItem());
|
||||
timelineFromGetterRef.set(controller.getCurrentTimeline());
|
||||
});
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
MediaTestUtils.assertTimelineContains(timelineFromParamRef.get(), expectedMediaItems);
|
||||
assertThat(timelineChangedReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
assertThat(onEventsRef.get()).isEqualTo(expectedEvents);
|
||||
assertThat(currentMediaItemIndexRef.get()).isEqualTo(1);
|
||||
assertThat(currentMediaItemRef.get()).isEqualTo(newMediaItems.get(0));
|
||||
MediaTestUtils.assertTimelineContains(timelineFromGetterRef.get(), expectedMediaItems);
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,21 @@ public class MediaSessionPermissionTest {
|
||||
controller -> controller.removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItem() throws Exception {
|
||||
testOnCommandRequest(
|
||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
controller -> controller.replaceMediaItem(/* index= */ 0, MediaItem.EMPTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems() throws Exception {
|
||||
testOnCommandRequest(
|
||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
controller ->
|
||||
controller.replaceMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1, ImmutableList.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeviceVolume() throws Exception {
|
||||
testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceVolume(0));
|
||||
|
@ -703,6 +703,34 @@ public class MediaSessionPlayerTest {
|
||||
assertThat(player.newIndex).isEqualTo(newIndex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItem() throws Exception {
|
||||
player.setMediaItems(MediaTestUtils.createMediaItems(4));
|
||||
MediaItem mediaItem = MediaTestUtils.createMediaItem("replaceMediaItem");
|
||||
|
||||
controller.replaceMediaItem(/* index= */ 2, mediaItem);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_REPLACE_MEDIA_ITEM, TIMEOUT_MS);
|
||||
assertThat(player.index).isEqualTo(2);
|
||||
assertThat(player.mediaItems).hasSize(4);
|
||||
assertThat(player.mediaItems.get(2)).isEqualTo(mediaItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems() throws Exception {
|
||||
player.setMediaItems(MediaTestUtils.createMediaItems(4));
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(2);
|
||||
|
||||
controller.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 2, mediaItems);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_REPLACE_MEDIA_ITEMS, TIMEOUT_MS);
|
||||
assertThat(player.fromIndex).isEqualTo(1);
|
||||
assertThat(player.toIndex).isEqualTo(2);
|
||||
assertThat(player.mediaItems).hasSize(5);
|
||||
assertThat(player.mediaItems.get(1)).isEqualTo(mediaItems.get(0));
|
||||
assertThat(player.mediaItems.get(2)).isEqualTo(mediaItems.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekToPreviousMediaItem() throws Exception {
|
||||
controller.seekToPreviousMediaItem();
|
||||
|
@ -370,6 +370,39 @@ public class MockPlayerTest {
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItem() {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
player.addMediaItems(mediaItems);
|
||||
MediaItem mediaItem = MediaTestUtils.createMediaItem("item");
|
||||
|
||||
player.replaceMediaItem(/* index= */ 1, mediaItem);
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_REPLACE_MEDIA_ITEM)).isTrue();
|
||||
assertThat(player.index).isEqualTo(1);
|
||||
assertThat(player.mediaItems).containsExactly(mediaItems.get(0), mediaItem, mediaItems.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceMediaItems() {
|
||||
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 4);
|
||||
player.addMediaItems(mediaItems);
|
||||
List<MediaItem> newMediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
|
||||
player.replaceMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, newMediaItems);
|
||||
|
||||
assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_REPLACE_MEDIA_ITEMS)).isTrue();
|
||||
assertThat(player.fromIndex).isEqualTo(1);
|
||||
assertThat(player.toIndex).isEqualTo(3);
|
||||
assertThat(player.mediaItems)
|
||||
.containsExactly(
|
||||
mediaItems.get(0),
|
||||
newMediaItems.get(0),
|
||||
newMediaItems.get(1),
|
||||
newMediaItems.get(2),
|
||||
mediaItems.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekToPreviousMediaItem() {
|
||||
player.seekToPreviousMediaItem();
|
||||
|
@ -463,6 +463,28 @@ public class MediaControllerProviderService extends Service {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(String controllerId, int index, Bundle mediaItem)
|
||||
throws RemoteException {
|
||||
runOnHandler(
|
||||
() -> {
|
||||
MediaController controller = mediaControllerMap.get(controllerId);
|
||||
controller.replaceMediaItem(index, MediaItem.CREATOR.fromBundle(mediaItem));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(
|
||||
String controllerId, int fromIndex, int toIndex, List<Bundle> mediaItems)
|
||||
throws RemoteException {
|
||||
runOnHandler(
|
||||
() -> {
|
||||
MediaController controller = mediaControllerMap.get(controllerId);
|
||||
controller.replaceMediaItems(
|
||||
fromIndex, toIndex, BundleableUtil.fromBundleList(MediaItem.CREATOR, mediaItems));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToPreviousMediaItem(String controllerId) throws RemoteException {
|
||||
runOnHandler(
|
||||
|
@ -107,7 +107,9 @@ public class MockPlayer implements Player {
|
||||
METHOD_SET_SHUFFLE_MODE,
|
||||
METHOD_SET_TRACK_SELECTION_PARAMETERS,
|
||||
METHOD_SET_VOLUME,
|
||||
METHOD_STOP
|
||||
METHOD_STOP,
|
||||
METHOD_REPLACE_MEDIA_ITEM,
|
||||
METHOD_REPLACE_MEDIA_ITEMS
|
||||
})
|
||||
public @interface Method {}
|
||||
|
||||
@ -203,6 +205,10 @@ public class MockPlayer implements Player {
|
||||
public static final int METHOD_SET_DEVICE_MUTED_WITH_FLAGS = 44;
|
||||
/** Maps to {@link Player#setDeviceVolume(int, int)}. */
|
||||
public static final int METHOD_SET_DEVICE_VOLUME_WITH_FLAGS = 45;
|
||||
/** Maps to {@link Player#replaceMediaItem(int, MediaItem)}. */
|
||||
public static final int METHOD_REPLACE_MEDIA_ITEM = 46;
|
||||
/** Maps to {@link Player#replaceMediaItems(int, int, List)} . */
|
||||
public static final int METHOD_REPLACE_MEDIA_ITEMS = 47;
|
||||
|
||||
private final boolean changePlayerStateWithTransportControl;
|
||||
private final Looper applicationLooper;
|
||||
@ -990,6 +996,22 @@ public class MockPlayer implements Player {
|
||||
checkNotNull(conditionVariables.get(METHOD_MOVE_MEDIA_ITEMS)).open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||
this.index = index;
|
||||
this.mediaItems.set(index, mediaItem);
|
||||
checkNotNull(conditionVariables.get(METHOD_REPLACE_MEDIA_ITEM)).open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
this.fromIndex = fromIndex;
|
||||
this.toIndex = toIndex;
|
||||
this.mediaItems.addAll(toIndex, mediaItems);
|
||||
Util.removeRange(this.mediaItems, fromIndex, toIndex);
|
||||
checkNotNull(conditionVariables.get(METHOD_REPLACE_MEDIA_ITEMS)).open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||
*/
|
||||
@ -1410,6 +1432,8 @@ public class MockPlayer implements Player {
|
||||
.put(METHOD_SET_TRACK_SELECTION_PARAMETERS, new ConditionVariable())
|
||||
.put(METHOD_SET_VOLUME, new ConditionVariable())
|
||||
.put(METHOD_STOP, new ConditionVariable())
|
||||
.put(METHOD_REPLACE_MEDIA_ITEM, new ConditionVariable())
|
||||
.put(METHOD_REPLACE_MEDIA_ITEMS, new ConditionVariable())
|
||||
.buildOrThrow();
|
||||
}
|
||||
|
||||
|
@ -218,6 +218,16 @@ public class RemoteMediaController {
|
||||
binder.moveMediaItems(controllerId, fromIndex, toIndex, newIndex);
|
||||
}
|
||||
|
||||
public void replaceMediaItem(int index, MediaItem mediaItem) throws RemoteException {
|
||||
binder.replaceMediaItem(controllerId, index, mediaItem.toBundle());
|
||||
}
|
||||
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems)
|
||||
throws RemoteException {
|
||||
binder.replaceMediaItems(
|
||||
controllerId, fromIndex, toIndex, BundleableUtil.toBundleList(mediaItems));
|
||||
}
|
||||
|
||||
public void seekToPreviousMediaItem() throws RemoteException {
|
||||
binder.seekToPreviousMediaItem(controllerId);
|
||||
}
|
||||
|
@ -102,6 +102,11 @@ public class StubPlayer extends BasePlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
Loading…
x
Reference in New Issue
Block a user