[Cast] Notify media item transition only when playing period removed

When playback transitions automatically, the timeline may have changed because the cast device learned about the duration of the next media item and includes this in the new media status that is sent to the CastPlayer. In such a case we need to make sure that we don't report a media item transition with reason PLAYLIST_CHANGED but for reason AUTO.

PiperOrigin-RevId: 366025323
This commit is contained in:
bachinger 2021-03-31 14:57:22 +01:00 committed by Oliver Woodman
parent ffb5b41a20
commit 2fa3675edb
2 changed files with 62 additions and 13 deletions

View file

@ -650,6 +650,7 @@ 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;
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
@ -658,10 +659,12 @@ public final class CastPlayer extends BasePlayer {
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
}
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
updateTimelineAndNotifyIfChanged();
boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
int currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
if (!playingPeriodChangedByTimelineChange
&& previousWindowIndex != currentWindowIndex
&& pendingSeekCount == 0) {
this.currentWindowIndex = currentWindowIndex;
// TODO(b/181262841): call new onPositionDiscontinuity callback
listeners.queueEvent(
@ -714,10 +717,16 @@ public final class CastPlayer extends BasePlayer {
}
}
/**
* Updates the timeline and notifies {@link Player.EventListener event listeners} if required.
*
* @return Whether the timeline change has caused a change of the period currently being played.
*/
@SuppressWarnings("deprecation") // Calling deprecated listener method.
private void updateTimelineAndNotifyIfChanged() {
private boolean updateTimelineAndNotifyIfChanged() {
Timeline previousTimeline = currentTimeline;
int previousWindowIndex = currentWindowIndex;
boolean playingPeriodChanged = false;
if (updateTimeline()) {
// TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and
// TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553].
@ -732,17 +741,14 @@ public final class CastPlayer extends BasePlayer {
updateAvailableCommandsAndNotifyIfChanged();
boolean mediaItemTransitioned;
if (currentTimeline.isEmpty() && previousTimeline.isEmpty()) {
mediaItemTransitioned = false;
} else if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) {
mediaItemTransitioned = true;
} else {
if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) {
// Timeline initially populated or timeline cleared.
playingPeriodChanged = true;
} else if (!currentTimeline.isEmpty()) {
Object previousWindowUid = previousTimeline.getWindow(previousWindowIndex, window).uid;
Object currentWindowUid = currentTimeline.getWindow(currentWindowIndex, window).uid;
mediaItemTransitioned = !currentWindowUid.equals(previousWindowUid);
playingPeriodChanged = currentTimeline.getIndexOfPeriod(previousWindowUid) == C.INDEX_UNSET;
}
if (mediaItemTransitioned) {
if (playingPeriodChanged) {
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener ->
@ -750,6 +756,7 @@ public final class CastPlayer extends BasePlayer {
getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
}
}
return playingPeriodChanged;
}
/**

View file

@ -76,7 +76,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
/** Tests for {@link CastPlayer}. */
@RunWith(AndroidJUnit4.class)
@ -592,6 +594,46 @@ public class CastPlayerTest {
verify(mockListener).onMediaItemTransition(any(), anyInt());
}
@Test
@SuppressWarnings("deprecation") // Mocks deprecated method used by the CastPlayer.
public void autoTransition_notifiesMediaItemTransitionAndPositionDiscontinuity() {
int[] mediaQueueItemIds = new int[] {1, 2};
int[] streamTypes = {MediaInfo.STREAM_TYPE_BUFFERED, MediaInfo.STREAM_TYPE_BUFFERED};
long[] durationsFirstMs = {12500, C.TIME_UNSET};
// 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.
long[] durationsSecondMs = {12500, 22000};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
castPlayer.addMediaItems(mediaItems);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 1,
/* streamTypes= */ streamTypes,
/* durationsMs= */ durationsFirstMs);
updateTimeLine(
mediaItems,
mediaQueueItemIds,
/* currentItemId= */ 2,
/* streamTypes= */ streamTypes,
/* durationsMs= */ durationsSecondMs);
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());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(anyInt());
inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt());
}
@Test
public void isCommandAvailable_isTrueForAvailableCommands() {
int[] mediaQueueItemIds = new int[] {1, 2};
@ -1038,7 +1080,7 @@ public class CastPlayerTest {
.thenReturn(currentItemId == C.INDEX_UNSET ? 0 : currentItemId);
// Call listener to update the timeline of the player.
remoteMediaClientCallback.onQueueStatusUpdated();
remoteMediaClientCallback.onStatusUpdated();
}
private static Player.Commands createWithPermanentCommands(