mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Implement Player.replaceMediaItem(s)
This change moves the default logic into the actual Player implementations, but does not introduce any behavior changes compared to addMediaItems+removeMediaItems except to make the updates "atomic" in ExoPlayerImpl, SimpleBasePlayer and MediaController. It also provides backwards compatbility for cases where Players don't support the operation. Issue: google/ExoPlayer#8046 #minor-release PiperOrigin-RevId: 534945089 (cherry picked from commit 2c07468908fdba9918d33d0f02c6ff872b87fefc)
This commit is contained in:
parent
638dee44a9
commit
f2edc0bed1
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 removeListener(androidx.media3.common.Player.Listener);
|
||||||
method public void removeMediaItem(int);
|
method public void removeMediaItem(int);
|
||||||
method public void removeMediaItems(int, int);
|
method public void removeMediaItems(int, int);
|
||||||
method public default void replaceMediaItem(int, androidx.media3.common.MediaItem);
|
method public void replaceMediaItem(int, androidx.media3.common.MediaItem);
|
||||||
method public default void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
|
method public void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public void seekBack();
|
method public void seekBack();
|
||||||
method public void seekForward();
|
method public void seekForward();
|
||||||
method public void seekTo(long);
|
method public void seekTo(long);
|
||||||
@ -1603,6 +1603,8 @@ package androidx.media3.session {
|
|||||||
method public final void removeListener(androidx.media3.common.Player.Listener);
|
method public final void removeListener(androidx.media3.common.Player.Listener);
|
||||||
method public final void removeMediaItem(int);
|
method public final void removeMediaItem(int);
|
||||||
method public final void removeMediaItems(int, 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 seekBack();
|
||||||
method public final void seekForward();
|
method public final void seekForward();
|
||||||
method public final void seekTo(long);
|
method public final void seekTo(long);
|
||||||
|
@ -321,6 +321,18 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
moveMediaItemsInternal(uids, fromIndex, newIndex);
|
moveMediaItemsInternal(uids, fromIndex, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||||
|
int playlistSize = currentTimeline.getWindowCount();
|
||||||
|
if (fromIndex > playlistSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toIndex = min(toIndex, playlistSize);
|
||||||
|
addMediaItems(toIndex, mediaItems);
|
||||||
|
removeMediaItems(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
||||||
|
@ -699,6 +699,38 @@ public class CastPlayerTest {
|
|||||||
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
|
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replaceMediaItems_callsRemoteMediaClient() {
|
||||||
|
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
|
||||||
|
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||||
|
// Add two items.
|
||||||
|
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||||
|
String uri = "http://www.google.com/video3";
|
||||||
|
MediaItem anotherMediaItem =
|
||||||
|
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build();
|
||||||
|
ImmutableList<MediaItem> newPlaylist = ImmutableList.of(mediaItems.get(0), anotherMediaItem);
|
||||||
|
|
||||||
|
// Replace item at position 1.
|
||||||
|
castPlayer.replaceMediaItems(
|
||||||
|
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of(anotherMediaItem));
|
||||||
|
updateTimeLine(
|
||||||
|
newPlaylist,
|
||||||
|
/* mediaQueueItemIds= */ new int[] {mediaQueueItemIds[0], 123},
|
||||||
|
/* currentItemId= */ 123);
|
||||||
|
|
||||||
|
verify(mockRemoteMediaClient, times(2))
|
||||||
|
.queueInsertItems(queueItemsArgumentCaptor.capture(), anyInt(), any());
|
||||||
|
verify(mockRemoteMediaClient).queueRemoveItems(new int[] {2}, /* customData= */ null);
|
||||||
|
assertThat(queueItemsArgumentCaptor.getAllValues().get(1)[0])
|
||||||
|
.isEqualTo(mediaItemConverter.toMediaQueueItem(anotherMediaItem));
|
||||||
|
Timeline.Window currentWindow =
|
||||||
|
castPlayer
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getWindow(castPlayer.getCurrentMediaItemIndex(), new Timeline.Window());
|
||||||
|
assertThat(currentWindow.uid).isEqualTo(123);
|
||||||
|
assertThat(currentWindow.mediaItem).isEqualTo(anotherMediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void addMediaItems_fillsTimeline() {
|
public void addMediaItems_fillsTimeline() {
|
||||||
|
@ -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
|
@Override
|
||||||
public final void removeMediaItem(int index) {
|
public final void removeMediaItem(int index) {
|
||||||
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
|
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
|
||||||
|
@ -37,7 +37,6 @@ import androidx.media3.common.util.Size;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -2158,10 +2157,7 @@ public interface Player {
|
|||||||
* of the playlist, the request is ignored.
|
* of the playlist, the request is ignored.
|
||||||
* @param mediaItem The new {@link MediaItem}.
|
* @param mediaItem The new {@link MediaItem}.
|
||||||
*/
|
*/
|
||||||
default void replaceMediaItem(int index, MediaItem mediaItem) {
|
void replaceMediaItem(int index, MediaItem mediaItem);
|
||||||
replaceMediaItems(
|
|
||||||
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the media items at the given range of the playlist.
|
* Replaces the media items at the given range of the playlist.
|
||||||
@ -2180,10 +2176,7 @@ public interface Player {
|
|||||||
* larger than the size of the playlist, items up to the end of the playlist are replaced.
|
* larger than the size of the playlist, items up to the end of the playlist are replaced.
|
||||||
* @param mediaItems The {@linkplain MediaItem media items} to replace the range with.
|
* @param mediaItems The {@linkplain MediaItem media items} to replace the range with.
|
||||||
*/
|
*/
|
||||||
default void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);
|
||||||
addMediaItems(toIndex, mediaItems);
|
|
||||||
removeMediaItems(fromIndex, toIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the media item at the given index of the playlist.
|
* Removes the media item at the given index of the playlist.
|
||||||
|
@ -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
|
@Override
|
||||||
public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
addMediaItems(toIndex, mediaItems);
|
verifyApplicationThreadAndInitState();
|
||||||
removeMediaItems(fromIndex, toIndex);
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||||
|
State state = this.state;
|
||||||
|
int playlistSize = state.playlist.size();
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || fromIndex > playlistSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int correctedToIndex = min(toIndex, playlistSize);
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
|
||||||
|
/* placeholderStateSupplier= */ () -> {
|
||||||
|
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
|
||||||
|
for (int i = 0; i < mediaItems.size(); i++) {
|
||||||
|
placeholderPlaylist.add(
|
||||||
|
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
||||||
|
}
|
||||||
|
State updatedState;
|
||||||
|
if (!state.playlist.isEmpty()) {
|
||||||
|
updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period);
|
||||||
|
} else {
|
||||||
|
// Handle initial position update when these are the first items added to the playlist.
|
||||||
|
updatedState =
|
||||||
|
getStateWithNewPlaylistAndPosition(
|
||||||
|
state,
|
||||||
|
placeholderPlaylist,
|
||||||
|
state.currentMediaItemIndex,
|
||||||
|
state.contentPositionMsSupplier.get());
|
||||||
|
}
|
||||||
|
if (fromIndex < correctedToIndex) {
|
||||||
|
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
|
||||||
|
return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period);
|
||||||
|
} else {
|
||||||
|
return updatedState;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -3182,6 +3209,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS");
|
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}.
|
* Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
|
||||||
*
|
*
|
||||||
|
@ -6868,6 +6868,559 @@ public class SimpleBasePlayerTest {
|
|||||||
assertThat(callForwarded.get()).isFalse();
|
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.
|
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||||
@Test
|
@Test
|
||||||
public void seekTo_immediateHandling_updatesStateAndInformsListeners() {
|
public void seekTo_immediateHandling_updatesStateAndInformsListeners() {
|
||||||
|
@ -732,6 +732,38 @@ import java.util.concurrent.TimeoutException;
|
|||||||
/* repeatCurrentMediaItem= */ false);
|
/* 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
|
@Override
|
||||||
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
|
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
|
@ -944,6 +944,12 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
player.moveMediaItems(fromIndex, toIndex, newIndex);
|
player.moveMediaItems(fromIndex, toIndex, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
blockUntilConstructorFinished();
|
||||||
|
player.replaceMediaItems(fromIndex, toIndex, mediaItems);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
blockUntilConstructorFinished();
|
blockUntilConstructorFinished();
|
||||||
|
@ -85,6 +85,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -12651,6 +12652,232 @@ public final class ExoPlayerTest {
|
|||||||
eventsInOrder.verify(mockListener).onPlayerError(any(), any());
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||||
|
@ -83,6 +83,8 @@ oneway interface IMediaSession {
|
|||||||
IMediaController caller, int seq, int currentIndex, int newIndex) = 3021;
|
IMediaController caller, int seq, int currentIndex, int newIndex) = 3021;
|
||||||
void moveMediaItems(
|
void moveMediaItems(
|
||||||
IMediaController caller, int seq, int fromIndex, int toIndex, int newIndex) = 3022;
|
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 play(IMediaController caller, int seq) = 3023;
|
||||||
void pause(IMediaController caller, int seq) = 3024;
|
void pause(IMediaController caller, int seq) = 3024;
|
||||||
void prepare(IMediaController caller, int seq) = 3025;
|
void prepare(IMediaController caller, int seq) = 3025;
|
||||||
@ -118,7 +120,7 @@ oneway interface IMediaSession {
|
|||||||
void setRatingWithMediaId(
|
void setRatingWithMediaId(
|
||||||
IMediaController caller, int seq, String mediaId, in Bundle rating) = 3048;
|
IMediaController caller, int seq, String mediaId, in Bundle rating) = 3048;
|
||||||
void setRating(IMediaController caller, int seq, in Bundle rating) = 3049;
|
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 getLibraryRoot(IMediaController caller, int seq, in Bundle libraryParams) = 4000;
|
||||||
void getItem(IMediaController caller, int seq, String mediaId) = 4001;
|
void getItem(IMediaController caller, int seq, String mediaId) = 4001;
|
||||||
|
@ -1071,12 +1071,6 @@ public class MediaController implements Player {
|
|||||||
impl.addMediaItem(index, mediaItem);
|
impl.addMediaItem(index, mediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically add items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void addMediaItems(List<MediaItem> mediaItems) {
|
public final void addMediaItems(List<MediaItem> mediaItems) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1087,12 +1081,6 @@ public class MediaController implements Player {
|
|||||||
impl.addMediaItems(mediaItems);
|
impl.addMediaItems(mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically add items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void addMediaItems(int index, List<MediaItem> mediaItems) {
|
public final void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1113,12 +1101,6 @@ public class MediaController implements Player {
|
|||||||
impl.removeMediaItem(index);
|
impl.removeMediaItem(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically remove items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void removeMediaItems(int fromIndex, int toIndex) {
|
public final void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1129,12 +1111,6 @@ public class MediaController implements Player {
|
|||||||
impl.removeMediaItems(fromIndex, toIndex);
|
impl.removeMediaItems(fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically clear items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void clearMediaItems() {
|
public final void clearMediaItems() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1145,12 +1121,6 @@ public class MediaController implements Player {
|
|||||||
impl.clearMediaItems();
|
impl.clearMediaItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically move items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void moveMediaItem(int currentIndex, int newIndex) {
|
public final void moveMediaItem(int currentIndex, int newIndex) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1161,12 +1131,6 @@ public class MediaController implements Player {
|
|||||||
impl.moveMediaItem(currentIndex, newIndex);
|
impl.moveMediaItem(currentIndex, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>Interoperability: When connected to {@link
|
|
||||||
* android.support.v4.media.session.MediaSessionCompat}, this doesn't atomically move items.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -1177,6 +1141,26 @@ public class MediaController implements Player {
|
|||||||
impl.moveMediaItems(fromIndex, toIndex, newIndex);
|
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.
|
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||||
*/
|
*/
|
||||||
@ -2034,6 +2018,10 @@ public class MediaController implements Player {
|
|||||||
|
|
||||||
void moveMediaItems(int fromIndex, int toIndex, int newIndex);
|
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 getCurrentPeriodIndex();
|
||||||
|
|
||||||
int getCurrentMediaItemIndex();
|
int getCurrentMediaItemIndex();
|
||||||
|
@ -1183,6 +1183,79 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||||||
moveMediaItemsInternal(fromIndex, toIndex, newIndex);
|
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
|
@Override
|
||||||
public int getCurrentPeriodIndex() {
|
public int getCurrentPeriodIndex() {
|
||||||
return playerInfo.sessionPositionInfo.positionInfo.periodIndex;
|
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
|
@Override
|
||||||
public int getCurrentPeriodIndex() {
|
public int getCurrentPeriodIndex() {
|
||||||
return getCurrentMediaItemIndex();
|
return getCurrentMediaItemIndex();
|
||||||
|
@ -113,7 +113,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
private static final String TAG = "MediaSessionStub";
|
private static final String TAG = "MediaSessionStub";
|
||||||
|
|
||||||
/** The version of the IMediaSession interface. */
|
/** 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 WeakReference<MediaSessionImpl> sessionImpl;
|
||||||
private final MediaSessionManager sessionManager;
|
private final MediaSessionManager sessionManager;
|
||||||
@ -1270,6 +1270,74 @@ import java.util.concurrent.ExecutionException;
|
|||||||
sendSessionResultSuccess(player -> player.moveMediaItems(fromIndex, toIndex, newIndex)));
|
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
|
@Override
|
||||||
public void seekToPreviousMediaItem(@Nullable IMediaController caller, int sequenceNumber) {
|
public void seekToPreviousMediaItem(@Nullable IMediaController caller, int sequenceNumber) {
|
||||||
if (caller == null) {
|
if (caller == null) {
|
||||||
|
@ -484,6 +484,18 @@ import java.util.List;
|
|||||||
super.moveMediaItems(fromIndex, toIndex, newIndex);
|
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
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPrevious() {
|
public boolean hasPrevious() {
|
||||||
|
@ -62,6 +62,8 @@ interface IRemoteMediaController {
|
|||||||
void clearMediaItems(String controllerId);
|
void clearMediaItems(String controllerId);
|
||||||
void moveMediaItem(String controllerId, int currentIndex, int newIndex);
|
void moveMediaItem(String controllerId, int currentIndex, int newIndex);
|
||||||
void moveMediaItems(String controllerId, int fromIndex, int toIndex, 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 seekToPreviousMediaItem(String controllerId);
|
||||||
void seekToNextMediaItem(String controllerId);
|
void seekToNextMediaItem(String controllerId);
|
||||||
void seekToPrevious(String controllerId);
|
void seekToPrevious(String controllerId);
|
||||||
|
@ -3240,6 +3240,334 @@ public class MediaControllerStateMaskingTest {
|
|||||||
assertThat(itemsAfterMove).containsExactly(items.get(1), items.get(0)).inOrder();
|
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(
|
private void assertMoveMediaItems(
|
||||||
int initialMediaItemCount,
|
int initialMediaItemCount,
|
||||||
int initialMediaItemIndex,
|
int initialMediaItemIndex,
|
||||||
|
@ -1307,4 +1307,380 @@ public class MediaControllerStateMaskingWithMediaSessionCompatTest {
|
|||||||
|
|
||||||
assertThat(itemsAfterMove).containsExactly(items.get(1), items.get(0)).inOrder();
|
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));
|
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
|
@Test
|
||||||
public void setDeviceVolume() throws Exception {
|
public void setDeviceVolume() throws Exception {
|
||||||
testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceVolume(0));
|
testOnCommandRequest(COMMAND_SET_DEVICE_VOLUME, controller -> controller.setDeviceVolume(0));
|
||||||
|
@ -703,6 +703,34 @@ public class MediaSessionPlayerTest {
|
|||||||
assertThat(player.newIndex).isEqualTo(newIndex);
|
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
|
@Test
|
||||||
public void seekToPreviousMediaItem() throws Exception {
|
public void seekToPreviousMediaItem() throws Exception {
|
||||||
controller.seekToPreviousMediaItem();
|
controller.seekToPreviousMediaItem();
|
||||||
|
@ -370,6 +370,39 @@ public class MockPlayerTest {
|
|||||||
.inOrder();
|
.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
|
@Test
|
||||||
public void seekToPreviousMediaItem() {
|
public void seekToPreviousMediaItem() {
|
||||||
player.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
|
@Override
|
||||||
public void seekToPreviousMediaItem(String controllerId) throws RemoteException {
|
public void seekToPreviousMediaItem(String controllerId) throws RemoteException {
|
||||||
runOnHandler(
|
runOnHandler(
|
||||||
|
@ -107,7 +107,9 @@ public class MockPlayer implements Player {
|
|||||||
METHOD_SET_SHUFFLE_MODE,
|
METHOD_SET_SHUFFLE_MODE,
|
||||||
METHOD_SET_TRACK_SELECTION_PARAMETERS,
|
METHOD_SET_TRACK_SELECTION_PARAMETERS,
|
||||||
METHOD_SET_VOLUME,
|
METHOD_SET_VOLUME,
|
||||||
METHOD_STOP
|
METHOD_STOP,
|
||||||
|
METHOD_REPLACE_MEDIA_ITEM,
|
||||||
|
METHOD_REPLACE_MEDIA_ITEMS
|
||||||
})
|
})
|
||||||
public @interface Method {}
|
public @interface Method {}
|
||||||
|
|
||||||
@ -203,6 +205,10 @@ public class MockPlayer implements Player {
|
|||||||
public static final int METHOD_SET_DEVICE_MUTED_WITH_FLAGS = 44;
|
public static final int METHOD_SET_DEVICE_MUTED_WITH_FLAGS = 44;
|
||||||
/** Maps to {@link Player#setDeviceVolume(int, int)}. */
|
/** Maps to {@link Player#setDeviceVolume(int, int)}. */
|
||||||
public static final int METHOD_SET_DEVICE_VOLUME_WITH_FLAGS = 45;
|
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 boolean changePlayerStateWithTransportControl;
|
||||||
private final Looper applicationLooper;
|
private final Looper applicationLooper;
|
||||||
@ -990,6 +996,22 @@ public class MockPlayer implements Player {
|
|||||||
checkNotNull(conditionVariables.get(METHOD_MOVE_MEDIA_ITEMS)).open();
|
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.
|
* @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_TRACK_SELECTION_PARAMETERS, new ConditionVariable())
|
||||||
.put(METHOD_SET_VOLUME, new ConditionVariable())
|
.put(METHOD_SET_VOLUME, new ConditionVariable())
|
||||||
.put(METHOD_STOP, new ConditionVariable())
|
.put(METHOD_STOP, new ConditionVariable())
|
||||||
|
.put(METHOD_REPLACE_MEDIA_ITEM, new ConditionVariable())
|
||||||
|
.put(METHOD_REPLACE_MEDIA_ITEMS, new ConditionVariable())
|
||||||
.buildOrThrow();
|
.buildOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +218,16 @@ public class RemoteMediaController {
|
|||||||
binder.moveMediaItems(controllerId, fromIndex, toIndex, newIndex);
|
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 {
|
public void seekToPreviousMediaItem() throws RemoteException {
|
||||||
binder.seekToPreviousMediaItem(controllerId);
|
binder.seekToPreviousMediaItem(controllerId);
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,11 @@ public class StubPlayer extends BasePlayer {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user