Call new onPositionDiscontinuity callback in CastPlayer

PiperOrigin-RevId: 367020270
This commit is contained in:
bachinger 2021-04-06 17:19:41 +01:00 committed by marcbaechinger
parent 6f4db96da1
commit 4c33c5110e
2 changed files with 656 additions and 59 deletions

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.ext.cast;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min;
import android.os.Looper;
@ -39,6 +40,7 @@ import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
@ -129,6 +131,7 @@ public final class CastPlayer extends BasePlayer {
private int pendingSeekCount;
private int pendingSeekWindowIndex;
private long pendingSeekPositionMs;
@Nullable private PositionInfo pendingMediaItemRemovalPosition;
/**
* Creates a new cast player that uses a {@link DefaultMediaItemConverter}.
@ -460,23 +463,29 @@ public final class CastPlayer extends BasePlayer {
if (getCurrentWindowIndex() != windowIndex) {
remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(windowIndex, period).uid,
positionMs, null).setResultCallback(seekResultCallback);
} else {
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
}
PositionInfo oldPosition = getCurrentPositionInfo();
pendingSeekCount++;
pendingSeekWindowIndex = windowIndex;
pendingSeekPositionMs = positionMs;
PositionInfo newPosition = getCurrentPositionInfo();
listeners.queueEvent(
Player.EVENT_POSITION_DISCONTINUITY,
listener -> {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK);
listener.onPositionDiscontinuity(oldPosition, newPosition, DISCONTINUITY_REASON_SEEK);
});
if (oldPosition.windowIndex != newPosition.windowIndex) {
// TODO(internal b/182261884): queue `onMediaItemTransition` event when the media item is
// repeated.
MediaItem mediaItem = currentTimeline.getWindow(windowIndex, window).mediaItem;
MediaItem mediaItem = getCurrentTimeline().getWindow(windowIndex, window).mediaItem;
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener ->
listener.onMediaItemTransition(mediaItem, MEDIA_ITEM_TRANSITION_REASON_SEEK));
} else {
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
}
pendingSeekCount++;
pendingSeekWindowIndex = windowIndex;
pendingSeekPositionMs = positionMs;
// TODO(b/181262841): call new onPositionDiscontinuity callback
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);
@ -657,7 +666,12 @@ public final class CastPlayer extends BasePlayer {
// There is no session. We leave the state of the player as it is now.
return;
}
int previousWindowIndex = this.currentWindowIndex;
int oldWindowIndex = this.currentWindowIndex;
@Nullable
Object oldPeriodUid =
!getCurrentTimeline().isEmpty()
? getCurrentTimeline().getPeriod(oldWindowIndex, period, /* setIds= */ true).uid
: null;
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
@ -667,16 +681,49 @@ public final class CastPlayer extends BasePlayer {
}
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
int currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
Timeline currentTimeline = getCurrentTimeline();
currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
@Nullable
Object currentPeriodUid =
!currentTimeline.isEmpty()
? currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true).uid
: null;
if (!playingPeriodChangedByTimelineChange
&& previousWindowIndex != currentWindowIndex
&& !Util.areEqual(oldPeriodUid, currentPeriodUid)
&& pendingSeekCount == 0) {
this.currentWindowIndex = currentWindowIndex;
// TODO(b/181262841): call new onPositionDiscontinuity callback
// Report discontinuity and media item auto transition.
currentTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true);
currentTimeline.getWindow(oldWindowIndex, window);
long windowDurationMs = window.getDurationMs();
PositionInfo oldPosition =
new PositionInfo(
window.uid,
period.windowIndex,
period.uid,
period.windowIndex,
/* positionMs= */ windowDurationMs,
/* contentPositionMs= */ windowDurationMs,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true);
currentTimeline.getWindow(currentWindowIndex, window);
PositionInfo newPosition =
new PositionInfo(
window.uid,
period.windowIndex,
period.uid,
period.windowIndex,
/* positionMs= */ window.getDefaultPositionMs(),
/* contentPositionMs= */ window.getDefaultPositionMs(),
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
listeners.queueEvent(
Player.EVENT_POSITION_DISCONTINUITY,
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AUTO_TRANSITION));
listener -> {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AUTO_TRANSITION);
listener.onPositionDiscontinuity(
oldPosition, newPosition, DISCONTINUITY_REASON_AUTO_TRANSITION);
});
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener ->
@ -731,13 +778,14 @@ public final class CastPlayer extends BasePlayer {
*/
@SuppressWarnings("deprecation") // Calling deprecated listener method.
private boolean updateTimelineAndNotifyIfChanged() {
Timeline previousTimeline = currentTimeline;
int previousWindowIndex = currentWindowIndex;
Timeline oldTimeline = currentTimeline;
int oldWindowIndex = currentWindowIndex;
boolean playingPeriodChanged = false;
if (updateTimeline()) {
// TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and
// TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553].
Timeline timeline = currentTimeline;
// Call onTimelineChanged.
listeners.queueEvent(
Player.EVENT_TIMELINE_CHANGED,
listener -> {
@ -746,15 +794,48 @@ public final class CastPlayer extends BasePlayer {
listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
});
updateAvailableCommandsAndNotifyIfChanged();
if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) {
// Timeline initially populated or timeline cleared.
playingPeriodChanged = true;
} else if (!currentTimeline.isEmpty()) {
Object previousWindowUid = previousTimeline.getWindow(previousWindowIndex, window).uid;
playingPeriodChanged = currentTimeline.getIndexOfPeriod(previousWindowUid) == C.INDEX_UNSET;
// Call onPositionDiscontinuity if required.
Timeline currentTimeline = getCurrentTimeline();
boolean playingPeriodRemoved = false;
if (!oldTimeline.isEmpty()) {
Object oldPeriodUid =
castNonNull(oldTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true).uid);
playingPeriodRemoved = currentTimeline.getIndexOfPeriod(oldPeriodUid) == C.INDEX_UNSET;
}
if (playingPeriodRemoved) {
PositionInfo oldPosition;
if (pendingMediaItemRemovalPosition != null) {
oldPosition = pendingMediaItemRemovalPosition;
pendingMediaItemRemovalPosition = null;
} else {
// If the media item has been removed by another client, we don't know the removal
// position. We use the current position as a fallback.
oldTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true);
oldTimeline.getWindow(period.windowIndex, window);
oldPosition =
new PositionInfo(
window.uid,
period.windowIndex,
period.uid,
period.windowIndex,
getCurrentPosition(),
getContentPosition(),
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
}
PositionInfo newPosition = getCurrentPositionInfo();
listeners.queueEvent(
Player.EVENT_POSITION_DISCONTINUITY,
listener -> {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_REMOVE);
listener.onPositionDiscontinuity(
oldPosition, newPosition, DISCONTINUITY_REASON_REMOVE);
});
}
// Call onMediaItemTransition if required.
playingPeriodChanged =
currentTimeline.isEmpty() != oldTimeline.isEmpty() || playingPeriodRemoved;
if (playingPeriodChanged) {
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
@ -762,6 +843,7 @@ public final class CastPlayer extends BasePlayer {
listener.onMediaItemTransition(
getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
}
updateAvailableCommandsAndNotifyIfChanged();
}
return playingPeriodChanged;
}
@ -856,6 +938,10 @@ public final class CastPlayer extends BasePlayer {
startWindowIndex = getCurrentWindowIndex();
startPositionMs = getCurrentPosition();
}
Timeline currentTimeline = getCurrentTimeline();
if (!currentTimeline.isEmpty()) {
pendingMediaItemRemovalPosition = getCurrentPositionInfo();
}
return remoteMediaClient.queueLoad(
mediaQueueItems,
min(startWindowIndex, mediaQueueItems.length - 1),
@ -891,9 +977,41 @@ public final class CastPlayer extends BasePlayer {
if (remoteMediaClient == null || getMediaStatus() == null) {
return null;
}
Timeline timeline = getCurrentTimeline();
if (!timeline.isEmpty()) {
Object periodUid =
castNonNull(timeline.getPeriod(getCurrentPeriodIndex(), period, /* setIds= */ true).uid);
for (int uid : uids) {
if (periodUid.equals(uid)) {
pendingMediaItemRemovalPosition = getCurrentPositionInfo();
break;
}
}
}
return remoteMediaClient.queueRemoveItems(uids, /* customData= */ null);
}
private PositionInfo getCurrentPositionInfo() {
Timeline currentTimeline = getCurrentTimeline();
@Nullable
Object newPeriodUid =
!currentTimeline.isEmpty()
? currentTimeline.getPeriod(getCurrentPeriodIndex(), period, /* setIds= */ true).uid
: null;
@Nullable
Object newWindowUid =
newPeriodUid != null ? currentTimeline.getWindow(period.windowIndex, window).uid : null;
return new PositionInfo(
newWindowUid,
getCurrentWindowIndex(),
newPeriodUid,
getCurrentPeriodIndex(),
getCurrentPosition(),
getContentPosition(),
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
}
private void setRepeatModeAndNotifyIfChanged(@Player.RepeatMode int repeatMode) {
if (this.repeatMode.value != repeatMode) {
this.repeatMode.value = repeatMode;

View file

@ -36,6 +36,8 @@ import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH;
import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_REMOVE;
import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@ -49,6 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
@ -292,6 +295,109 @@ public class CastPlayerTest {
assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(uri2);
}
@SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly.
@Test
public void setMediaItems_replaceExistingPlaylist_notifiesMediaItemTransition() {
List<MediaItem> firstPlaylist = new ArrayList<>();
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());
firstPlaylist.add(
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
ImmutableList<MediaItem> secondPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setUri(Uri.EMPTY)
.setMimeType(MimeTypes.APPLICATION_MPD)
.build());
castPlayer.setMediaItems(
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
updateTimeLine(
firstPlaylist, /* mediaQueueItemIds= */ new int[] {1, 2}, /* currentItemId= */ 2);
// Replacing existing playlist.
castPlayer.setMediaItems(
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
updateTimeLine(secondPlaylist, /* mediaQueueItemIds= */ new int[] {3}, /* currentItemId= */ 3);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener, times(2))
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getAllValues().get(1).playbackProperties.tag).isEqualTo(3);
}
@SuppressWarnings("deprecation") // Verifies deprecated callback being called correctly.
@Test
public void setMediaItems_replaceExistingPlaylist_notifiesPositionDiscontinuity() {
List<MediaItem> firstPlaylist = new ArrayList<>();
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());
firstPlaylist.add(
new MediaItem.Builder().setUri(uri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
ImmutableList<MediaItem> secondPlaylist =
ImmutableList.of(
new MediaItem.Builder()
.setUri(Uri.EMPTY)
.setMimeType(MimeTypes.APPLICATION_MPD)
.build());
castPlayer.setMediaItems(
firstPlaylist, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
updateTimeLine(
firstPlaylist,
/* mediaQueueItemIds= */ new int[] {1, 2},
/* currentItemId= */ 2,
/* streamTypes= */ new int[] {
MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED
},
/* durationsMs= */ new long[] {20_000, 20_000},
/* positionMs= */ 2000L);
// Replacing existing playlist.
castPlayer.setMediaItems(
secondPlaylist, /* startWindowIndex= */ 0, /* startPositionMs= */ 1000L);
updateTimeLine(
secondPlaylist,
/* mediaQueueItemIds= */ new int[] {3},
/* currentItemId= */ 3,
/* streamTypes= */ new int[] {MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000},
/* positionMs= */ 1000L);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 2000,
/* contentPositionMs= */ 2000,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 3,
/* windowIndex= */ 0,
/* periodUid= */ 3,
/* periodIndex= */ 0,
/* positionMs= */ 1000,
/* contentPositionMs= */ 1000,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(DISCONTINUITY_REASON_REMOVE));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(eq(oldPosition), eq(newPosition), eq(DISCONTINUITY_REASON_REMOVE));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
public void addMediaItems_callsRemoteMediaClient() {
MediaItem.Builder builder = new MediaItem.Builder();
@ -486,12 +592,14 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener)
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getValue().playbackProperties.tag)
.isEqualTo(mediaItem.playbackProperties.tag);
verify(mockListener).onMediaItemTransition(any(), anyInt());
}
@Test
@ -501,17 +609,74 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.clearMediaItems();
updateTimeLine(
/* mediaItems= */ ImmutableList.of(),
/* mediaQueueItemIds= */ new int[0],
/* currentItemId= */ C.INDEX_UNSET);
verify(mockListener)
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
/* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED);
verify(mockListener, times(2)).onMediaItemTransition(any(), anyInt());
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void clearMediaItems_notifiesPositionDiscontinuity() {
int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 1,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000L, 30_000L},
/* positionMs= */ 1234);
castPlayer.clearMediaItems();
updateTimeLine(
/* mediaItems= */ ImmutableList.of(),
/* mediaQueueItemIds= */ new int[0],
/* currentItemId= */ C.INDEX_UNSET,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000L},
/* positionMs= */ 0);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
/* contentPositionMs= */ 1234,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ null,
/* windowIndex= */ 0,
/* periodUid= */ null,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
@ -523,19 +688,160 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.removeMediaItem(/* index= */ 0);
// Update with the new timeline after removal.
updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2);
verify(mockListener, times(2))
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener, times(2))
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
assertThat(mediaItemCaptor.getValue().playbackProperties.tag)
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getAllValues().get(0).playbackProperties.tag)
.isEqualTo(mediaItem1.playbackProperties.tag);
assertThat(mediaItemCaptor.getAllValues().get(1).playbackProperties.tag)
.isEqualTo(mediaItem2.playbackProperties.tag);
verify(mockListener, times(2)).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void removeCurrentMediaItem_notifiesPositionDiscontinuity() {
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2};
castPlayer.addMediaItems(mediaItems);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 1,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000L, 30_000L},
/* positionMs= */ 1234);
castPlayer.removeMediaItem(/* index= */ 0);
// Update with the new timeline after removal.
updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000L},
/* positionMs= */ 0);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
/* contentPositionMs= */ 1234,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 0,
/* periodUid= */ 2,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
public void removeCurrentMediaItem_byRemoteClient_notifiesMediaItemTransition() {
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, new int[] {1, 2}, /* currentItemId= */ 1);
// Update with the new timeline after removal on the device.
updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener, times(2))
.onMediaItemTransition(
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).playbackProperties.tag)
.isEqualTo(mediaItem1.playbackProperties.tag);
assertThat(capturedMediaItems.get(1).playbackProperties.tag)
.isEqualTo(mediaItem2.playbackProperties.tag);
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void removeCurrentMediaItem_byRemoteClient_notifiesPositionDiscontinuity() {
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(
mediaItems,
new int[] {1, 2},
/* currentItemId= */ 1,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {20_000L, 30_000L},
/* positionMs= */ 1234);
// Update with the new timeline after removal on the device.
updateTimeLine(
ImmutableList.of(mediaItem2),
/* mediaQueueItemIds= */ new int[] {2},
/* currentItemId= */ 2,
new int[] {MediaInfo.STREAM_TYPE_BUFFERED},
/* durationsMs= */ new long[] {30_000L},
/* positionMs= */ 0);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0, // position at which we receive the timeline change
/* contentPositionMs= */ 0, // position at which we receive the timeline change
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 0,
/* periodUid= */ 2,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_REMOVE));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
@ -547,14 +853,37 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.removeMediaItem(/* index= */ 1);
updateTimeLine(
ImmutableList.of(mediaItem1),
/* mediaQueueItemIds= */ new int[] {1},
/* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void removeNonCurrentMediaItem_doesNotNotifyPositionDiscontinuity() {
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2};
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.removeMediaItem(/* index= */ 1);
updateTimeLine(
ImmutableList.of(mediaItem1),
/* mediaQueueItemIds= */ new int[] {1},
/* currentItemId= */ 1);
verify(mockListener, never()).onPositionDiscontinuity(anyInt());
verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
@ -568,15 +897,63 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0);
verify(mockListener)
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
assertThat(mediaItemCaptor.getValue().playbackProperties.tag)
.isEqualTo(mediaItem2.playbackProperties.tag);
verify(mockListener, times(2)).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void seekTo_otherWindow_notifiesPositionDiscontinuity() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1);
MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2);
List<MediaItem> mediaItems = ImmutableList.of(mediaItem1, mediaItem2);
int[] mediaQueueItemIds = new int[] {1, 2};
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1234);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 1234,
/* contentPositionMs= */ 1234,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_SEEK));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_SEEK));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
@ -588,15 +965,82 @@ public class CastPlayerTest {
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
verify(mockListener).onMediaItemTransition(any(), anyInt());
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
verify(mockListener).onMediaItemTransition(any(), anyInt());
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void autoTransition_notifiesMediaItemTransitionAndPositionDiscontinuity() {
public void seekTo_sameWindow_notifiesPositionDiscontinuity() {
when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult);
int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1234);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 1234,
/* contentPositionMs= */ 1234,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder.verify(mockListener).onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_SEEK));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_SEEK));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
public void autoTransition_notifiesMediaItemTransition() {
int[] mediaQueueItemIds = new int[] {1, 2};
// When the remote Cast player transitions to an item that wasn't played before, the media state
// delivers the duration for that media item which updates the timeline accordingly.
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 2);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onMediaItemTransition(
mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
assertThat(mediaItemCaptor.getValue().playbackProperties.tag).isEqualTo(2);
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void autoTransition_notifiesPositionDiscontinuity() {
int[] mediaQueueItemIds = new int[] {1, 2};
int[] streamTypes = {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED};
long[] durationsFirstMs = {12500, C.TIME_UNSET};
@ -611,25 +1055,44 @@ public class CastPlayerTest {
mediaQueueItemIds,
/* currentItemId= */ 1,
/* streamTypes= */ streamTypes,
/* durationsMs= */ durationsFirstMs);
/* durationsMs= */ durationsFirstMs,
/* positionMs= */ C.TIME_UNSET);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 2,
/* streamTypes= */ streamTypes,
/* durationsMs= */ durationsSecondMs);
/* durationsMs= */ durationsSecondMs,
/* positionMs= */ C.TIME_UNSET);
Player.PositionInfo oldPosition =
new Player.PositionInfo(
/* windowUid= */ 1,
/* windowIndex= */ 0,
/* periodUid= */ 1,
/* periodIndex= */ 0,
/* positionMs= */ 12500,
/* contentPositionMs= */ 12500,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Player.PositionInfo newPosition =
new Player.PositionInfo(
/* windowUid= */ 2,
/* windowIndex= */ 1,
/* periodUid= */ 2,
/* periodIndex= */ 1,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
InOrder inOrder = Mockito.inOrder(mockListener);
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
inOrder
.verify(mockListener)
.onPositionDiscontinuity(eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION));
inOrder
.verify(mockListener)
.onMediaItemTransition(any(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO));
inOrder.verify(mockListener, never()).onMediaItemTransition(any(), anyInt());
.onPositionDiscontinuity(
eq(oldPosition), eq(newPosition), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION));
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@ -674,7 +1137,13 @@ public class CastPlayerTest {
long[] durationsMs = new long[] {C.TIME_UNSET};
castPlayer.addMediaItem(mediaItem);
updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1, streamTypes, durationsMs);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 1,
streamTypes,
durationsMs,
/* positionMs= */ C.TIME_UNSET);
assertThat(castPlayer.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)).isFalse();
}
@ -1041,7 +1510,13 @@ public class CastPlayerTest {
int[] streamTypes = new int[mediaItems.size()];
Arrays.fill(streamTypes, MediaInfo.STREAM_TYPE_BUFFERED);
long[] durationsMs = new long[mediaItems.size()];
updateTimeLine(mediaItems, mediaQueueItemIds, currentItemId, streamTypes, durationsMs);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
currentItemId,
streamTypes,
durationsMs,
/* positionMs= */ C.TIME_UNSET);
}
private void updateTimeLine(
@ -1049,7 +1524,8 @@ public class CastPlayerTest {
int[] mediaQueueItemIds,
int currentItemId,
int[] streamTypes,
long[] durationsMs) {
long[] durationsMs,
long positionMs) {
// Set up mocks to allow the player to update the timeline.
List<MediaQueueItem> queueItems = new ArrayList<>();
for (int i = 0; i < mediaQueueItemIds.length; i++) {
@ -1074,6 +1550,9 @@ public class CastPlayerTest {
when(mockMediaStatus.getMediaInfo()).thenReturn(mediaInfo);
}
}
if (positionMs != C.TIME_UNSET) {
when(mockRemoteMediaClient.getApproximateStreamPosition()).thenReturn(positionMs);
}
when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds);
when(mockMediaStatus.getQueueItems()).thenReturn(queueItems);
when(mockMediaStatus.getCurrentItemId())