Add Player onAvailableCommandsChanged callback
PiperOrigin-RevId: 361122259
This commit is contained in:
parent
3de81c6d31
commit
0dcdbf0adf
@ -105,6 +105,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
private CastTimeline currentTimeline;
|
||||
private TrackGroupArray currentTrackGroups;
|
||||
private TrackSelectionArray currentTrackSelection;
|
||||
private Commands availableCommands;
|
||||
@Player.State private int playbackState;
|
||||
private int currentWindowIndex;
|
||||
private long lastReportedPositionMs;
|
||||
@ -147,6 +148,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||
availableCommands = Commands.EMPTY;
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
|
||||
@ -369,6 +371,11 @@ public final class CastPlayer extends BasePlayer {
|
||||
removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ currentTimeline.getWindowCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommandAvailable(@Command int command) {
|
||||
return availableCommands.contains(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
// Do nothing.
|
||||
@ -452,6 +459,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_POSITION_DISCONTINUITY,
|
||||
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK));
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
} else if (pendingSeekCount == 0) {
|
||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
|
||||
}
|
||||
@ -645,6 +653,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
|
||||
}
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
listeners.flushEvents();
|
||||
}
|
||||
|
||||
@ -693,6 +702,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
timeline, /* manifest= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
});
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -762,6 +772,16 @@ public final class CastPlayer extends BasePlayer {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateAvailableCommandsAndNotifyIfChanged() {
|
||||
Commands previousAvailableCommands = availableCommands;
|
||||
availableCommands = getAvailableCommands();
|
||||
if (!availableCommands.equals(previousAvailableCommands)) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
||||
listener -> listener.onAvailableCommandsChanged(availableCommands));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PendingResult<MediaChannelResult> setMediaItemsInternal(
|
||||
MediaQueueItem[] mediaQueueItems,
|
||||
@ -819,6 +839,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
this.repeatMode.value = repeatMode;
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1003,6 +1024,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
@Override
|
||||
public void onQueueStatusUpdated() {
|
||||
updateTimelineAndNotifyIfChanged();
|
||||
listeners.flushEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,11 +15,14 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.cast;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
@ -42,6 +45,7 @@ import com.google.android.gms.cast.framework.media.MediaQueue;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -293,7 +297,7 @@ public class CastPlayerTest {
|
||||
public void addMediaItems_insertAtIndex_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
String uri = "http://www.google.com/video3";
|
||||
MediaItem anotherMediaItem =
|
||||
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build();
|
||||
@ -316,7 +320,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItem_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2);
|
||||
|
||||
@ -328,7 +332,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItem_toBegin_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 0);
|
||||
|
||||
@ -340,7 +344,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItem_toEnd_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 4);
|
||||
|
||||
@ -355,7 +359,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItems_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 3, /* newIndex= */ 1);
|
||||
|
||||
@ -368,7 +372,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItems_toBeginning_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4, /* newIndex= */ 0);
|
||||
|
||||
@ -381,7 +385,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItems_toEnd_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 3);
|
||||
|
||||
@ -396,7 +400,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItems_noItems_doesNotCallRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 1, /* newIndex= */ 0);
|
||||
|
||||
@ -407,7 +411,7 @@ public class CastPlayerTest {
|
||||
public void moveMediaItems_noMove_doesNotCallRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 1);
|
||||
|
||||
@ -418,7 +422,7 @@ public class CastPlayerTest {
|
||||
public void removeMediaItems_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4);
|
||||
|
||||
@ -429,7 +433,7 @@ public class CastPlayerTest {
|
||||
public void clearMediaItems_callsRemoteMediaClient() {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
castPlayer.clearMediaItems();
|
||||
|
||||
@ -444,7 +448,7 @@ public class CastPlayerTest {
|
||||
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
|
||||
fillTimeline(mediaItems, mediaQueueItemIds);
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
|
||||
Timeline currentTimeline = castPlayer.getCurrentTimeline();
|
||||
for (int i = 0; i < mediaItems.size(); i++) {
|
||||
@ -453,6 +457,128 @@ public class CastPlayerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_otherWindow_notifiesAvailableCommandsChanged() {
|
||||
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
|
||||
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
|
||||
.thenReturn(mockPendingResult);
|
||||
Player.Commands commandsWithHasNext =
|
||||
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
|
||||
int[] mediaQueueItemIds = new int[] {1, 2, 3};
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMediaItems_whenLastPlaying_notifiesAvailableCommandsChanged() {
|
||||
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
|
||||
Player.Commands commandsWithHasNext =
|
||||
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
|
||||
int[] mediaQueueItemIds = new int[] {1};
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
|
||||
int[] mediaQueueItemIdsToAdd = new int[] {2};
|
||||
List<MediaItem> mediaItemsToAdd = createMediaItems(mediaQueueItemIdsToAdd);
|
||||
addMediaItemsAndUpdateTimeline(
|
||||
/* existingMediaItems= */ mediaItems,
|
||||
/* existingMediaQueueItemIds= */ mediaQueueItemIds,
|
||||
mediaItemsToAdd,
|
||||
mediaQueueItemIdsToAdd);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
mediaQueueItemIdsToAdd = new int[] {3};
|
||||
mediaItemsToAdd = createMediaItems(mediaQueueItemIdsToAdd);
|
||||
addMediaItemsAndUpdateTimeline(
|
||||
/* existingMediaItems= */ mediaItems,
|
||||
/* existingMediaQueueItemIds= */ mediaQueueItemIds,
|
||||
mediaItemsToAdd,
|
||||
mediaQueueItemIdsToAdd);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeMediaItems_followingCurrent_notifiesAvailableCommandsChanged() {
|
||||
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
|
||||
Player.Commands commandsWithHasNext =
|
||||
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
|
||||
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
|
||||
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
|
||||
MediaItem mediaItem3 = createMediaItem(/* mediaQueueItemId= */ 3);
|
||||
int[] mediaQueueItemIds = new int[] {1, 2, 3};
|
||||
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2, mediaItem3);
|
||||
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
removeMediaItemsAndUpdateTimeline(
|
||||
/* existingMediaItems= */ mediaItems,
|
||||
/* existingMediaQueueItemIds= */ mediaQueueItemIds,
|
||||
/* fromIndex= */ 2,
|
||||
/* toIndex= */ 3);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
removeMediaItemsAndUpdateTimeline(
|
||||
/* existingMediaItems= */ ImmutableList.of(mediaItem1, mediaItem2),
|
||||
/* existingMediaQueueItemIds= */ new int[] {1, 2},
|
||||
/* fromIndex= */ 1,
|
||||
/* toIndex= */ 2);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRepeatMode_all_notifiesAvailableCommandsChanged() {
|
||||
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
|
||||
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null)))
|
||||
.thenReturn(mockPendingResult);
|
||||
Player.Commands commandsWithHasNext =
|
||||
new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
|
||||
int[] mediaQueueItemIds = new int[] {1};
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() {
|
||||
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
|
||||
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null)))
|
||||
.thenReturn(mockPendingResult);
|
||||
int[] mediaQueueItemIds = new int[] {1};
|
||||
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||
|
||||
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||
castPlayer.setRepeatMode(Player.REPEAT_MODE_ONE);
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
private int[] createMediaQueueItemIds(int numberOfIds) {
|
||||
int[] mediaQueueItemIds = new int[numberOfIds];
|
||||
for (int i = 0; i < numberOfIds; i++) {
|
||||
@ -462,22 +588,77 @@ public class CastPlayerTest {
|
||||
}
|
||||
|
||||
private List<MediaItem> createMediaItems(int[] mediaQueueItemIds) {
|
||||
MediaItem.Builder builder = new MediaItem.Builder();
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
for (int mediaQueueItemId : mediaQueueItemIds) {
|
||||
MediaItem mediaItem =
|
||||
builder
|
||||
.setUri("http://www.google.com/video" + mediaQueueItemId)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(mediaQueueItemId)
|
||||
.build();
|
||||
mediaItems.add(mediaItem);
|
||||
mediaItems.add(createMediaItem(mediaQueueItemId));
|
||||
}
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
private void fillTimeline(List<MediaItem> mediaItems, int[] mediaQueueItemIds) {
|
||||
Assertions.checkState(mediaItems.size() == mediaQueueItemIds.length);
|
||||
private MediaItem createMediaItem(int mediaQueueItemId) {
|
||||
return new MediaItem.Builder()
|
||||
.setUri("http://www.google.com/video" + mediaQueueItemId)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(mediaQueueItemId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void addMediaItemsAndUpdateTimeline(List<MediaItem> mediaItems, int[] mediaQueueItemIds) {
|
||||
addMediaItemsAndUpdateTimeline(
|
||||
/* existingMediaItems= */ ImmutableList.of(),
|
||||
/* existingMediaQueueItemIds= */ new int[0],
|
||||
/* 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 updateTimeLine(List<MediaItem> mediaItems, int[] mediaQueueItemIds) {
|
||||
List<MediaQueueItem> queueItems = new ArrayList<>();
|
||||
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
|
||||
for (MediaItem mediaItem : mediaItems) {
|
||||
@ -491,7 +672,6 @@ public class CastPlayerTest {
|
||||
when(mockMediaInfo.getStreamType()).thenReturn(MediaInfo.STREAM_TYPE_NONE);
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(queueItems);
|
||||
|
||||
castPlayer.addMediaItems(mediaItems);
|
||||
// Call listener to update the timeline of the player.
|
||||
remoteMediaClientCallback.onQueueStatusUpdated();
|
||||
}
|
||||
|
@ -71,14 +71,6 @@ public abstract class BasePlayer implements Player {
|
||||
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommandAvailable(@Command int command) {
|
||||
if (command == COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) {
|
||||
return hasNext();
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void play() {
|
||||
setPlayWhenReady(true);
|
||||
@ -262,4 +254,8 @@ public abstract class BasePlayer implements Player {
|
||||
@RepeatMode int repeatMode = getRepeatMode();
|
||||
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
||||
}
|
||||
|
||||
protected Commands getAvailableCommands() {
|
||||
return new Commands.Builder().addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNext()).build();
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,11 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
@ -481,6 +484,17 @@ public interface Player {
|
||||
@Deprecated
|
||||
default void onLoadingChanged(boolean isLoading) {}
|
||||
|
||||
/**
|
||||
* Called when the value returned from {@link #isCommandAvailable(int)} changes for at least one
|
||||
* {@link Command}.
|
||||
*
|
||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
||||
* other events that happen in the same {@link Looper} message queue iteration.
|
||||
*
|
||||
* @param availableCommands The available {@link Commands}.
|
||||
*/
|
||||
default void onAvailableCommandsChanged(Commands availableCommands) {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link
|
||||
* #onPlayWhenReadyChanged(boolean, int)} instead.
|
||||
@ -692,6 +706,105 @@ public interface Player {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of {@link Command commands}.
|
||||
*
|
||||
* <p>Instances are immutable.
|
||||
*/
|
||||
final class Commands {
|
||||
|
||||
/** A builder for {@link Commands} instances. */
|
||||
public static final class Builder {
|
||||
|
||||
private final SparseBooleanArray commandsArray;
|
||||
|
||||
private boolean buildCalled;
|
||||
|
||||
/** Creates a builder. */
|
||||
public Builder() {
|
||||
commandsArray = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
/** Creates a builder with the values of the provided {@link Commands}. */
|
||||
private Builder(Commands commands) {
|
||||
this.commandsArray = commands.commandsArray.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link Command}.
|
||||
*
|
||||
* @param command A {@link Command}.
|
||||
* @return This builder.
|
||||
* @throws IllegalStateException If {@link #build()} has already been called.
|
||||
*/
|
||||
public Builder add(@Command int command) {
|
||||
checkState(!buildCalled);
|
||||
commandsArray.append(command, /* value= */ true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link Command} if the provided condition is true. Does nothing otherwise.
|
||||
*
|
||||
* @param command A {@link Command}.
|
||||
* @param condition A condition.
|
||||
* @return This builder.
|
||||
* @throws IllegalStateException If {@link #build()} has already been called.
|
||||
*/
|
||||
public Builder addIf(@Command int command, boolean condition) {
|
||||
checkState(!buildCalled);
|
||||
if (condition) {
|
||||
commandsArray.append(command, /* value= */ true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds a {@link Commands} instance. */
|
||||
public Commands build() {
|
||||
checkState(!buildCalled);
|
||||
buildCalled = true;
|
||||
return new Commands(commandsArray);
|
||||
}
|
||||
}
|
||||
|
||||
/** An empty set of commands. */
|
||||
public static final Commands EMPTY = new Commands.Builder().build();
|
||||
|
||||
// A SparseBooleanArray is used instead of a Set to avoid auto-boxing the Command values.
|
||||
private final SparseBooleanArray commandsArray;
|
||||
|
||||
private Commands(SparseBooleanArray commandsArray) {
|
||||
this.commandsArray = commandsArray;
|
||||
}
|
||||
|
||||
/** Returns a {@link Commands.Builder} initialized with the values of this instance. */
|
||||
public Builder buildUpon() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/** Returns whether the set of commands contains the specified {@link Command}. */
|
||||
public boolean contains(@Command int command) {
|
||||
return commandsArray.get(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Commands)) {
|
||||
return false;
|
||||
}
|
||||
Commands commands = (Commands) obj;
|
||||
return this.commandsArray.equals(commands.commandsArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return commandsArray.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
|
||||
* {@link #STATE_ENDED}.
|
||||
@ -881,7 +994,8 @@ public interface Player {
|
||||
EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
EVENT_PLAYER_ERROR,
|
||||
EVENT_POSITION_DISCONTINUITY,
|
||||
EVENT_PLAYBACK_PARAMETERS_CHANGED
|
||||
EVENT_PLAYBACK_PARAMETERS_CHANGED,
|
||||
EVENT_AVAILABLE_COMMANDS_CHANGED
|
||||
})
|
||||
@interface EventFlags {}
|
||||
/** {@link #getCurrentTimeline()} changed. */
|
||||
@ -912,6 +1026,8 @@ public interface Player {
|
||||
int EVENT_POSITION_DISCONTINUITY = 12;
|
||||
/** {@link #getPlaybackParameters()} changed. */
|
||||
int EVENT_PLAYBACK_PARAMETERS_CHANGED = 13;
|
||||
/** {@link #isCommandAvailable(int)} changed for at least one {@link Command}. */
|
||||
int EVENT_AVAILABLE_COMMANDS_CHANGED = 14;
|
||||
|
||||
/**
|
||||
* Commands that can be executed on a {@code Player}. One of {@link
|
||||
@ -1105,6 +1221,7 @@ public interface Player {
|
||||
*
|
||||
* @param command A {@link Command}.
|
||||
* @return Whether the {@link Command} is available.
|
||||
* @see EventListener#onAvailableCommandsChanged(Commands)
|
||||
*/
|
||||
boolean isCommandAvailable(@Command int command);
|
||||
|
||||
|
@ -91,6 +91,7 @@ import java.util.List;
|
||||
private SeekParameters seekParameters;
|
||||
private ShuffleOrder shuffleOrder;
|
||||
private boolean pauseAtEndOfMediaItems;
|
||||
private Commands availableCommands;
|
||||
|
||||
// Playback information when there is no pending seek/set source operation.
|
||||
private PlaybackInfo playbackInfo;
|
||||
@ -174,6 +175,7 @@ import java.util.List;
|
||||
new ExoTrackSelection[renderers.length],
|
||||
/* info= */ null);
|
||||
period = new Timeline.Period();
|
||||
availableCommands = Commands.EMPTY;
|
||||
maskingWindowIndex = C.INDEX_UNSET;
|
||||
playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
||||
playbackInfoUpdateListener =
|
||||
@ -283,6 +285,11 @@ import java.util.List;
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommandAvailable(@Command int command) {
|
||||
return availableCommands.contains(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
@State
|
||||
public int getPlaybackState() {
|
||||
@ -573,8 +580,10 @@ import java.util.List;
|
||||
if (this.repeatMode != repeatMode) {
|
||||
this.repeatMode = repeatMode;
|
||||
internalPlayer.setRepeatMode(repeatMode);
|
||||
listeners.sendEvent(
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode));
|
||||
updateAvailableCommands();
|
||||
listeners.flushEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,9 +597,11 @@ import java.util.List;
|
||||
if (this.shuffleModeEnabled != shuffleModeEnabled) {
|
||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||
internalPlayer.setShuffleModeEnabled(shuffleModeEnabled);
|
||||
listeners.sendEvent(
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
|
||||
listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled));
|
||||
updateAvailableCommands();
|
||||
listeners.flushEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1110,6 +1121,7 @@ import java.util.List;
|
||||
listener ->
|
||||
listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload));
|
||||
}
|
||||
updateAvailableCommands();
|
||||
listeners.flushEvents();
|
||||
}
|
||||
|
||||
@ -1159,6 +1171,16 @@ import java.util.List;
|
||||
return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
private void updateAvailableCommands() {
|
||||
Commands previousAvailableCommands = availableCommands;
|
||||
availableCommands = getAvailableCommands();
|
||||
if (!availableCommands.equals(previousAvailableCommands)) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
||||
listener -> listener.onAvailableCommandsChanged(availableCommands));
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaSourcesInternal(
|
||||
List<MediaSource> mediaSources,
|
||||
int startWindowIndex,
|
||||
|
@ -1257,6 +1257,12 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommandAvailable(@Command int command) {
|
||||
verifyApplicationThread();
|
||||
return player.isCommandAvailable(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
verifyApplicationThread();
|
||||
|
@ -8078,6 +8078,143 @@ public final class ExoPlayerTest {
|
||||
exoPlayerTestRunner.assertMediaItemsTransitionedSame(initialMediaItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_otherWindow_notifiesAvailableCommandsChanged() {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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(), new FakeMediaSource()));
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
|
||||
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void automaticWindowTransition_notifiesAvailableCommandsChanged() throws Exception {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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(), new FakeMediaSource()));
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.prepare();
|
||||
player.play();
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMediaItems_whenLastPlaying_notifiesAvailableCommandsChanged() throws Exception {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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.addMediaSource(new FakeMediaSource());
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
|
||||
player.addMediaSource(new FakeMediaSource());
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.addMediaSource(new FakeMediaSource());
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeMediaItems_followingCurrent_notifiesAvailableCommandsChanged()
|
||||
throws Exception {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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(), new FakeMediaSource()));
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.removeMediaItem(/* index= */ 2);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
player.removeMediaItem(/* index= */ 1);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRepeatMode_all_notifiesAvailableCommandsChanged() throws Exception {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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.addMediaSource(new FakeMediaSource());
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() throws Exception {
|
||||
Player.EventListener mockListener = mock(Player.EventListener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(mockListener);
|
||||
|
||||
player.addMediaSource(new FakeMediaSource());
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
||||
verify(mockListener, never()).onAvailableCommandsChanged(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setShuffleModeEnabled_notifiesAvailableCommandsChanged() throws Exception {
|
||||
Player.Commands commandsWithHasNext =
|
||||
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);
|
||||
MediaSource mediaSource =
|
||||
new ConcatenatingMediaSource(
|
||||
false,
|
||||
new FakeShuffleOrder(/* length= */ 2),
|
||||
new FakeMediaSource(),
|
||||
new FakeMediaSource());
|
||||
|
||||
player.addMediaSource(mediaSource);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithHasNext);
|
||||
|
||||
player.setShuffleModeEnabled(true);
|
||||
verify(mockListener).onAvailableCommandsChanged(Player.Commands.EMPTY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
mediaSourceMaybeThrowSourceInfoRefreshError_isNotThrownUntilPlaybackReachedFailingItem()
|
||||
|
@ -237,6 +237,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommandAvailable(int command) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
Loading…
x
Reference in New Issue
Block a user