Commit 2fa3675e by bachinger Committed by Oliver Woodman

[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
parent ffb5b41a
...@@ -650,6 +650,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -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. // There is no session. We leave the state of the player as it is now.
return; return;
} }
int previousWindowIndex = this.currentWindowIndex;
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value; boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null); updatePlayerStateAndNotifyIfChanged(/* resultCallback= */ null);
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value; boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady.value;
...@@ -658,10 +659,12 @@ public final class CastPlayer extends BasePlayer { ...@@ -658,10 +659,12 @@ public final class CastPlayer extends BasePlayer {
Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying)); Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying));
} }
updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null); updateRepeatModeAndNotifyIfChanged(/* resultCallback= */ null);
updateTimelineAndNotifyIfChanged(); boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
int currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline); int currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) { if (!playingPeriodChangedByTimelineChange
&& previousWindowIndex != currentWindowIndex
&& pendingSeekCount == 0) {
this.currentWindowIndex = currentWindowIndex; this.currentWindowIndex = currentWindowIndex;
// TODO(b/181262841): call new onPositionDiscontinuity callback // TODO(b/181262841): call new onPositionDiscontinuity callback
listeners.queueEvent( listeners.queueEvent(
...@@ -714,10 +717,16 @@ public final class CastPlayer extends BasePlayer { ...@@ -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. @SuppressWarnings("deprecation") // Calling deprecated listener method.
private void updateTimelineAndNotifyIfChanged() { private boolean updateTimelineAndNotifyIfChanged() {
Timeline previousTimeline = currentTimeline; Timeline previousTimeline = currentTimeline;
int previousWindowIndex = currentWindowIndex; int previousWindowIndex = currentWindowIndex;
boolean playingPeriodChanged = false;
if (updateTimeline()) { if (updateTimeline()) {
// TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and // TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and
// TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553]. // TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553].
...@@ -732,17 +741,14 @@ public final class CastPlayer extends BasePlayer { ...@@ -732,17 +741,14 @@ public final class CastPlayer extends BasePlayer {
updateAvailableCommandsAndNotifyIfChanged(); updateAvailableCommandsAndNotifyIfChanged();
boolean mediaItemTransitioned; if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) {
if (currentTimeline.isEmpty() && previousTimeline.isEmpty()) { // Timeline initially populated or timeline cleared.
mediaItemTransitioned = false; playingPeriodChanged = true;
} else if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) { } else if (!currentTimeline.isEmpty()) {
mediaItemTransitioned = true;
} else {
Object previousWindowUid = previousTimeline.getWindow(previousWindowIndex, window).uid; Object previousWindowUid = previousTimeline.getWindow(previousWindowIndex, window).uid;
Object currentWindowUid = currentTimeline.getWindow(currentWindowIndex, window).uid; playingPeriodChanged = currentTimeline.getIndexOfPeriod(previousWindowUid) == C.INDEX_UNSET;
mediaItemTransitioned = !currentWindowUid.equals(previousWindowUid);
} }
if (mediaItemTransitioned) { if (playingPeriodChanged) {
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION, Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener ->
...@@ -750,6 +756,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -750,6 +756,7 @@ public final class CastPlayer extends BasePlayer {
getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED));
} }
} }
return playingPeriodChanged;
} }
/** /**
......
...@@ -76,7 +76,9 @@ import org.junit.Test; ...@@ -76,7 +76,9 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
/** Tests for {@link CastPlayer}. */ /** Tests for {@link CastPlayer}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
...@@ -593,6 +595,46 @@ public class CastPlayerTest { ...@@ -593,6 +595,46 @@ public class CastPlayerTest {
} }
@Test @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() { public void isCommandAvailable_isTrueForAvailableCommands() {
int[] mediaQueueItemIds = new int[] {1, 2}; int[] mediaQueueItemIds = new int[] {1, 2};
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds); List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
...@@ -1038,7 +1080,7 @@ public class CastPlayerTest { ...@@ -1038,7 +1080,7 @@ public class CastPlayerTest {
.thenReturn(currentItemId == C.INDEX_UNSET ? 0 : currentItemId); .thenReturn(currentItemId == C.INDEX_UNSET ? 0 : currentItemId);
// Call listener to update the timeline of the player. // Call listener to update the timeline of the player.
remoteMediaClientCallback.onQueueStatusUpdated(); remoteMediaClientCallback.onStatusUpdated();
} }
private static Player.Commands createWithPermanentCommands( private static Player.Commands createWithPermanentCommands(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment