Use the public MediaItem in the timeline of CastPlayer
The media item needs to be assigned to `Window.mediaItem` in `CastTimeline.setWindow`. For this the `MediaItem` needs to be available in the timeline. When a `MediaItem` is passed to the `set/addMediaItems` method, we can't yet know the Cast `MediaQueueItem.itemId` that is generated on the device and arrives with an async update of the `RemoteMediaClient` state. Hence in the `CastTimelineTracker`, we need to store the `MediaItem` by Casts's `MediaItem.contentId`. When we then receive the updated queue, we look the media item up by the content ID to augment the `ItemData` that is available in the `CastTimeline`. Issue: androidx/media#25 Issue: google/ExoPlayer#8212 #minor-release PiperOrigin-RevId: 460325235
This commit is contained in:
parent
7dc54efdb9
commit
30fbc3a27d
@ -35,6 +35,11 @@
|
||||
* Leanback extension:
|
||||
* Listen to `playWhenReady` changes in `LeanbackAdapter`
|
||||
([10420](https://github.com/google/ExoPlayer/issues/10420)).
|
||||
* Cast:
|
||||
* Use the `MediaItem` that has been passed to the playlist methods as
|
||||
`Window.mediaItem` in `CastTimeline`
|
||||
([#25](https://github.com/androidx/media/issues/25),
|
||||
[#8212](https://github.com/google/ExoPlayer/issues/8212)).
|
||||
|
||||
### 1.0.0-beta01 (2022-06-16)
|
||||
|
||||
|
@ -200,7 +200,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
this.mediaItemConverter = mediaItemConverter;
|
||||
this.seekBackIncrementMs = seekBackIncrementMs;
|
||||
this.seekForwardIncrementMs = seekForwardIncrementMs;
|
||||
timelineTracker = new CastTimelineTracker();
|
||||
timelineTracker = new CastTimelineTracker(mediaItemConverter);
|
||||
period = new Timeline.Period();
|
||||
statusListener = new StatusListener();
|
||||
seekResultCallback = new SeekResultCallback();
|
||||
@ -285,8 +285,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
|
||||
setMediaItemsInternal(
|
||||
toMediaQueueItems(mediaItems), startIndex, startPositionMs, repeatMode.value);
|
||||
setMediaItemsInternal(mediaItems, startIndex, startPositionMs, repeatMode.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -296,7 +295,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
if (index < currentTimeline.getWindowCount()) {
|
||||
uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid;
|
||||
}
|
||||
addMediaItemsInternal(toMediaQueueItems(mediaItems), uid);
|
||||
addMediaItemsInternal(mediaItems, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1022,14 +1021,13 @@ public final class CastPlayer extends BasePlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PendingResult<MediaChannelResult> setMediaItemsInternal(
|
||||
MediaQueueItem[] mediaQueueItems,
|
||||
private void setMediaItemsInternal(
|
||||
List<MediaItem> mediaItems,
|
||||
int startIndex,
|
||||
long startPositionMs,
|
||||
@RepeatMode int repeatMode) {
|
||||
if (remoteMediaClient == null || mediaQueueItems.length == 0) {
|
||||
return null;
|
||||
if (remoteMediaClient == null || mediaItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
startPositionMs = startPositionMs == C.TIME_UNSET ? 0 : startPositionMs;
|
||||
if (startIndex == C.INDEX_UNSET) {
|
||||
@ -1040,34 +1038,35 @@ public final class CastPlayer extends BasePlayer {
|
||||
if (!currentTimeline.isEmpty()) {
|
||||
pendingMediaItemRemovalPosition = getCurrentPositionInfo();
|
||||
}
|
||||
return remoteMediaClient.queueLoad(
|
||||
MediaQueueItem[] mediaQueueItems = toMediaQueueItems(mediaItems);
|
||||
timelineTracker.onMediaItemsSet(mediaItems, mediaQueueItems);
|
||||
remoteMediaClient.queueLoad(
|
||||
mediaQueueItems,
|
||||
min(startIndex, mediaQueueItems.length - 1),
|
||||
min(startIndex, mediaItems.size() - 1),
|
||||
getCastRepeatMode(repeatMode),
|
||||
startPositionMs,
|
||||
/* customData= */ null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PendingResult<MediaChannelResult> addMediaItemsInternal(MediaQueueItem[] items, int uid) {
|
||||
private void addMediaItemsInternal(List<MediaItem> mediaItems, int uid) {
|
||||
if (remoteMediaClient == null || getMediaStatus() == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
return remoteMediaClient.queueInsertItems(items, uid, /* customData= */ null);
|
||||
MediaQueueItem[] itemsToInsert = toMediaQueueItems(mediaItems);
|
||||
timelineTracker.onMediaItemsAdded(mediaItems, itemsToInsert);
|
||||
remoteMediaClient.queueInsertItems(itemsToInsert, uid, /* customData= */ null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PendingResult<MediaChannelResult> moveMediaItemsInternal(
|
||||
int[] uids, int fromIndex, int newIndex) {
|
||||
private void moveMediaItemsInternal(int[] uids, int fromIndex, int newIndex) {
|
||||
if (remoteMediaClient == null || getMediaStatus() == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
int insertBeforeIndex = fromIndex < newIndex ? newIndex + uids.length : newIndex;
|
||||
int insertBeforeItemId = MediaQueueItem.INVALID_ITEM_ID;
|
||||
if (insertBeforeIndex < currentTimeline.getWindowCount()) {
|
||||
insertBeforeItemId = (int) currentTimeline.getWindow(insertBeforeIndex, window).uid;
|
||||
}
|
||||
return remoteMediaClient.queueReorderItems(uids, insertBeforeItemId, /* customData= */ null);
|
||||
remoteMediaClient.queueReorderItems(uids, insertBeforeItemId, /* customData= */ null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -15,13 +15,13 @@
|
||||
*/
|
||||
package androidx.media3.cast;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Timeline;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A {@link Timeline} for Cast media queues. */
|
||||
@ -30,12 +30,16 @@ import java.util.Arrays;
|
||||
/** Holds {@link Timeline} related data for a Cast media item. */
|
||||
public static final class ItemData {
|
||||
|
||||
/* package */ static final String UNKNOWN_CONTENT_ID = "UNKNOWN_CONTENT_ID";
|
||||
|
||||
/** Holds no media information. */
|
||||
public static final ItemData EMPTY =
|
||||
new ItemData(
|
||||
/* durationUs= */ C.TIME_UNSET,
|
||||
/* defaultPositionUs= */ C.TIME_UNSET,
|
||||
/* isLive= */ false);
|
||||
/* isLive= */ false,
|
||||
MediaItem.EMPTY,
|
||||
UNKNOWN_CONTENT_ID);
|
||||
|
||||
/** The duration of the item in microseconds, or {@link C#TIME_UNSET} if unknown. */
|
||||
public final long durationUs;
|
||||
@ -45,6 +49,10 @@ import java.util.Arrays;
|
||||
public final long defaultPositionUs;
|
||||
/** Whether the item is live content, or {@code false} if unknown. */
|
||||
public final boolean isLive;
|
||||
/** The original media item that has been set or added to the playlist. */
|
||||
public final MediaItem mediaItem;
|
||||
/** The {@linkplain MediaInfo#getContentId() content ID} of the cast media queue item. */
|
||||
public final String contentId;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
@ -52,11 +60,20 @@ import java.util.Arrays;
|
||||
* @param durationUs See {@link #durationsUs}.
|
||||
* @param defaultPositionUs See {@link #defaultPositionUs}.
|
||||
* @param isLive See {@link #isLive}.
|
||||
* @param mediaItem See {@link #mediaItem}.
|
||||
* @param contentId See {@link #contentId}.
|
||||
*/
|
||||
public ItemData(long durationUs, long defaultPositionUs, boolean isLive) {
|
||||
public ItemData(
|
||||
long durationUs,
|
||||
long defaultPositionUs,
|
||||
boolean isLive,
|
||||
MediaItem mediaItem,
|
||||
String contentId) {
|
||||
this.durationUs = durationUs;
|
||||
this.defaultPositionUs = defaultPositionUs;
|
||||
this.isLive = isLive;
|
||||
this.mediaItem = mediaItem;
|
||||
this.contentId = contentId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,14 +83,23 @@ import java.util.Arrays;
|
||||
* @param defaultPositionUs The default start position in microseconds, or {@link C#TIME_UNSET}
|
||||
* if unknown.
|
||||
* @param isLive Whether the item is live, or {@code false} if unknown.
|
||||
* @param mediaItem The media item.
|
||||
* @param contentId The content ID.
|
||||
*/
|
||||
public ItemData copyWithNewValues(long durationUs, long defaultPositionUs, boolean isLive) {
|
||||
public ItemData copyWithNewValues(
|
||||
long durationUs,
|
||||
long defaultPositionUs,
|
||||
boolean isLive,
|
||||
MediaItem mediaItem,
|
||||
String contentId) {
|
||||
if (durationUs == this.durationUs
|
||||
&& defaultPositionUs == this.defaultPositionUs
|
||||
&& isLive == this.isLive) {
|
||||
&& isLive == this.isLive
|
||||
&& contentId.equals(this.contentId)
|
||||
&& mediaItem.equals(this.mediaItem)) {
|
||||
return this;
|
||||
}
|
||||
return new ItemData(durationUs, defaultPositionUs, isLive);
|
||||
return new ItemData(durationUs, defaultPositionUs, isLive, mediaItem, contentId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +108,7 @@ import java.util.Arrays;
|
||||
new CastTimeline(new int[0], new SparseArray<>());
|
||||
|
||||
private final SparseIntArray idsToIndex;
|
||||
private final MediaItem[] mediaItems;
|
||||
private final int[] ids;
|
||||
private final long[] durationsUs;
|
||||
private final long[] defaultPositionsUs;
|
||||
@ -100,10 +127,12 @@ import java.util.Arrays;
|
||||
durationsUs = new long[itemCount];
|
||||
defaultPositionsUs = new long[itemCount];
|
||||
isLive = new boolean[itemCount];
|
||||
mediaItems = new MediaItem[itemCount];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
int id = ids[i];
|
||||
idsToIndex.put(id, i);
|
||||
ItemData data = itemIdToData.get(id, ItemData.EMPTY);
|
||||
mediaItems[i] = data.mediaItem.buildUpon().setTag(id).build();
|
||||
durationsUs[i] = data.durationUs;
|
||||
defaultPositionsUs[i] = data.defaultPositionUs == C.TIME_UNSET ? 0 : data.defaultPositionUs;
|
||||
isLive[i] = data.isLive;
|
||||
@ -121,18 +150,16 @@ import java.util.Arrays;
|
||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
long durationUs = durationsUs[windowIndex];
|
||||
boolean isDynamic = durationUs == C.TIME_UNSET;
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(ids[windowIndex]).build();
|
||||
return window.set(
|
||||
/* uid= */ ids[windowIndex],
|
||||
/* mediaItem= */ mediaItem,
|
||||
/* mediaItem= */ mediaItems[windowIndex],
|
||||
/* manifest= */ null,
|
||||
/* presentationStartTimeMs= */ C.TIME_UNSET,
|
||||
/* windowStartTimeMs= */ C.TIME_UNSET,
|
||||
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
|
||||
/* isSeekable= */ !isDynamic,
|
||||
isDynamic,
|
||||
isLive[windowIndex] ? mediaItem.liveConfiguration : null,
|
||||
isLive[windowIndex] ? mediaItems[windowIndex].liveConfiguration : null,
|
||||
defaultPositionsUs[windowIndex],
|
||||
durationUs,
|
||||
/* firstPeriodIndex= */ windowIndex,
|
||||
|
@ -15,14 +15,23 @@
|
||||
*/
|
||||
package androidx.media3.cast;
|
||||
|
||||
import static androidx.media3.cast.CastTimeline.ItemData.UNKNOWN_CONTENT_ID;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import com.google.android.gms.cast.MediaQueueItem;
|
||||
import com.google.android.gms.cast.MediaStatus;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Creates {@link CastTimeline CastTimelines} from cast receiver app status updates.
|
||||
@ -33,9 +42,47 @@ import java.util.HashSet;
|
||||
/* package */ final class CastTimelineTracker {
|
||||
|
||||
private final SparseArray<CastTimeline.ItemData> itemIdToData;
|
||||
private final MediaItemConverter mediaItemConverter;
|
||||
@VisibleForTesting /* package */ final HashMap<String, MediaItem> mediaItemsByContentId;
|
||||
|
||||
public CastTimelineTracker() {
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param mediaItemConverter The converter used to convert from a {@link MediaQueueItem} to a
|
||||
* {@link MediaItem}.
|
||||
*/
|
||||
public CastTimelineTracker(MediaItemConverter mediaItemConverter) {
|
||||
this.mediaItemConverter = mediaItemConverter;
|
||||
itemIdToData = new SparseArray<>();
|
||||
mediaItemsByContentId = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when media items {@linkplain Player#setMediaItems have been set to the playlist} and are
|
||||
* sent to the cast playback queue. A future queue update of the {@link RemoteMediaClient} will
|
||||
* reflect this addition.
|
||||
*
|
||||
* @param mediaItems The media items that have been set.
|
||||
* @param mediaQueueItems The corresponding media queue items.
|
||||
*/
|
||||
public void onMediaItemsSet(List<MediaItem> mediaItems, MediaQueueItem[] mediaQueueItems) {
|
||||
mediaItemsByContentId.clear();
|
||||
onMediaItemsAdded(mediaItems, mediaQueueItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when media items {@linkplain Player#addMediaItems(List) have been added} and are sent to
|
||||
* the cast playback queue. A future queue update of the {@link RemoteMediaClient} will reflect
|
||||
* this addition.
|
||||
*
|
||||
* @param mediaItems The media items that have been added.
|
||||
* @param mediaQueueItems The corresponding media queue items.
|
||||
*/
|
||||
public void onMediaItemsAdded(List<MediaItem> mediaItems, MediaQueueItem[] mediaQueueItems) {
|
||||
for (int i = 0; i < mediaItems.size(); i++) {
|
||||
mediaItemsByContentId.put(
|
||||
checkNotNull(mediaQueueItems[i].getMedia()).getContentId(), mediaItems.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,18 +110,36 @@ import java.util.HashSet;
|
||||
}
|
||||
|
||||
int currentItemId = mediaStatus.getCurrentItemId();
|
||||
String currentContentId = checkStateNotNull(mediaStatus.getMediaInfo()).getContentId();
|
||||
MediaItem mediaItem = mediaItemsByContentId.get(currentContentId);
|
||||
updateItemData(
|
||||
currentItemId, mediaStatus.getMediaInfo(), /* defaultPositionUs= */ C.TIME_UNSET);
|
||||
currentItemId,
|
||||
mediaItem != null ? mediaItem : MediaItem.EMPTY,
|
||||
mediaStatus.getMediaInfo(),
|
||||
currentContentId,
|
||||
/* defaultPositionUs= */ C.TIME_UNSET);
|
||||
|
||||
for (MediaQueueItem item : mediaStatus.getQueueItems()) {
|
||||
long defaultPositionUs = (long) (item.getStartTime() * C.MICROS_PER_SECOND);
|
||||
updateItemData(item.getItemId(), item.getMedia(), defaultPositionUs);
|
||||
for (MediaQueueItem queueItem : mediaStatus.getQueueItems()) {
|
||||
long defaultPositionUs = (long) (queueItem.getStartTime() * C.MICROS_PER_SECOND);
|
||||
@Nullable MediaInfo mediaInfo = queueItem.getMedia();
|
||||
String contentId = mediaInfo != null ? mediaInfo.getContentId() : UNKNOWN_CONTENT_ID;
|
||||
mediaItem = mediaItemsByContentId.get(contentId);
|
||||
updateItemData(
|
||||
queueItem.getItemId(),
|
||||
mediaItem != null ? mediaItem : mediaItemConverter.toMediaItem(queueItem),
|
||||
mediaInfo,
|
||||
contentId,
|
||||
defaultPositionUs);
|
||||
}
|
||||
|
||||
return new CastTimeline(itemIds, itemIdToData);
|
||||
}
|
||||
|
||||
private void updateItemData(int itemId, @Nullable MediaInfo mediaInfo, long defaultPositionUs) {
|
||||
private void updateItemData(
|
||||
int itemId,
|
||||
MediaItem mediaItem,
|
||||
@Nullable MediaInfo mediaInfo,
|
||||
String contentId,
|
||||
long defaultPositionUs) {
|
||||
CastTimeline.ItemData previousData = itemIdToData.get(itemId, CastTimeline.ItemData.EMPTY);
|
||||
long durationUs = CastUtils.getStreamDurationUs(mediaInfo);
|
||||
if (durationUs == C.TIME_UNSET) {
|
||||
@ -87,7 +152,10 @@ import java.util.HashSet;
|
||||
if (defaultPositionUs == C.TIME_UNSET) {
|
||||
defaultPositionUs = previousData.defaultPositionUs;
|
||||
}
|
||||
itemIdToData.put(itemId, previousData.copyWithNewValues(durationUs, defaultPositionUs, isLive));
|
||||
itemIdToData.put(
|
||||
itemId,
|
||||
previousData.copyWithNewValues(
|
||||
durationUs, defaultPositionUs, isLive, mediaItem, contentId));
|
||||
}
|
||||
|
||||
private void removeUnusedItemDataEntries(int[] itemIds) {
|
||||
@ -99,6 +167,8 @@ import java.util.HashSet;
|
||||
int index = 0;
|
||||
while (index < itemIdToData.size()) {
|
||||
if (!scratchItemIds.contains(itemIdToData.keyAt(index))) {
|
||||
CastTimeline.ItemData itemData = itemIdToData.valueAt(index);
|
||||
mediaItemsByContentId.remove(itemData.contentId);
|
||||
itemIdToData.removeAt(index);
|
||||
} else {
|
||||
index++;
|
||||
|
@ -63,6 +63,7 @@ import static org.mockito.MockitoAnnotations.initMocks;
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.PlaybackParameters;
|
||||
import androidx.media3.common.Player;
|
||||
@ -126,6 +127,7 @@ public class CastPlayerTest {
|
||||
when(mockCastSession.getRemoteMediaClient()).thenReturn(mockRemoteMediaClient);
|
||||
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
|
||||
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(new MediaInfo.Builder("contentId").build());
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[0]);
|
||||
// Make the remote media client present the same default values as ExoPlayer:
|
||||
when(mockRemoteMediaClient.isPaused()).thenReturn(true);
|
||||
@ -388,7 +390,7 @@ public class CastPlayerTest {
|
||||
mediaItems.add(
|
||||
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
|
||||
|
||||
castPlayer.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
castPlayer.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
|
||||
verify(mockRemoteMediaClient)
|
||||
.queueLoad(queueItemsArgumentCaptor.capture(), eq(1), anyInt(), eq(2000L), any());
|
||||
@ -424,32 +426,42 @@ public class CastPlayerTest {
|
||||
String uri1 = "http://www.google.com/video1";
|
||||
String uri2 = "http://www.google.com/video2";
|
||||
firstPlaylist.add(
|
||||
new MediaItem.Builder().setUri(uri1).setMimeType(MimeTypes.APPLICATION_MPD).build());
|
||||
new MediaItem.Builder()
|
||||
.setUri(uri1)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(1)
|
||||
.build());
|
||||
firstPlaylist.add(
|
||||
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
|
||||
new MediaItem.Builder()
|
||||
.setUri(uri2)
|
||||
.setMimeType(MimeTypes.APPLICATION_MP4)
|
||||
.setTag(2)
|
||||
.build());
|
||||
ImmutableList<MediaItem> secondPlaylist =
|
||||
ImmutableList.of(
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setTag(3)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.build());
|
||||
|
||||
castPlayer.setMediaItems(
|
||||
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
updateTimeLine(
|
||||
firstPlaylist, /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 2);
|
||||
// Replacing existing playlist.
|
||||
castPlayer.setMediaItems(
|
||||
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
|
||||
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 1000L);
|
||||
updateTimeLine(secondPlaylist, /* mediaQueueItemIds= */ new int[] {3}, /* currentItemId= */ 3);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener, times(2))
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
mediaItemCaptor.capture(), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
eq(firstPlaylist.get(1)), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
eq(secondPlaylist.get(0)), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
assertThat(mediaItemCaptor.getAllValues().get(1).localConfiguration.tag).isEqualTo(3);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly.
|
||||
@ -459,18 +471,26 @@ public class CastPlayerTest {
|
||||
String uri1 = "http://www.google.com/video1";
|
||||
String uri2 = "http://www.google.com/video2";
|
||||
firstPlaylist.add(
|
||||
new MediaItem.Builder().setUri(uri1).setMimeType(MimeTypes.APPLICATION_MPD).build());
|
||||
new MediaItem.Builder()
|
||||
.setUri(uri1)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(1)
|
||||
.build());
|
||||
firstPlaylist.add(
|
||||
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
|
||||
new MediaItem.Builder()
|
||||
.setUri(uri2)
|
||||
.setMimeType(MimeTypes.APPLICATION_MP4)
|
||||
.setTag(2)
|
||||
.build());
|
||||
ImmutableList<MediaItem> secondPlaylist =
|
||||
ImmutableList.of(
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(3)
|
||||
.build());
|
||||
|
||||
castPlayer.setMediaItems(
|
||||
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 2000L);
|
||||
updateTimeLine(
|
||||
firstPlaylist,
|
||||
/* mediaQueueItemIds= */ new int[] {1, 2},
|
||||
@ -481,8 +501,7 @@ public class CastPlayerTest {
|
||||
/* durationsMs= */ new long[] {20_000, 20_000},
|
||||
/* positionMs= */ 2000L);
|
||||
// Replacing existing playlist.
|
||||
castPlayer.setMediaItems(
|
||||
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
|
||||
castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 1000L);
|
||||
updateTimeLine(
|
||||
secondPlaylist,
|
||||
/* mediaQueueItemIds= */ new int[] {3},
|
||||
@ -494,8 +513,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 2,
|
||||
/* windowIndex= */ 1,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
|
||||
/* mediaItemIndex= */ 1,
|
||||
firstPlaylist.get(1),
|
||||
/* periodUid= */ 2,
|
||||
/* periodIndex= */ 1,
|
||||
/* positionMs= */ 2000,
|
||||
@ -505,8 +524,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 3,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(3).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
secondPlaylist.get(0),
|
||||
/* periodUid= */ 3,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 1000,
|
||||
@ -720,10 +739,8 @@ public class CastPlayerTest {
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
eq(mediaItem), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
assertThat(mediaItemCaptor.getValue().localConfiguration.tag)
|
||||
.isEqualTo(mediaItem.localConfiguration.tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -742,7 +759,8 @@ public class CastPlayerTest {
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
.onMediaItemTransition(
|
||||
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
@ -776,8 +794,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 1234,
|
||||
@ -787,7 +805,7 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ null,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
/* mediaItem= */ null,
|
||||
/* periodUid= */ null,
|
||||
/* periodIndex= */ 0,
|
||||
@ -827,10 +845,8 @@ public class CastPlayerTest {
|
||||
.onMediaItemTransition(
|
||||
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
assertThat(mediaItemCaptor.getAllValues().get(0).localConfiguration.tag)
|
||||
.isEqualTo(mediaItem1.localConfiguration.tag);
|
||||
assertThat(mediaItemCaptor.getAllValues().get(1).localConfiguration.tag)
|
||||
.isEqualTo(mediaItem2.localConfiguration.tag);
|
||||
assertThat(mediaItemCaptor.getAllValues().get(0)).isEqualTo(mediaItem1);
|
||||
assertThat(mediaItemCaptor.getAllValues().get(1)).isEqualTo(mediaItem2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -862,8 +878,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem1,
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 1234,
|
||||
@ -873,8 +889,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 2,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem2,
|
||||
/* periodUid= */ 2,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0,
|
||||
@ -912,10 +928,8 @@ public class CastPlayerTest {
|
||||
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
List<MediaItem> capturedMediaItems = mediaItemCaptor.getAllValues();
|
||||
assertThat(capturedMediaItems.get(0).localConfiguration.tag)
|
||||
.isEqualTo(mediaItem1.localConfiguration.tag);
|
||||
assertThat(capturedMediaItems.get(1).localConfiguration.tag)
|
||||
.isEqualTo(mediaItem2.localConfiguration.tag);
|
||||
assertThat(capturedMediaItems.get(0)).isEqualTo(mediaItem1);
|
||||
assertThat(capturedMediaItems.get(1)).isEqualTo(mediaItem2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -945,8 +959,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem1,
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0, // position at which we receive the timeline change
|
||||
@ -956,8 +970,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 2,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem2,
|
||||
/* periodUid= */ 2,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0,
|
||||
@ -992,7 +1006,8 @@ public class CastPlayerTest {
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
.onMediaItemTransition(
|
||||
eq(mediaItem1), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
}
|
||||
|
||||
@ -1027,19 +1042,17 @@ public class CastPlayerTest {
|
||||
|
||||
castPlayer.addMediaItems(mediaItems);
|
||||
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1234);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
.onMediaItemTransition(
|
||||
eq(mediaItem1), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK));
|
||||
.onMediaItemTransition(eq(mediaItem2), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK));
|
||||
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
|
||||
assertThat(mediaItemCaptor.getValue().localConfiguration.tag)
|
||||
.isEqualTo(mediaItem2.localConfiguration.tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1054,13 +1067,13 @@ public class CastPlayerTest {
|
||||
|
||||
castPlayer.addMediaItems(mediaItems);
|
||||
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1234);
|
||||
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem1,
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0,
|
||||
@ -1070,8 +1083,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 2,
|
||||
/* windowIndex= */ 1,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
|
||||
/* mediaItemIndex= */ 1,
|
||||
mediaItem2,
|
||||
/* periodUid= */ 2,
|
||||
/* periodIndex= */ 1,
|
||||
/* positionMs= */ 1234,
|
||||
@ -1097,12 +1110,13 @@ public class CastPlayerTest {
|
||||
|
||||
castPlayer.addMediaItems(mediaItems);
|
||||
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
.onMediaItemTransition(
|
||||
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
}
|
||||
|
||||
@ -1115,14 +1129,13 @@ public class CastPlayerTest {
|
||||
|
||||
castPlayer.addMediaItems(mediaItems);
|
||||
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 1234);
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0,
|
||||
@ -1132,8 +1145,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 1234,
|
||||
@ -1164,13 +1177,12 @@ public class CastPlayerTest {
|
||||
InOrder inOrder = Mockito.inOrder(mockListener);
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
.onMediaItemTransition(
|
||||
eq(mediaItems.get(0)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
|
||||
inOrder
|
||||
.verify(mockListener)
|
||||
.onMediaItemTransition(
|
||||
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
|
||||
.onMediaItemTransition(eq(mediaItems.get(1)), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
|
||||
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
|
||||
assertThat(mediaItemCaptor.getValue().localConfiguration.tag).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1203,8 +1215,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build(),
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 12500,
|
||||
@ -1214,8 +1226,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 2,
|
||||
/* windowIndex= */ 1,
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setTag(2).build(),
|
||||
/* mediaItemIndex= */ 1,
|
||||
mediaItems.get(1),
|
||||
/* periodUid= */ 2,
|
||||
/* periodIndex= */ 1,
|
||||
/* positionMs= */ 0,
|
||||
@ -1250,12 +1262,11 @@ public class CastPlayerTest {
|
||||
mediaItems, mediaQueueItemIds, currentItemId, streamTypes, durationsMs, positionMs);
|
||||
castPlayer.seekBack();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 2 * C.DEFAULT_SEEK_BACK_INCREMENT_MS,
|
||||
@ -1265,8 +1276,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS,
|
||||
@ -1299,12 +1310,11 @@ public class CastPlayerTest {
|
||||
mediaItems, mediaQueueItemIds, currentItemId, streamTypes, durationsMs, positionMs);
|
||||
castPlayer.seekForward();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).setTag(1).build();
|
||||
Player.PositionInfo oldPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ 0,
|
||||
@ -1314,8 +1324,8 @@ public class CastPlayerTest {
|
||||
Player.PositionInfo newPosition =
|
||||
new Player.PositionInfo(
|
||||
/* windowUid= */ 1,
|
||||
/* windowIndex= */ 0,
|
||||
mediaItem,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItems.get(0),
|
||||
/* periodUid= */ 1,
|
||||
/* periodIndex= */ 0,
|
||||
/* positionMs= */ C.DEFAULT_SEEK_FORWARD_INCREMENT_MS,
|
||||
@ -1475,14 +1485,14 @@ public class CastPlayerTest {
|
||||
// Check that there were no other calls to onAvailableCommandsChanged.
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 3, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 3, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousWindow);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
@ -1509,14 +1519,14 @@ public class CastPlayerTest {
|
||||
// Check that there were no other calls to onAvailableCommandsChanged.
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 2, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToPreviousAndNextWindow);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 0);
|
||||
verify(mockListener, times(2)).onAvailableCommandsChanged(any());
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0);
|
||||
verify(mockListener).onAvailableCommandsChanged(commandsWithSeekToNextWindow);
|
||||
verify(mockListener, times(3)).onAvailableCommandsChanged(any());
|
||||
}
|
||||
@ -1533,8 +1543,8 @@ public class CastPlayerTest {
|
||||
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
|
||||
verify(mockListener).onAvailableCommandsChanged(defaultCommands);
|
||||
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 200);
|
||||
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 100);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 200);
|
||||
castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 100);
|
||||
// Check that there were no other calls to onAvailableCommandsChanged.
|
||||
verify(mockListener).onAvailableCommandsChanged(any());
|
||||
}
|
||||
@ -1782,6 +1792,7 @@ public class CastPlayerTest {
|
||||
private MediaItem createMediaItem(int mediaQueueItemId) {
|
||||
return new MediaItem.Builder()
|
||||
.setUri("http://www.google.com/video" + mediaQueueItemId)
|
||||
.setMediaMetadata(new MediaMetadata.Builder().setArtist("Foo Bar").build())
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setTag(mediaQueueItemId)
|
||||
.build();
|
||||
@ -1821,8 +1832,12 @@ public class CastPlayerTest {
|
||||
int mediaQueueItemId = mediaQueueItemIds[i];
|
||||
int streamType = streamTypes[i];
|
||||
long durationMs = durationsMs[i];
|
||||
String contentId =
|
||||
mediaItem.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID)
|
||||
? mediaItem.localConfiguration.uri.toString()
|
||||
: mediaItem.mediaId;
|
||||
MediaInfo.Builder mediaInfoBuilder =
|
||||
new MediaInfo.Builder(mediaItem.localConfiguration.uri.toString())
|
||||
new MediaInfo.Builder(contentId)
|
||||
.setStreamType(streamType)
|
||||
.setContentType(mediaItem.localConfiguration.mimeType);
|
||||
if (durationMs != C.TIME_UNSET) {
|
||||
|
@ -15,21 +15,30 @@
|
||||
*/
|
||||
package androidx.media3.cast;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline.Window;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.test.utils.TimelineAsserts;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import com.google.android.gms.cast.MediaQueueItem;
|
||||
import com.google.android.gms.cast.MediaStatus;
|
||||
import com.google.android.gms.cast.framework.media.MediaQueue;
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/** Tests for {@link CastTimelineTracker}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@ -40,10 +49,19 @@ public class CastTimelineTrackerTest {
|
||||
private static final long DURATION_4_MS = 4000;
|
||||
private static final long DURATION_5_MS = 5000;
|
||||
|
||||
private MediaItemConverter mediaItemConverter;
|
||||
private CastTimelineTracker castTimelineTracker;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
mediaItemConverter = new DefaultMediaItemConverter();
|
||||
castTimelineTracker = new CastTimelineTracker(mediaItemConverter);
|
||||
}
|
||||
|
||||
/** Tests that duration of the current media info is correctly propagated to the timeline. */
|
||||
@Test
|
||||
public void getCastTimelinePersistsDuration() {
|
||||
CastTimelineTracker tracker = new CastTimelineTracker();
|
||||
CastTimelineTracker tracker = new CastTimelineTracker(new DefaultMediaItemConverter());
|
||||
|
||||
RemoteMediaClient remoteMediaClient =
|
||||
mockRemoteMediaClient(
|
||||
@ -104,10 +122,179 @@ public class CastTimelineTrackerTest {
|
||||
Util.msToUs(DURATION_5_MS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCastTimeline_onMediaItemsSet_correctMediaItemsInTimeline() {
|
||||
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
|
||||
MediaQueue mockMediaQueue = mock(MediaQueue.class);
|
||||
MediaStatus mockMediaStatus = mock(MediaStatus.class);
|
||||
ImmutableList<MediaItem> playlistMediaItems =
|
||||
ImmutableList.of(createMediaItem(0), createMediaItem(1));
|
||||
MediaQueueItem[] playlistMediaQueueItems =
|
||||
new MediaQueueItem[] {
|
||||
createMediaQueueItem(playlistMediaItems.get(0), 0),
|
||||
createMediaQueueItem(playlistMediaItems.get(1), 1)
|
||||
};
|
||||
castTimelineTracker.onMediaItemsSet(playlistMediaItems, playlistMediaQueueItems);
|
||||
// Mock remote media client state after adding two items.
|
||||
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
|
||||
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(playlistMediaQueueItems[0].getMedia());
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(playlistMediaQueueItems));
|
||||
|
||||
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
|
||||
.isEqualTo(playlistMediaItems.get(0));
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
|
||||
.isEqualTo(playlistMediaItems.get(1));
|
||||
|
||||
MediaItem thirdMediaItem = createMediaItem(2);
|
||||
MediaQueueItem thirdMediaQueueItem = createMediaQueueItem(thirdMediaItem, 2);
|
||||
castTimelineTracker.onMediaItemsSet(
|
||||
ImmutableList.of(thirdMediaItem), new MediaQueueItem[] {thirdMediaQueueItem});
|
||||
// Mock remote media client state after a single item overrides the previous playlist.
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {2});
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(2);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(thirdMediaQueueItem.getMedia());
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(ImmutableList.of(thirdMediaQueueItem));
|
||||
|
||||
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(1);
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
|
||||
.isEqualTo(thirdMediaItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCastTimeline_onMediaItemsAdded_correctMediaItemsInTimeline() {
|
||||
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
|
||||
MediaQueue mockMediaQueue = mock(MediaQueue.class);
|
||||
MediaStatus mockMediaStatus = mock(MediaStatus.class);
|
||||
ImmutableList<MediaItem> playlistMediaItems =
|
||||
ImmutableList.of(createMediaItem(0), createMediaItem(1));
|
||||
MediaQueueItem[] playlistQueueItems =
|
||||
new MediaQueueItem[] {
|
||||
createMediaQueueItem(playlistMediaItems.get(0), /* uid= */ 0),
|
||||
createMediaQueueItem(playlistMediaItems.get(1), /* uid= */ 1)
|
||||
};
|
||||
ImmutableList<MediaItem> secondPlaylistMediaItems =
|
||||
new ImmutableList.Builder<MediaItem>()
|
||||
.addAll(playlistMediaItems)
|
||||
.add(createMediaItem(2))
|
||||
.build();
|
||||
castTimelineTracker.onMediaItemsAdded(playlistMediaItems, playlistQueueItems);
|
||||
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
|
||||
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
|
||||
// Mock remote media client state after two items have been added.
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(playlistQueueItems[0].getMedia());
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(playlistQueueItems));
|
||||
|
||||
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
|
||||
.isEqualTo(playlistMediaItems.get(0));
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
|
||||
.isEqualTo(playlistMediaItems.get(1));
|
||||
|
||||
// Mock remote media client state after adding a third item.
|
||||
List<MediaQueueItem> playlistThreeQueueItems =
|
||||
new ArrayList<>(Arrays.asList(playlistQueueItems));
|
||||
playlistThreeQueueItems.add(createMediaQueueItem(secondPlaylistMediaItems.get(2), 2));
|
||||
castTimelineTracker.onMediaItemsAdded(
|
||||
secondPlaylistMediaItems, playlistThreeQueueItems.toArray(new MediaQueueItem[0]));
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1, 2});
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(playlistThreeQueueItems);
|
||||
|
||||
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(3);
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
|
||||
.isEqualTo(secondPlaylistMediaItems.get(0));
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 1, new Window()).mediaItem)
|
||||
.isEqualTo(secondPlaylistMediaItems.get(1));
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 2, new Window()).mediaItem)
|
||||
.isEqualTo(secondPlaylistMediaItems.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCastTimeline_itemsRemoved_correctMediaItemsInTimelineAndMapCleanedUp() {
|
||||
RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class);
|
||||
MediaQueue mockMediaQueue = mock(MediaQueue.class);
|
||||
MediaStatus mockMediaStatus = mock(MediaStatus.class);
|
||||
ImmutableList<MediaItem> playlistMediaItems =
|
||||
ImmutableList.of(createMediaItem(0), createMediaItem(1));
|
||||
MediaQueueItem[] initialPlaylistTwoQueueItems =
|
||||
new MediaQueueItem[] {
|
||||
createMediaQueueItem(playlistMediaItems.get(0), 0),
|
||||
createMediaQueueItem(playlistMediaItems.get(1), 1)
|
||||
};
|
||||
castTimelineTracker.onMediaItemsSet(playlistMediaItems, initialPlaylistTwoQueueItems);
|
||||
when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mockMediaQueue);
|
||||
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mockMediaStatus);
|
||||
// Mock remote media client state with two items in the queue.
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {0, 1});
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(0);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(initialPlaylistTwoQueueItems[0].getMedia());
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(Arrays.asList(initialPlaylistTwoQueueItems));
|
||||
|
||||
CastTimeline castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(2);
|
||||
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(2);
|
||||
|
||||
// Mock remote media client state after the first item has been removed.
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[] {1});
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(1);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(initialPlaylistTwoQueueItems[1].getMedia());
|
||||
when(mockMediaStatus.getQueueItems())
|
||||
.thenReturn(ImmutableList.of(initialPlaylistTwoQueueItems[1]));
|
||||
|
||||
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(1);
|
||||
assertThat(castTimeline.getWindow(/* windowIndex= */ 0, new Window()).mediaItem)
|
||||
.isEqualTo(playlistMediaItems.get(1));
|
||||
// Assert that the removed item has been removed from the content ID map.
|
||||
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(1);
|
||||
|
||||
// Mock remote media client state for empty queue.
|
||||
when(mockRemoteMediaClient.getMediaStatus()).thenReturn(null);
|
||||
when(mockMediaQueue.getItemIds()).thenReturn(new int[0]);
|
||||
when(mockMediaStatus.getCurrentItemId()).thenReturn(MediaQueueItem.INVALID_ITEM_ID);
|
||||
when(mockMediaStatus.getMediaInfo()).thenReturn(null);
|
||||
when(mockMediaStatus.getQueueItems()).thenReturn(ImmutableList.of());
|
||||
|
||||
castTimeline = castTimelineTracker.getCastTimeline(mockRemoteMediaClient);
|
||||
|
||||
assertThat(castTimeline.getWindowCount()).isEqualTo(0);
|
||||
// Queue is not emptied when remote media client is empty. See [Internal ref: b/128825216].
|
||||
assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(1);
|
||||
}
|
||||
|
||||
private MediaItem createMediaItem(int uid) {
|
||||
return new MediaItem.Builder()
|
||||
.setUri("http://www.google.com/" + uid)
|
||||
.setMimeType(MimeTypes.AUDIO_MPEG)
|
||||
.setTag(uid)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MediaQueueItem createMediaQueueItem(MediaItem mediaItem, int uid) {
|
||||
return new MediaQueueItem.Builder(mediaItemConverter.toMediaQueueItem(mediaItem))
|
||||
.setItemId(uid)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static RemoteMediaClient mockRemoteMediaClient(
|
||||
int[] itemIds, int currentItemId, long currentDurationMs) {
|
||||
RemoteMediaClient remoteMediaClient = Mockito.mock(RemoteMediaClient.class);
|
||||
MediaStatus status = Mockito.mock(MediaStatus.class);
|
||||
RemoteMediaClient remoteMediaClient = mock(RemoteMediaClient.class);
|
||||
MediaStatus status = mock(MediaStatus.class);
|
||||
when(status.getQueueItems()).thenReturn(Collections.emptyList());
|
||||
when(remoteMediaClient.getMediaStatus()).thenReturn(status);
|
||||
when(status.getMediaInfo()).thenReturn(getMediaInfo(currentDurationMs));
|
||||
@ -118,7 +305,7 @@ public class CastTimelineTrackerTest {
|
||||
}
|
||||
|
||||
private static MediaQueue mockMediaQueue(int[] itemIds) {
|
||||
MediaQueue mediaQueue = Mockito.mock(MediaQueue.class);
|
||||
MediaQueue mediaQueue = mock(MediaQueue.class);
|
||||
when(mediaQueue.getItemIds()).thenReturn(itemIds);
|
||||
return mediaQueue;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user