diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java index b7f895b1f0..8b4f44aece 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java @@ -31,6 +31,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_WINDOW; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME; @@ -1207,6 +1208,7 @@ public class CastPlayerTest { assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_WINDOW)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_WINDOW)).isFalse(); + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_NEXT_WINDOW)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_WINDOW)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_BACK)).isTrue(); @@ -1251,35 +1253,74 @@ public class CastPlayerTest { assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_FORWARD)).isFalse(); } + @Test + public void isCommandAvailable_duringUnseekableLiveItem_isFalseForSeekToPrevious() { + MediaItem mediaItem = createMediaItem(/* mediaQueueItemId= */ 1); + List mediaItems = ImmutableList.of(mediaItem); + int[] mediaQueueItemIds = new int[] {1}; + int[] streamTypes = new int[] {MediaInfo.STREAM_TYPE_LIVE}; + long[] durationsMs = new long[] {C.TIME_UNSET}; + + castPlayer.addMediaItem(mediaItem); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 1, + streamTypes, + durationsMs, + /* positionMs= */ C.TIME_UNSET); + + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isFalse(); + } + + @Test + public void + isCommandAvailable_duringUnseekableLiveItemWithPreviousWindow_isTrueForSeekToPrevious() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + int[] streamTypes = new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_LIVE}; + long[] durationsMs = new long[] {10_000, C.TIME_UNSET}; + + castPlayer.addMediaItems(mediaItems); + updateTimeLine( + mediaItems, + mediaQueueItemIds, + /* currentItemId= */ 2, + streamTypes, + durationsMs, + /* positionMs= */ C.TIME_UNSET); + + assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isTrue(); + } + @Test public void seekTo_nextWindow_notifiesAvailableCommandsChanged() { when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) .thenReturn(mockPendingResult); - Player.Commands commandsWithSeekInCurrentAndToNext = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekInCurrentAndToPrevious = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); - Player.Commands commandsWithSeekAnywhere = - createWithPermanentAndSeekInCurrentCommands( - COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToPreviousWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); int[] mediaQueueItemIds = new int[] {1, 2, 3, 4}; List mediaItems = createMediaItems(mediaQueueItemIds); castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); 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(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -1287,31 +1328,30 @@ public class CastPlayerTest { public void seekTo_previousWindow_notifiesAvailableCommandsChanged() { when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) .thenReturn(mockPendingResult); - Player.Commands commandsWithSeekInCurrentAndToNext = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekInCurrentAndToPrevious = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); - Player.Commands commandsWithSeekAnywhere = - createWithPermanentAndSeekInCurrentCommands( - COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToPreviousWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); int[] mediaQueueItemIds = new int[] {1, 2, 3, 4}; List mediaItems = createMediaItems(mediaQueueItemIds); castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 4); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -1319,13 +1359,13 @@ public class CastPlayerTest { @SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer. public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() { when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult); - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); + Player.Commands defaultCommands = createWithDefaultCommands(); int[] mediaQueueItemIds = new int[] {1}; List mediaItems = createMediaItems(mediaQueueItemIds); castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); @@ -1335,10 +1375,9 @@ public class CastPlayerTest { @Test public void addMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; - Player.Commands commandsWithSeekInCurrentAndToNext = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); @@ -1348,7 +1387,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1), /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1357,7 +1396,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1, mediaItem2), /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.addMediaItem(mediaItem3); @@ -1370,10 +1409,9 @@ public class CastPlayerTest { @Test public void addMediaItem_atTheStart_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; - Player.Commands commandsWithSeekInCurrentAndToPrevious = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); @@ -1383,7 +1421,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1), /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1392,7 +1430,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem2, mediaItem1), /* mediaQueueItemIds= */ new int[] {2, 1}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.addMediaItem(/* index= */ 0, mediaItem3); @@ -1405,11 +1443,10 @@ public class CastPlayerTest { @Test public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithoutSeek = createWithPermanentCommands(); - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; - Player.Commands commandsWithSeekInCurrentAndToNext = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty= */ true); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); @@ -1419,7 +1456,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1, mediaItem2, mediaItem3), /* mediaQueueItemIds= */ new int[] {1, 2, 3}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1435,7 +1472,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1), /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.removeMediaItem(/* index= */ 0); @@ -1443,7 +1480,7 @@ public class CastPlayerTest { ImmutableList.of(), /* mediaQueueItemIds= */ new int[0], /* currentItemId= */ C.INDEX_UNSET); - verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener).onAvailableCommandsChanged(emptyTimelineCommands); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @@ -1451,11 +1488,10 @@ public class CastPlayerTest { public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() { when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) .thenReturn(mockPendingResult); - Player.Commands commandsWithoutSeek = createWithPermanentCommands(); - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; - Player.Commands commandsWithSeekInCurrentAndToPrevious = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty= */ true); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3); @@ -1465,7 +1501,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1, mediaItem2, mediaItem3), /* mediaQueueItemIds= */ new int[] {1, 2, 3}, /* currentItemId= */ 3); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1481,7 +1517,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem3), /* mediaQueueItemIds= */ new int[] {3}, /* currentItemId= */ 3); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); castPlayer.removeMediaItem(/* index= */ 0); @@ -1489,15 +1525,15 @@ public class CastPlayerTest { ImmutableList.of(), /* mediaQueueItemIds= */ new int[0], /* currentItemId= */ C.INDEX_UNSET); - verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener).onAvailableCommandsChanged(emptyTimelineCommands); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @Test public void removeMediaItem_current_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - Player.Commands commandsWithSeekInCurrentAndToNext = - createWithPermanentAndSeekInCurrentCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); @@ -1506,7 +1542,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem1, mediaItem2), /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1515,7 +1551,7 @@ public class CastPlayerTest { ImmutableList.of(mediaItem2), /* mediaQueueItemIds= */ new int[] {2}, /* currentItemId= */ 2); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @@ -1523,22 +1559,20 @@ public class CastPlayerTest { public void setRepeatMode_all_notifiesAvailableCommandsChanged() { when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) .thenReturn(mockPendingResult); - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; - Player.Commands commandsWithSeekAnywhere = - createWithPermanentAndSeekInCurrentCommands( - COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); int[] mediaQueueItemIds = new int[] {1}; List mediaItems = createMediaItems(mediaQueueItemIds); castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); castPlayer.setRepeatMode(Player.REPEAT_MODE_ALL); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekAnywhere); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @@ -1546,14 +1580,13 @@ public class CastPlayerTest { public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) .thenReturn(mockPendingResult); - Player.Commands commandsWithSeekInCurrent = createWithPermanentAndSeekInCurrentCommands(); - ; + Player.Commands defaultCommands = createWithDefaultCommands(); int[] mediaQueueItemIds = new int[] {1}; List mediaItems = createMediaItems(mediaQueueItemIds); castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrent); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -1648,22 +1681,22 @@ public class CastPlayerTest { remoteMediaClientCallback.onStatusUpdated(); } - private static Player.Commands createWithPermanentCommands( - @Player.Command int... additionalCommands) { + private static Player.Commands createWithDefaultCommands( + boolean isTimelineEmpty, @Player.Command int... additionalCommands) { Player.Commands.Builder builder = new Player.Commands.Builder(); builder.addAll(CastPlayer.PERMANENT_AVAILABLE_COMMANDS); + if (!isTimelineEmpty) { + builder.add(COMMAND_SEEK_IN_CURRENT_WINDOW); + builder.add(COMMAND_SEEK_TO_PREVIOUS); + builder.add(COMMAND_SEEK_BACK); + builder.add(COMMAND_SEEK_FORWARD); + } builder.addAll(additionalCommands); return builder.build(); } - private static Player.Commands createWithPermanentAndSeekInCurrentCommands( + private static Player.Commands createWithDefaultCommands( @Player.Command int... additionalCommands) { - Player.Commands.Builder builder = new Player.Commands.Builder(); - builder.addAll(CastPlayer.PERMANENT_AVAILABLE_COMMANDS); - builder.add(COMMAND_SEEK_IN_CURRENT_WINDOW); - builder.add(COMMAND_SEEK_BACK); - builder.add(COMMAND_SEEK_FORWARD); - builder.addAll(additionalCommands); - return builder.build(); + return createWithDefaultCommands(/* isTimelineEmpty= */ false, additionalCommands); } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index c406d8297a..a6c6791d99 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -296,6 +296,11 @@ public abstract class BasePlayer implements Player { .addIf(COMMAND_SEEK_TO_DEFAULT_POSITION, !isPlayingAd()) .addIf(COMMAND_SEEK_IN_CURRENT_WINDOW, isCurrentWindowSeekable() && !isPlayingAd()) .addIf(COMMAND_SEEK_TO_PREVIOUS_WINDOW, hasPrevious() && !isPlayingAd()) + .addIf( + COMMAND_SEEK_TO_PREVIOUS, + !getCurrentTimeline().isEmpty() + && (hasPrevious() || !isCurrentWindowLive() || isCurrentWindowSeekable()) + && !isPlayingAd()) .addIf(COMMAND_SEEK_TO_NEXT_WINDOW, hasNext() && !isPlayingAd()) .addIf(COMMAND_SEEK_TO_WINDOW, !isPlayingAd()) .addIf(COMMAND_SEEK_BACK, isCurrentWindowSeekable() && !isPlayingAd()) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 0f0dda5683..8c8a6e8ff8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -640,6 +640,7 @@ public interface Player { COMMAND_SEEK_TO_DEFAULT_POSITION, COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW, + COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_WINDOW, COMMAND_SEEK_BACK, @@ -1255,15 +1256,16 @@ public interface Player { * Commands that can be executed on a {@code Player}. One of {@link #COMMAND_PLAY_PAUSE}, {@link * #COMMAND_PREPARE_STOP}, {@link #COMMAND_SEEK_TO_DEFAULT_POSITION}, {@link * #COMMAND_SEEK_IN_CURRENT_WINDOW}, {@link #COMMAND_SEEK_TO_PREVIOUS_WINDOW}, {@link - * #COMMAND_SEEK_TO_NEXT_WINDOW}, {@link #COMMAND_SEEK_TO_WINDOW}, {@link #COMMAND_SEEK_BACK}, - * {@link #COMMAND_SEEK_FORWARD}, {@link #COMMAND_SET_SPEED_AND_PITCH}, {@link - * #COMMAND_SET_SHUFFLE_MODE}, {@link #COMMAND_SET_REPEAT_MODE}, {@link - * #COMMAND_GET_CURRENT_MEDIA_ITEM}, {@link #COMMAND_GET_TIMELINE}, {@link - * #COMMAND_GET_MEDIA_ITEMS_METADATA}, {@link #COMMAND_SET_MEDIA_ITEMS_METADATA}, {@link - * #COMMAND_CHANGE_MEDIA_ITEMS}, {@link #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link - * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link - * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link - * #COMMAND_SET_VIDEO_SURFACE} or {@link #COMMAND_GET_TEXT}. + * #COMMAND_SEEK_TO_PREVIOUS}, {@link #COMMAND_SEEK_TO_NEXT_WINDOW}, {@link + * #COMMAND_SEEK_TO_WINDOW}, {@link #COMMAND_SEEK_BACK}, {@link #COMMAND_SEEK_FORWARD}, {@link + * #COMMAND_SET_SPEED_AND_PITCH}, {@link #COMMAND_SET_SHUFFLE_MODE}, {@link + * #COMMAND_SET_REPEAT_MODE}, {@link #COMMAND_GET_CURRENT_MEDIA_ITEM}, {@link + * #COMMAND_GET_TIMELINE}, {@link #COMMAND_GET_MEDIA_ITEMS_METADATA}, {@link + * #COMMAND_SET_MEDIA_ITEMS_METADATA}, {@link #COMMAND_CHANGE_MEDIA_ITEMS}, {@link + * #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link #COMMAND_GET_VOLUME}, {@link + * #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link #COMMAND_SET_DEVICE_VOLUME}, + * {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link #COMMAND_SET_VIDEO_SURFACE} or {@link + * #COMMAND_GET_TEXT}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -1274,6 +1276,7 @@ public interface Player { COMMAND_SEEK_TO_DEFAULT_POSITION, COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW, + COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_WINDOW, COMMAND_SEEK_BACK, @@ -1306,46 +1309,48 @@ public interface Player { int COMMAND_SEEK_IN_CURRENT_WINDOW = 4; /** Command to seek to the default position of the previous window. */ int COMMAND_SEEK_TO_PREVIOUS_WINDOW = 5; + /** Command to seek to an earlier position in the current or previous window. */ + int COMMAND_SEEK_TO_PREVIOUS = 6; /** Command to seek to the default position of the next window. */ - int COMMAND_SEEK_TO_NEXT_WINDOW = 6; + int COMMAND_SEEK_TO_NEXT_WINDOW = 7; /** Command to seek anywhere in any window. */ - int COMMAND_SEEK_TO_WINDOW = 7; + int COMMAND_SEEK_TO_WINDOW = 8; /** Command to seek back by a fixed increment into the current window. */ - int COMMAND_SEEK_BACK = 8; + int COMMAND_SEEK_BACK = 9; /** Command to seek forward by a fixed increment into the current window. */ - int COMMAND_SEEK_FORWARD = 9; + int COMMAND_SEEK_FORWARD = 10; /** Command to set the playback speed and pitch. */ - int COMMAND_SET_SPEED_AND_PITCH = 10; + int COMMAND_SET_SPEED_AND_PITCH = 11; /** Command to enable shuffling. */ - int COMMAND_SET_SHUFFLE_MODE = 11; + int COMMAND_SET_SHUFFLE_MODE = 12; /** Command to set the repeat mode. */ - int COMMAND_SET_REPEAT_MODE = 12; + int COMMAND_SET_REPEAT_MODE = 13; /** Command to get the {@link MediaItem} of the current window. */ - int COMMAND_GET_CURRENT_MEDIA_ITEM = 13; + int COMMAND_GET_CURRENT_MEDIA_ITEM = 14; /** Command to get the information about the current timeline. */ - int COMMAND_GET_TIMELINE = 14; + int COMMAND_GET_TIMELINE = 15; /** Command to get the {@link MediaItem MediaItems} metadata. */ - int COMMAND_GET_MEDIA_ITEMS_METADATA = 15; + int COMMAND_GET_MEDIA_ITEMS_METADATA = 16; /** Command to set the {@link MediaItem MediaItems} metadata. */ - int COMMAND_SET_MEDIA_ITEMS_METADATA = 16; + int COMMAND_SET_MEDIA_ITEMS_METADATA = 17; /** Command to change the {@link MediaItem MediaItems} in the playlist. */ - int COMMAND_CHANGE_MEDIA_ITEMS = 17; + int COMMAND_CHANGE_MEDIA_ITEMS = 18; /** Command to get the player current {@link AudioAttributes}. */ - int COMMAND_GET_AUDIO_ATTRIBUTES = 18; + int COMMAND_GET_AUDIO_ATTRIBUTES = 19; /** Command to get the player volume. */ - int COMMAND_GET_VOLUME = 19; + int COMMAND_GET_VOLUME = 20; /** Command to get the device volume and whether it is muted. */ - int COMMAND_GET_DEVICE_VOLUME = 20; + int COMMAND_GET_DEVICE_VOLUME = 21; /** Command to set the player volume. */ - int COMMAND_SET_VOLUME = 21; + int COMMAND_SET_VOLUME = 22; /** Command to set the device volume and mute it. */ - int COMMAND_SET_DEVICE_VOLUME = 22; + int COMMAND_SET_DEVICE_VOLUME = 23; /** Command to increase and decrease the device volume and mute it. */ - int COMMAND_ADJUST_DEVICE_VOLUME = 23; + int COMMAND_ADJUST_DEVICE_VOLUME = 24; /** Command to set and clear the surface on which to render the video. */ - int COMMAND_SET_VIDEO_SURFACE = 24; + int COMMAND_SET_VIDEO_SURFACE = 25; /** Command to get the text that should currently be displayed by the player. */ - int COMMAND_GET_TEXT = 25; + int COMMAND_GET_TEXT = 26; /** Represents an invalid {@link Command}. */ int COMMAND_INVALID = -1; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 982c114f6f..ab3ae39157 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -31,6 +31,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_WINDOW; +import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_WINDOW; import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME; @@ -8165,6 +8166,7 @@ public final class ExoPlayerTest { assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_WINDOW)).isFalse(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_WINDOW)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT_WINDOW)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_WINDOW)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SEEK_BACK)).isFalse(); @@ -8214,6 +8216,7 @@ public final class ExoPlayerTest { assertThat(player.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_WINDOW)).isFalse(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_WINDOW)).isFalse(); + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isFalse(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT_WINDOW)).isFalse(); assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_WINDOW)).isFalse(); assertThat(player.isCommandAvailable(COMMAND_SEEK_BACK)).isFalse(); @@ -8240,13 +8243,67 @@ public final class ExoPlayerTest { assertThat(player.isCommandAvailable(COMMAND_SEEK_FORWARD)).isFalse(); } + @Test + public void isCommandAvailable_duringUnseekableLiveItem_isFalseForSeekToPrevious() + throws Exception { + Timeline timelineWithUnseekableLiveWindow = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ false, + /* isDynamic= */ true, + /* isLive= */ true, + /* isPlaceholder= */ false, + /* durationUs= */ C.TIME_UNSET, + /* defaultPositionUs= */ 10_000_000, + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + AdPlaybackState.NONE)); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + + player.addMediaSource(new FakeMediaSource(timelineWithUnseekableLiveWindow)); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isFalse(); + } + + @Test + public void + isCommandAvailable_duringUnseekableLiveItemWithPreviousWindow_isTrueForSeekToPrevious() + throws Exception { + Timeline timelineWithUnseekableLiveWindow = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0), + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 1, + /* isSeekable= */ false, + /* isDynamic= */ true, + /* isLive= */ true, + /* isPlaceholder= */ false, + /* durationUs= */ C.TIME_UNSET, + /* defaultPositionUs= */ 10_000_000, + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + AdPlaybackState.NONE)); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + + player.addMediaSource(new FakeMediaSource(timelineWithUnseekableLiveWindow)); + player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + player.prepare(); + runUntilPlaybackState(player, Player.STATE_READY); + + assertThat(player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS)).isTrue(); + } + @Test public void seekTo_nextWindow_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekToPrevious = + Player.Commands commandsWithSeekToPreviousWindow = createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); - Player.Commands commandsWithSeekToNextAndPrevious = - createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); @@ -8257,29 +8314,30 @@ public final class ExoPlayerTest { new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); 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(commandsWithSeekToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @Test public void seekTo_previousWindow_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekToPrevious = + Player.Commands commandsWithSeekToPreviousWindow = createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); - Player.Commands commandsWithSeekToNextAndPrevious = - createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); @@ -8291,44 +8349,49 @@ public final class ExoPlayerTest { new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @Test public void seekTo_sameWindow_doesNotNotifyAvailableCommandsChanged() { + Player.Commands defaultCommands = createWithDefaultCommands(); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSources(ImmutableList.of(new FakeMediaSource())); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); + player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200); player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100); - verify(mockListener, never()).onAvailableCommandsChanged(any()); + // Check that there were no other calls to onAvailableCommandsChanged. + verify(mockListener).onAvailableCommandsChanged(any()); } @Test public void automaticWindowTransition_notifiesAvailableCommandsChanged() throws Exception { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekInCurrentAndToNext = + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands commandsWithSeekInCurrentAndToNextWindow = createWithDefaultCommands( COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_BACK, COMMAND_SEEK_FORWARD, COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekInCurrentAndToPrevious = + Player.Commands commandsWithSeekInCurrentAndToPreviousWindow = createWithDefaultCommands( COMMAND_SEEK_IN_CURRENT_WINDOW, COMMAND_SEEK_BACK, @@ -8351,13 +8414,13 @@ public final class ExoPlayerTest { new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.prepare(); runUntilPlaybackState(player, Player.STATE_READY); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToNextWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); playUntilStartOfWindow(player, /* windowIndex= */ 1); @@ -8371,60 +8434,67 @@ public final class ExoPlayerTest { player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekInCurrentAndToPreviousWindow); verify(mockListener, times(4)).onAvailableCommandsChanged(any()); } @Test public void addMediaSource_atTheEnd_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSource(new FakeMediaSource()); - verify(mockListener, never()).onAvailableCommandsChanged(any()); - - player.addMediaSource(new FakeMediaSource()); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.addMediaSource(new FakeMediaSource()); - verify(mockListener).onAvailableCommandsChanged(any()); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.addMediaSource(new FakeMediaSource()); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @Test public void addMediaSource_atTheStart_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToPrevious = + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousWindow = createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); 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(commandsWithSeekToPrevious); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.addMediaSource(/* index= */ 0, new FakeMediaSource()); - verify(mockListener).onAvailableCommandsChanged(any()); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + + player.addMediaSource(/* index= */ 0, new FakeMediaSource()); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @Test public void removeMediaItem_atTheEnd_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); + Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty */ true); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSources( ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -8432,18 +8502,20 @@ public final class ExoPlayerTest { verify(mockListener).onAvailableCommandsChanged(any()); player.removeMediaItem(/* index= */ 1); - verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); player.removeMediaItem(/* index= */ 0); - verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + verify(mockListener).onAvailableCommandsChanged(emptyTimelineCommands); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @Test public void removeMediaItem_atTheStart_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToPrevious = + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousWindow = createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); - Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Commands emptyTimelineCommands = createWithDefaultCommands(/* isTimelineEmpty */ true); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); @@ -8451,7 +8523,7 @@ public final class ExoPlayerTest { player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0); player.addMediaSources( ImmutableList.of(new FakeMediaSource(), new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); @@ -8459,64 +8531,72 @@ public final class ExoPlayerTest { verify(mockListener).onAvailableCommandsChanged(any()); player.removeMediaItem(/* index= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); player.removeMediaItem(/* index= */ 0); - verify(mockListener, times(2)).onAvailableCommandsChanged(any()); + verify(mockListener).onAvailableCommandsChanged(emptyTimelineCommands); + verify(mockListener, times(3)).onAvailableCommandsChanged(any()); } @Test public void removeMediaItem_current_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithoutSeek = createWithDefaultCommands(); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.removeMediaItem(/* index= */ 0); - verify(mockListener).onAvailableCommandsChanged(commandsWithoutSeek); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @Test public void setRepeatMode_all_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNextAndPrevious = - createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW, COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands defaultCommands = createWithDefaultCommands(); + Player.Commands commandsWithSeekToPreviousAndNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW, COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSource(new FakeMediaSource()); - verify(mockListener, never()).onAvailableCommandsChanged(any()); - - player.setRepeatMode(Player.REPEAT_MODE_ALL); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextAndPrevious); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); + + player.setRepeatMode(Player.REPEAT_MODE_ALL); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow); + verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @Test public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { + Player.Commands defaultCommands = createWithDefaultCommands(); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); player.addMediaSource(new FakeMediaSource()); + verify(mockListener).onAvailableCommandsChanged(defaultCommands); + player.setRepeatMode(Player.REPEAT_MODE_ONE); - verify(mockListener, never()).onAvailableCommandsChanged(any()); + verify(mockListener).onAvailableCommandsChanged(any()); } @Test public void setShuffleModeEnabled_notifiesAvailableCommandsChanged() { - Player.Commands commandsWithSeekToNext = createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); - Player.Commands commandsWithSeekToPrevious = + Player.Commands commandsWithSeekToPreviousWindow = createWithDefaultCommands(COMMAND_SEEK_TO_PREVIOUS_WINDOW); + Player.Commands commandsWithSeekToNextWindow = + createWithDefaultCommands(COMMAND_SEEK_TO_NEXT_WINDOW); Player.Listener mockListener = mock(Player.Listener.class); ExoPlayer player = new TestExoPlayerBuilder(context).build(); player.addListener(mockListener); @@ -8528,12 +8608,12 @@ public final class ExoPlayerTest { new FakeMediaSource()); player.addMediaSource(mediaSource); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNext); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow); // Check that there were no other calls to onAvailableCommandsChanged. verify(mockListener).onAvailableCommandsChanged(any()); player.setShuffleModeEnabled(true); - verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPrevious); + verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow); verify(mockListener, times(2)).onAvailableCommandsChanged(any()); } @@ -10799,11 +10879,13 @@ public final class ExoPlayerTest { } private static Player.Commands createWithDefaultCommands( - @Player.Command int... additionalCommands) { + boolean isTimelineEmpty, @Player.Command int... additionalCommands) { Player.Commands.Builder builder = new Player.Commands.Builder(); builder.addAll( COMMAND_PLAY_PAUSE, COMMAND_PREPARE_STOP, + COMMAND_SEEK_TO_DEFAULT_POSITION, + COMMAND_SEEK_TO_WINDOW, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_REPEAT_MODE, @@ -10819,13 +10901,19 @@ public final class ExoPlayerTest { COMMAND_SET_DEVICE_VOLUME, COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_SET_VIDEO_SURFACE, - COMMAND_GET_TEXT, - COMMAND_SEEK_TO_DEFAULT_POSITION, - COMMAND_SEEK_TO_WINDOW); + COMMAND_GET_TEXT); + if (!isTimelineEmpty) { + builder.add(COMMAND_SEEK_TO_PREVIOUS); + } builder.addAll(additionalCommands); return builder.build(); } + private static Player.Commands createWithDefaultCommands( + @Player.Command int... additionalCommands) { + return createWithDefaultCommands(/* isTimelineEmpty= */ false, additionalCommands); + } + // Internal classes. /** {@link FakeRenderer} that can sleep and be woken-up. */