Commit 851c915e by kimvde Committed by Ian Baker

Add COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM to available commands

PiperOrigin-RevId: 362036291
parent 383b6d42
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
...@@ -45,6 +46,7 @@ import com.google.android.gms.cast.framework.media.MediaQueue; ...@@ -45,6 +46,7 @@ import com.google.android.gms.cast.framework.media.MediaQueue;
import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
...@@ -69,11 +71,13 @@ public class CastPlayerTest { ...@@ -69,11 +71,13 @@ public class CastPlayerTest {
@Mock private MediaStatus mockMediaStatus; @Mock private MediaStatus mockMediaStatus;
@Mock private MediaInfo mockMediaInfo; @Mock private MediaInfo mockMediaInfo;
@Mock private MediaQueue mockMediaQueue; @Mock private MediaQueue mockMediaQueue;
@Mock private MediaQueueItem mockMediaQueueItem;
@Mock private CastContext mockCastContext; @Mock private CastContext mockCastContext;
@Mock private SessionManager mockSessionManager; @Mock private SessionManager mockSessionManager;
@Mock private CastSession mockCastSession; @Mock private CastSession mockCastSession;
@Mock private Player.EventListener mockListener; @Mock private Player.EventListener mockListener;
@Mock private PendingResult<RemoteMediaClient.MediaChannelResult> mockPendingResult; @Mock private PendingResult<RemoteMediaClient.MediaChannelResult> mockPendingResult;
@Mock private RemoteMediaClient.MediaChannelResult mediaChannelResultMock;
@Captor @Captor
private ArgumentCaptor<ResultCallback<RemoteMediaClient.MediaChannelResult>> private ArgumentCaptor<ResultCallback<RemoteMediaClient.MediaChannelResult>>
...@@ -93,6 +97,8 @@ public class CastPlayerTest { ...@@ -93,6 +97,8 @@ public class CastPlayerTest {
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus); when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue); when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
when(mockMediaQueue.getItemIds()).thenReturn(new int[0]); when(mockMediaQueue.getItemIds()).thenReturn(new int[0]);
when(mockRemoteMediaClient.getCurrentItem()).thenReturn(mockMediaQueueItem);
when(mediaChannelResultMock.getStatus()).thenReturn(Status.RESULT_SUCCESS);
// Make the remote media client present the same default values as ExoPlayer: // Make the remote media client present the same default values as ExoPlayer:
when(mockRemoteMediaClient.isPaused()).thenReturn(true); when(mockRemoteMediaClient.isPaused()).thenReturn(true);
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF); when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_OFF);
...@@ -463,7 +469,8 @@ public class CastPlayerTest { ...@@ -463,7 +469,8 @@ public class CastPlayerTest {
List<MediaItem> mediaItems = ImmutableList.of(mediaItem); List<MediaItem> mediaItems = ImmutableList.of(mediaItem);
int[] mediaQueueItemIds = new int[] {1}; int[] mediaQueueItemIds = new int[] {1};
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener) verify(mockListener)
.onMediaItemTransition( .onMediaItemTransition(
...@@ -478,10 +485,15 @@ public class CastPlayerTest { ...@@ -478,10 +485,15 @@ public class CastPlayerTest {
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
clearMediaItemsAndUpdateTimeline(); castPlayer.clearMediaItems();
updateTimeLine(
/* mediaItems= */ ImmutableList.of(),
/* mediaQueueItemIds= */ new int[0],
/* currentItemId= */ C.INDEX_UNSET);
verify(mockListener) verify(mockListener)
.onMediaItemTransition( .onMediaItemTransition(
/* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
...@@ -495,11 +507,15 @@ public class CastPlayerTest { ...@@ -495,11 +507,15 @@ public class CastPlayerTest {
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2); List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
removeMediaItemsAndUpdateTimeline( castPlayer.removeMediaItem(/* index= */ 0);
mediaItems, mediaQueueItemIds, /* fromIndex= */ 0, /* toIndex= */ 1); updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2);
verify(mockListener, times(2)) verify(mockListener, times(2))
.onMediaItemTransition( .onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
...@@ -510,14 +526,20 @@ public class CastPlayerTest { ...@@ -510,14 +526,20 @@ public class CastPlayerTest {
@Test @Test
public void removeNonCurrentMediaItem_doesNotNotifyMediaItemTransition() { public void removeNonCurrentMediaItem_doesNotNotifyMediaItemTransition() {
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
removeMediaItemsAndUpdateTimeline( castPlayer.removeMediaItem(/* index= */ 1);
mediaItems, mediaQueueItemIds, /* fromIndex= */ 1, /* toIndex= */ 2); updateTimeLine(
ImmutableList.of(mediaItem1),
/* mediaQueueItemIds= */ new int[] {1},
/* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
} }
...@@ -530,7 +552,8 @@ public class CastPlayerTest { ...@@ -530,7 +552,8 @@ public class CastPlayerTest {
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2); List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
...@@ -549,7 +572,8 @@ public class CastPlayerTest { ...@@ -549,7 +572,8 @@ public class CastPlayerTest {
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt()); verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
...@@ -557,89 +581,232 @@ public class CastPlayerTest { ...@@ -557,89 +581,232 @@ public class CastPlayerTest {
} }
@Test @Test
public void seekTo_otherWindow_notifiesAvailableCommandsChanged() { public void seekTo_nextWindow_notifiesAvailableCommandsChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult); .thenReturn(mockPendingResult);
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
int[] mediaQueueItemIds = new int[] {1, 2, 3}; Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
int[] mediaQueueItemIds = new int[] {1, 2, 3, 4};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
}
@Test
public void seekTo_previousWindow_notifiesAvailableCommandsChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
int[] mediaQueueItemIds = new int[] {1, 2, 3, 4};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 4);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any()); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener, times(3)).onAvailableCommandsChanged(any()); verify(mockListener, times(3)).onAvailableCommandsChanged(any());
} }
@Test @Test
public void addMediaItems_whenLastPlaying_notifiesAvailableCommandsChanged() { @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
Player.Commands commandsWithHasNext = public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() {
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult);
int[] mediaQueueItemIds = new int[] {1}; int[] mediaQueueItemIds = new int[] {1};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100);
verify(mockListener, never()).onAvailableCommandsChanged(any());
}
@Test
public void addMediaItem_atTheEnd_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3);
castPlayer.addMediaItem(mediaItem1);
updateTimeLine(
ImmutableList.of(mediaItem1),
/* mediaQueueItemIds= */ new int[] {1},
/* currentItemId= */ 1);
verify(mockListener, never()).onAvailableCommandsChanged(any()); verify(mockListener, never()).onAvailableCommandsChanged(any());
int[] mediaQueueItemIdsToAdd = new int[] {2}; castPlayer.addMediaItem(mediaItem2);
List<MediaItem> mediaItemsToAdd = createMediaItems(mediaQueueItemIdsToAdd); updateTimeLine(
addMediaItemsAndUpdateTimeline( ImmutableList.of(mediaItem1, mediaItem2),
/* existingMediaItems= */ mediaItems, /* mediaQueueItemIds= */ new int[] {1, 2},
/* existingMediaQueueItemIds= */ mediaQueueItemIds, /* currentItemId= */ 1);
mediaItemsToAdd,
mediaQueueItemIdsToAdd);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
mediaQueueItemIdsToAdd = new int[] {3}; castPlayer.addMediaItem(mediaItem3);
mediaItemsToAdd = createMediaItems(mediaQueueItemIdsToAdd); updateTimeLine(
addMediaItemsAndUpdateTimeline( ImmutableList.of(mediaItem1, mediaItem2, mediaItem3),
/* existingMediaItems= */ mediaItems, /* mediaQueueItemIds= */ new int[] {1, 2, 3},
/* existingMediaQueueItemIds= */ mediaQueueItemIds, /* currentItemId= */ 1);
mediaItemsToAdd,
mediaQueueItemIdsToAdd);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
} }
@Test @Test
public void removeMediaItems_followingCurrent_notifiesAvailableCommandsChanged() { public void addMediaItem_atTheStart_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3);
int[] mediaQueueItemIds = new int[] {1, 2, 3};
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2, mediaItem3);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItem(mediaItem1);
updateTimeLine(
ImmutableList.of(mediaItem1),
/* mediaQueueItemIds= */ new int[] {1},
/* currentItemId= */ 1);
verify(mockListener, never()).onAvailableCommandsChanged(any());
castPlayer.addMediaItem(/* index= */ 0, mediaItem2);
updateTimeLine(
ImmutableList.of(mediaItem2, mediaItem1),
/* mediaQueueItemIds= */ new int[] {2, 1},
/* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.addMediaItem(/* index= */ 0, mediaItem3);
updateTimeLine(
ImmutableList.of(mediaItem3, mediaItem2, mediaItem1),
/* mediaQueueItemIds= */ new int[] {3, 2, 1},
/* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(any());
}
@Test
public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3);
castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2, mediaItem3));
updateTimeLine(
ImmutableList.of(mediaItem1, mediaItem2, mediaItem3),
/* mediaQueueItemIds= */ new int[] {1, 2, 3},
/* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
removeMediaItemsAndUpdateTimeline( castPlayer.removeMediaItem(/* index= */ 2);
/* existingMediaItems= */ mediaItems, updateTimeLine(
/* existingMediaQueueItemIds= */ mediaQueueItemIds, ImmutableList.of(mediaItem1, mediaItem2),
/* fromIndex= */ 2, /* mediaQueueItemIds= */ new int[] {1, 2},
/* toIndex= */ 3); /* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
removeMediaItemsAndUpdateTimeline( castPlayer.removeMediaItem(/* index= */ 1);
/* existingMediaItems= */ ImmutableList.of(mediaItem1, mediaItem2), updateTimeLine(
/* existingMediaQueueItemIds= */ new int[] {1, 2}, ImmutableList.of(mediaItem1),
/* fromIndex= */ 1, /* mediaQueueItemIds= */ new int[] {1},
/* toIndex= */ 2); /* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.removeMediaItem(/* index= */ 0);
updateTimeLine(
ImmutableList.of(),
/* mediaQueueItemIds= */ new int[0],
/* currentItemId= */ C.INDEX_UNSET);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
}
@Test
public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3);
castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2, mediaItem3));
updateTimeLine(
ImmutableList.of(mediaItem1, mediaItem2, mediaItem3),
/* mediaQueueItemIds= */ new int[] {1, 2, 3},
/* currentItemId= */ 3);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.removeMediaItem(/* index= */ 0);
updateTimeLine(
ImmutableList.of(mediaItem2, mediaItem3),
/* mediaQueueItemIds= */ new int[] {2, 3},
/* currentItemId= */ 3);
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.removeMediaItem(/* index= */ 0);
updateTimeLine(
ImmutableList.of(mediaItem3),
/* mediaQueueItemIds= */ new int[] {3},
/* currentItemId= */ 3);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
castPlayer.removeMediaItem(/* index= */ 0);
updateTimeLine(
ImmutableList.of(),
/* mediaQueueItemIds= */ new int[0],
/* currentItemId= */ C.INDEX_UNSET);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
}
@Test
public void removeMediaItem_current_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
castPlayer.addMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
updateTimeLine(
ImmutableList.of(mediaItem1, mediaItem2),
/* mediaQueueItemIds= */ new int[] {1, 2},
/* currentItemId= */ 1);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any());
castPlayer.removeMediaItem(/* index= */ 0);
updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any()); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
} }
...@@ -648,16 +815,17 @@ public class CastPlayerTest { ...@@ -648,16 +815,17 @@ public class CastPlayerTest {
public void setRepeatMode_all_notifiesAvailableCommandsChanged() { public void setRepeatMode_all_notifiesAvailableCommandsChanged() {
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null)))
.thenReturn(mockPendingResult); .thenReturn(mockPendingResult);
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNextAndPrevious =
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
int[] mediaQueueItemIds = new int[] {1}; int[] mediaQueueItemIds = new int[] {1};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener, never()).onAvailableCommandsChanged(any()); verify(mockListener, never()).onAvailableCommandsChanged(any());
castPlayer.setRepeatMode(Player.REPEAT_MODE_ALL); castPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
} }
...@@ -668,7 +836,8 @@ public class CastPlayerTest { ...@@ -668,7 +836,8 @@ public class CastPlayerTest {
int[] mediaQueueItemIds = new int[] {1}; int[] mediaQueueItemIds = new int[] {1};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.setRepeatMode(Player.REPEAT_MODE_ONE); castPlayer.setRepeatMode(Player.REPEAT_MODE_ONE);
verify(mockListener, never()).onAvailableCommandsChanged(any()); verify(mockListener, never()).onAvailableCommandsChanged(any());
} }
...@@ -698,66 +867,13 @@ public class CastPlayerTest { ...@@ -698,66 +867,13 @@ public class CastPlayerTest {
} }
private void addMediaItemsAndUpdateTimeline(List<MediaItem> mediaItems, int[] mediaQueueItemIds) { private void addMediaItemsAndUpdateTimeline(List<MediaItem> mediaItems, int[] mediaQueueItemIds) {
addMediaItemsAndUpdateTimeline( Assertions.checkState(mediaItems.size() == mediaQueueItemIds.length);
/* existingMediaItems= */ ImmutableList.of(), castPlayer.addMediaItems(mediaItems);
/* existingMediaQueueItemIds= */ new int[0], updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
/* mediaItemsToAdd= */ mediaItems,
/* mediaQueueItemIdsToAdd= */ mediaQueueItemIds);
}
private void addMediaItemsAndUpdateTimeline(
List<MediaItem> existingMediaItems,
int[] existingMediaQueueItemIds,
List<MediaItem> mediaItemsToAdd,
int[] mediaQueueItemIdsToAdd) {
Assertions.checkState(existingMediaItems.size() == existingMediaQueueItemIds.length);
Assertions.checkState(mediaItemsToAdd.size() == mediaQueueItemIdsToAdd.length);
List<MediaItem> mediaItems = new ArrayList<>();
mediaItems.addAll(existingMediaItems);
mediaItems.addAll(mediaItemsToAdd);
int existingMediaItemCount = existingMediaQueueItemIds.length;
int mediaItemToAddCount = mediaQueueItemIdsToAdd.length;
int[] mediaQueueItemIds = new int[existingMediaItemCount + mediaItemToAddCount];
System.arraycopy(existingMediaQueueItemIds, 0, mediaQueueItemIds, 0, existingMediaItemCount);
System.arraycopy(
mediaQueueItemIdsToAdd, 0, mediaQueueItemIds, existingMediaItemCount, mediaItemToAddCount);
castPlayer.addMediaItems(mediaItemsToAdd);
updateTimeLine(mediaItems, mediaQueueItemIds);
}
private void removeMediaItemsAndUpdateTimeline(
List<MediaItem> existingMediaItems,
int[] existingMediaQueueItemIds,
int fromIndex,
int toIndex) {
Assertions.checkState(existingMediaItems.size() == existingMediaQueueItemIds.length);
Assertions.checkState(fromIndex >= 0);
Assertions.checkState(fromIndex < toIndex);
int existingMediaItemCount = existingMediaItems.size();
Assertions.checkState(toIndex <= existingMediaItemCount);
List<MediaItem> mediaItems = new ArrayList<>();
int[] mediaQueueItemIds = new int[existingMediaItemCount - toIndex + fromIndex];
for (int i = 0; i < existingMediaItemCount; i++) {
if (i < fromIndex || i >= toIndex) {
mediaItems.add(existingMediaItems.get(i));
mediaQueueItemIds[mediaItems.size() - 1] = existingMediaQueueItemIds[i];
}
}
castPlayer.removeMediaItems(fromIndex, toIndex);
updateTimeLine(mediaItems, mediaQueueItemIds);
}
private void clearMediaItemsAndUpdateTimeline() {
castPlayer.clearMediaItems();
updateTimeLine(ImmutableList.of(), new int[0]);
} }
private void updateTimeLine(List<MediaItem> mediaItems, int[] mediaQueueItemIds) { private void updateTimeLine(
List<MediaItem> mediaItems, int[] mediaQueueItemIds, int currentItemId) {
List<MediaQueueItem> queueItems = new ArrayList<>(); List<MediaQueueItem> queueItems = new ArrayList<>();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter(); DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
for (MediaItem mediaItem : mediaItems) { for (MediaItem mediaItem : mediaItems) {
...@@ -766,7 +882,10 @@ public class CastPlayerTest { ...@@ -766,7 +882,10 @@ public class CastPlayerTest {
// Set up mocks to allow the player to update the timeline. // Set up mocks to allow the player to update the timeline.
when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds); when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds);
when(mockMediaStatus.getCurrentItemId()).thenReturn(1); if (currentItemId != C.INDEX_UNSET) {
when(mockMediaQueueItem.getItemId()).thenReturn(currentItemId);
when(mockMediaStatus.getCurrentItemId()).thenReturn(currentItemId);
}
when(mockMediaStatus.getMediaInfo()).thenReturn(mockMediaInfo); when(mockMediaStatus.getMediaInfo()).thenReturn(mockMediaInfo);
when(mockMediaInfo.getStreamType()).thenReturn(MediaInfo.STREAM_TYPE_NONE); when(mockMediaInfo.getStreamType()).thenReturn(MediaInfo.STREAM_TYPE_NONE);
when(mockMediaStatus.getQueueItems()).thenReturn(queueItems); when(mockMediaStatus.getQueueItems()).thenReturn(queueItems);
...@@ -774,4 +893,12 @@ public class CastPlayerTest { ...@@ -774,4 +893,12 @@ public class CastPlayerTest {
// Call listener to update the timeline of the player. // Call listener to update the timeline of the player.
remoteMediaClientCallback.onQueueStatusUpdated(); remoteMediaClientCallback.onQueueStatusUpdated();
} }
private static Player.Commands createCommands(@Player.Command int... commands) {
Player.Commands.Builder builder = new Player.Commands.Builder();
for (int command : commands) {
builder.add(command);
}
return builder.build();
}
} }
...@@ -98,7 +98,10 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu ...@@ -98,7 +98,10 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
if (!timeline.isEmpty() && !player.isPlayingAd()) { if (!timeline.isEmpty() && !player.isPlayingAd()) {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
enableSkipTo = timeline.getWindowCount() > 1; enableSkipTo = timeline.getWindowCount() > 1;
enablePrevious = window.isSeekable || !window.isLive() || player.hasPrevious(); enablePrevious =
window.isSeekable
|| !window.isLive()
|| player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
enableNext = enableNext =
(window.isLive() && window.isDynamic) (window.isLive() && window.isDynamic)
|| player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM); || player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
......
...@@ -266,6 +266,9 @@ public abstract class BasePlayer implements Player { ...@@ -266,6 +266,9 @@ public abstract class BasePlayer implements Player {
} }
protected Commands getAvailableCommands() { protected Commands getAvailableCommands() {
return new Commands.Builder().addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNext()).build(); return new Commands.Builder()
.addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNext())
.addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPrevious())
.build();
} }
} }
...@@ -1031,14 +1031,16 @@ public interface Player { ...@@ -1031,14 +1031,16 @@ public interface Player {
/** /**
* Commands that can be executed on a {@code Player}. One of {@link * Commands that can be executed on a {@code Player}. One of {@link
* #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}. * #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} or {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}) @IntDef({COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM})
@interface Command {} @interface Command {}
/** Command to seek to the next {@link MediaItem} in the playlist. */ /** Command to seek to the next {@link MediaItem} in the playlist. */
int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 0; int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 0;
/** Command to seek to the previous {@link MediaItem} in the playlist. */
int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 1;
/** Returns the component of this player for audio output, or null if audio is not supported. */ /** Returns the component of this player for audio output, or null if audio is not supported. */
@Nullable @Nullable
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilStartOfWindow;
import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled;
...@@ -4593,7 +4595,7 @@ public final class ExoPlayerTest { ...@@ -4593,7 +4595,7 @@ public final class ExoPlayerTest {
player.setHandleAudioBecomingNoisy(false); player.setHandleAudioBecomingNoisy(false);
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady(); boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady();
player.release(); player.release();
...@@ -4607,7 +4609,7 @@ public final class ExoPlayerTest { ...@@ -4607,7 +4609,7 @@ public final class ExoPlayerTest {
player.setHandleAudioBecomingNoisy(true); player.setHandleAudioBecomingNoisy(true);
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady(); boolean playWhenReadyAfterBroadcast = player.getPlayWhenReady();
player.release(); player.release();
...@@ -4734,7 +4736,7 @@ public final class ExoPlayerTest { ...@@ -4734,7 +4736,7 @@ public final class ExoPlayerTest {
// Wait until the MediaSource is prepared, i.e. returned its timeline, and at least one // Wait until the MediaSource is prepared, i.e. returned its timeline, and at least one
// iteration of doSomeWork after this was run. // iteration of doSomeWork after this was run.
TestPlayerRunHelper.runUntilTimelineChanged(player); TestPlayerRunHelper.runUntilTimelineChanged(player);
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
assertThat(player.getPlayerError()).isNull(); assertThat(player.getPlayerError()).isNull();
} }
...@@ -7593,7 +7595,7 @@ public final class ExoPlayerTest { ...@@ -7593,7 +7595,7 @@ public final class ExoPlayerTest {
// Start playback and wait until player is idly waiting for an update of the first source. // Start playback and wait until player is idly waiting for an update of the first source.
player.prepare(); player.prepare();
player.play(); player.play();
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
// Update media with a non-zero default start position and window offset. // Update media with a non-zero default start position and window offset.
firstMediaSource.setNewSourceInfo(timelineWithOffsets); firstMediaSource.setNewSourceInfo(timelineWithOffsets);
// Wait until player transitions to second source (which also has non-zero offsets). // Wait until player transitions to second source (which also has non-zero offsets).
...@@ -8069,57 +8071,118 @@ public final class ExoPlayerTest { ...@@ -8069,57 +8071,118 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void seekTo_otherWindow_notifiesAvailableCommandsChanged() { public void seekTo_nextWindow_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
player.addMediaSources( player.addMediaSources(
ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); ImmutableList.of(
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource()));
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
}
@Test
public void seekTo_previousWindow_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener);
player.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0);
player.addMediaSources(
ImmutableList.of(
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource()));
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any()); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener, times(3)).onAvailableCommandsChanged(any()); verify(mockListener, times(3)).onAvailableCommandsChanged(any());
} }
@Test @Test
public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() {
Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener);
player.addMediaSources(ImmutableList.of(new FakeMediaSource()));
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200);
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100);
verify(mockListener, never()).onAvailableCommandsChanged(any());
}
@Test
public void automaticWindowTransition_notifiesAvailableCommandsChanged() throws Exception { public void automaticWindowTransition_notifiesAvailableCommandsChanged() throws Exception {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
player.addMediaSources( player.addMediaSources(
ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); ImmutableList.of(
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource(),
new FakeMediaSource()));
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
player.prepare(); player.prepare();
playUntilStartOfWindow(player, /* windowIndex= */ 1);
runUntilPendingCommandsAreFullyHandled(player);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
playUntilStartOfWindow(player, /* windowIndex= */ 2);
runUntilPendingCommandsAreFullyHandled(player);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.play(); player.play();
runUntilPlaybackState(player, Player.STATE_ENDED); runUntilPlaybackState(player, Player.STATE_ENDED);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener, times(2)).onAvailableCommandsChanged(any()); verify(mockListener, times(3)).onAvailableCommandsChanged(any());
} }
@Test @Test
public void addMediaItems_whenLastPlaying_notifiesAvailableCommandsChanged() throws Exception { public void addMediaSource_atTheEnd_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
...@@ -8136,10 +8199,26 @@ public final class ExoPlayerTest { ...@@ -8136,10 +8199,26 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void removeMediaItems_followingCurrent_notifiesAvailableCommandsChanged() public void addMediaSource_atTheStart_notifiesAvailableCommandsChanged() {
throws Exception { Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.Commands commandsWithHasNext = Player.EventListener mockListener = mock(Player.EventListener.class);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener);
player.addMediaSource(new FakeMediaSource());
verify(mockListener, never()).onAvailableCommandsChanged(any());
player.addMediaSource(/* index= */ 0, new FakeMediaSource());
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any());
player.addMediaSource(/* index= */ 0, new FakeMediaSource());
verify(mockListener).onAvailableCommandsChanged(any());
}
@Test
public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
...@@ -8155,12 +8234,55 @@ public final class ExoPlayerTest { ...@@ -8155,12 +8234,55 @@ public final class ExoPlayerTest {
player.removeMediaItem(/* index= */ 1); player.removeMediaItem(/* index= */ 1);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any()); verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.removeMediaItem(/* index= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
}
@Test
public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener);
player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
player.addMediaSources(
ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource()));
verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
verify(mockListener).onAvailableCommandsChanged(any());
player.removeMediaItem(/* index= */ 0);
verify(mockListener).onAvailableCommandsChanged(any());
player.removeMediaItem(/* index= */ 0);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
player.removeMediaItem(/* index= */ 0);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
} }
@Test @Test
public void setRepeatMode_all_notifiesAvailableCommandsChanged() throws Exception { public void removeMediaItem_current_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener);
player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource()));
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
verify(mockListener).onAvailableCommandsChanged(any());
player.removeMediaItem(/* index= */ 0);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
}
@Test
public void setRepeatMode_all_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNextAndPrevious =
createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
...@@ -8169,12 +8291,12 @@ public final class ExoPlayerTest { ...@@ -8169,12 +8291,12 @@ public final class ExoPlayerTest {
verify(mockListener, never()).onAvailableCommandsChanged(any()); verify(mockListener, never()).onAvailableCommandsChanged(any());
player.setRepeatMode(Player.REPEAT_MODE_ALL); player.setRepeatMode(Player.REPEAT_MODE_ALL);
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNextAndPrevious);
verify(mockListener).onAvailableCommandsChanged(any()); verify(mockListener).onAvailableCommandsChanged(any());
} }
@Test @Test
public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() throws Exception { public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() {
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
...@@ -8185,9 +8307,9 @@ public final class ExoPlayerTest { ...@@ -8185,9 +8307,9 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void setShuffleModeEnabled_notifiesAvailableCommandsChanged() throws Exception { public void setShuffleModeEnabled_notifiesAvailableCommandsChanged() {
Player.Commands commandsWithHasNext = Player.Commands commandsWithHasNext = createCommands(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
new Player.Commands.Builder().add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); Player.Commands commandsWithHasPrevious = createCommands(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
Player.EventListener mockListener = mock(Player.EventListener.class); Player.EventListener mockListener = mock(Player.EventListener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(mockListener); player.addListener(mockListener);
...@@ -8202,7 +8324,7 @@ public final class ExoPlayerTest { ...@@ -8202,7 +8324,7 @@ public final class ExoPlayerTest {
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext); verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
player.setShuffleModeEnabled(true); player.setShuffleModeEnabled(true);
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY); verify(mockListener).onAvailableCommandsChanged(commandsWithHasPrevious);
} }
@Test @Test
...@@ -9036,7 +9158,7 @@ public final class ExoPlayerTest { ...@@ -9036,7 +9158,7 @@ public final class ExoPlayerTest {
player.setMediaSource(new FakeMediaSource(new FakeTimeline(), formatWithStaticMetadata)); player.setMediaSource(new FakeMediaSource(new FakeTimeline(), formatWithStaticMetadata));
player.seekTo(2_000); player.seekTo(2_000);
player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f)); player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f));
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
verify(listener).onTimelineChanged(any(), anyInt()); verify(listener).onTimelineChanged(any(), anyInt());
verify(listener).onMediaItemTransition(any(), anyInt()); verify(listener).onMediaItemTransition(any(), anyInt());
...@@ -9059,7 +9181,7 @@ public final class ExoPlayerTest { ...@@ -9059,7 +9181,7 @@ public final class ExoPlayerTest {
} }
}); });
player.setRepeatMode(Player.REPEAT_MODE_ONE); player.setRepeatMode(Player.REPEAT_MODE_ONE);
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
verify(listener).onRepeatModeChanged(anyInt()); verify(listener).onRepeatModeChanged(anyInt());
verify(listener).onShuffleModeEnabledChanged(anyBoolean()); verify(listener).onShuffleModeEnabledChanged(anyBoolean());
...@@ -9075,7 +9197,7 @@ public final class ExoPlayerTest { ...@@ -9075,7 +9197,7 @@ public final class ExoPlayerTest {
player.play(); player.play();
player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4")); player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4"));
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE);
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player); runUntilPendingCommandsAreFullyHandled(player);
player.release(); player.release();
// Verify that all callbacks have been called at least once. // Verify that all callbacks have been called at least once.
...@@ -9148,6 +9270,14 @@ public final class ExoPlayerTest { ...@@ -9148,6 +9270,14 @@ public final class ExoPlayerTest {
return false; return false;
} }
private static Player.Commands createCommands(@Player.Command int... commands) {
Player.Commands.Builder builder = new Player.Commands.Builder();
for (int command : commands) {
builder.add(command);
}
return builder.build();
}
// Internal classes. // Internal classes.
/** {@link FakeRenderer} that can sleep and be woken-up. */ /** {@link FakeRenderer} that can sleep and be woken-up. */
......
...@@ -913,7 +913,10 @@ public class PlayerControlView extends FrameLayout { ...@@ -913,7 +913,10 @@ public class PlayerControlView extends FrameLayout {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
boolean isSeekable = window.isSeekable; boolean isSeekable = window.isSeekable;
enableSeeking = isSeekable; enableSeeking = isSeekable;
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious(); enablePrevious =
isSeekable
|| !window.isLive()
|| player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
enableRewind = isSeekable && controlDispatcher.isRewindEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = enableNext =
......
...@@ -1460,7 +1460,10 @@ public class PlayerNotificationManager { ...@@ -1460,7 +1460,10 @@ public class PlayerNotificationManager {
if (!timeline.isEmpty() && !player.isPlayingAd()) { if (!timeline.isEmpty() && !player.isPlayingAd()) {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
boolean isSeekable = window.isSeekable; boolean isSeekable = window.isSeekable;
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious(); enablePrevious =
isSeekable
|| !window.isLive()
|| player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
enableRewind = isSeekable && controlDispatcher.isRewindEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = enableNext =
......
...@@ -1148,7 +1148,10 @@ public class StyledPlayerControlView extends FrameLayout { ...@@ -1148,7 +1148,10 @@ public class StyledPlayerControlView extends FrameLayout {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
boolean isSeekable = window.isSeekable; boolean isSeekable = window.isSeekable;
enableSeeking = isSeekable; enableSeeking = isSeekable;
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious(); enablePrevious =
isSeekable
|| !window.isLive()
|| player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
enableRewind = isSeekable && controlDispatcher.isRewindEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = enableNext =
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment