diff --git a/RELEASENOTES.md b/RELEASENOTES.md index adfd097277..22bb504104 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -63,6 +63,8 @@ * Fix `onPositionDiscontinuity` event so that it is not triggered with reason `DISCONTINUITY_REASON_PERIOD_TRANSITION` after a seek to another media item and so that it is not triggered after a timeline change. + * Trigger `onMediaItemTransition` event for all reasons except + `MEDIA_ITEM_TRANSITION_REASON_REPEAT`. ### 2.13.2 (2021-02-25) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 6d2e4d368f..d4c3523e2a 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -440,6 +440,13 @@ public final class CastPlayer extends BasePlayer { if (getCurrentWindowIndex() != windowIndex) { remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(windowIndex, period).uid, positionMs, null).setResultCallback(seekResultCallback); + // TODO(internal b/182261884): queue `onMediaItemTransition` event when the media item is + // repeated. + MediaItem mediaItem = currentTimeline.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); } @@ -637,6 +644,11 @@ public final class CastPlayer extends BasePlayer { listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION)); + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> + listener.onMediaItemTransition( + getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_AUTO)); } if (updateTracksAndSelectionsAndNotifyIfChanged()) { listeners.queueEvent( @@ -681,6 +693,8 @@ public final class CastPlayer extends BasePlayer { @SuppressWarnings("deprecation") // Calling deprecated listener method. private void updateTimelineAndNotifyIfChanged() { + Timeline previousTimeline = currentTimeline; + int previousWindowIndex = currentWindowIndex; if (updateTimeline()) { // TODO: Differentiate TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED and // TIMELINE_CHANGE_REASON_SOURCE_UPDATE [see internal: b/65152553]. @@ -692,7 +706,26 @@ public final class CastPlayer extends BasePlayer { timeline, /* manifest= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); listener.onTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); }); + updateAvailableCommandsAndNotifyIfChanged(); + + boolean mediaItemTransitioned; + if (currentTimeline.isEmpty() && previousTimeline.isEmpty()) { + mediaItemTransitioned = false; + } else if (currentTimeline.isEmpty() != previousTimeline.isEmpty()) { + mediaItemTransitioned = true; + } else { + Object previousWindowUid = previousTimeline.getWindow(previousWindowIndex, window).uid; + Object currentWindowUid = currentTimeline.getWindow(currentWindowIndex, window).uid; + mediaItemTransitioned = !currentWindowUid.equals(previousWindowUid); + } + if (mediaItemTransitioned) { + listeners.queueEvent( + Player.EVENT_MEDIA_ITEM_TRANSITION, + listener -> + listener.onMediaItemTransition( + getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + } } } diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java index 4b331ce340..314cb1103f 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java @@ -80,8 +80,8 @@ public class CastPlayerTest { setResultCallbackArgumentCaptor; @Captor private ArgumentCaptor callbackArgumentCaptor; - @Captor private ArgumentCaptor queueItemsArgumentCaptor; + @Captor private ArgumentCaptor mediaItemCaptor; @SuppressWarnings("deprecation") @Before @@ -457,9 +457,107 @@ public class CastPlayerTest { } } + @Test + public void addMediaItems_notifiesMediaItemTransition() { + MediaItem mediaItem = createMediaItem(/* mediaQueueItemId= */ 1); + List mediaItems = ImmutableList.of(mediaItem); + int[] mediaQueueItemIds = new int[] {1}; + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + + verify(mockListener) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + assertThat(mediaItemCaptor.getValue().playbackProperties.tag) + .isEqualTo(mediaItem.playbackProperties.tag); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + } + + @Test + public void clearMediaItems_notifiesMediaItemTransition() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + + clearMediaItemsAndUpdateTimeline(); + verify(mockListener) + .onMediaItemTransition( + /* mediaItem= */ null, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); + verify(mockListener, times(2)).onMediaItemTransition(any(), anyInt()); + } + + @Test + public void removeCurrentMediaItem_notifiesMediaItemTransition() { + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + + removeMediaItemsAndUpdateTimeline( + mediaItems, mediaQueueItemIds, /* fromIndex= */ 0, /* toIndex= */ 1); + verify(mockListener, times(2)) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)); + assertThat(mediaItemCaptor.getValue().playbackProperties.tag) + .isEqualTo(mediaItem2.playbackProperties.tag); + verify(mockListener, times(2)).onMediaItemTransition(any(), anyInt()); + } + + @Test + public void removeNonCurrentMediaItem_doesNotNotifyMediaItemTransition() { + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + + removeMediaItemsAndUpdateTimeline( + mediaItems, mediaQueueItemIds, /* fromIndex= */ 1, /* toIndex= */ 2); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + } + + @Test + public void seekTo_otherWindow_notifiesMediaItemTransition() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); + MediaItem mediaItem2 = createMediaItem(/* mediaQueueItemId= */ 2); + List mediaItems = ImmutableList.of(mediaItem1, mediaItem2); + int[] mediaQueueItemIds = new int[] {1, 2}; + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + + castPlayer.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0); + verify(mockListener) + .onMediaItemTransition( + mediaItemCaptor.capture(), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK)); + 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_sameWindow_doesNotNotifyMediaItemTransition() { + when(mockRemoteMediaClient.seek(anyLong())).thenReturn(mockPendingResult); + int[] mediaQueueItemIds = new int[] {1, 2}; + List mediaItems = createMediaItems(mediaQueueItemIds); + + addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + + castPlayer.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0); + verify(mockListener).onMediaItemTransition(any(), anyInt()); + } + @Test public void seekTo_otherWindow_notifiesAvailableCommandsChanged() { - when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult); when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) .thenReturn(mockPendingResult); Player.Commands commandsWithHasNext = @@ -488,7 +586,6 @@ public class CastPlayerTest { @Test public void addMediaItems_whenLastPlaying_notifiesAvailableCommandsChanged() { - when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult); Player.Commands commandsWithHasNext = new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); int[] mediaQueueItemIds = new int[] {1}; @@ -519,7 +616,6 @@ public class CastPlayerTest { @Test public void removeMediaItems_followingCurrent_notifiesAvailableCommandsChanged() { - when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult); Player.Commands commandsWithHasNext = new Player.Commands.Builder().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build(); MediaItem mediaItem1 = createMediaItem(/* mediaQueueItemId= */ 1); @@ -550,7 +646,6 @@ public class CastPlayerTest { @Test public void setRepeatMode_all_notifiesAvailableCommandsChanged() { - when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult); when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) .thenReturn(mockPendingResult); Player.Commands commandsWithHasNext = @@ -568,7 +663,6 @@ public class CastPlayerTest { @Test public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { - when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult); when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), eq(null))) .thenReturn(mockPendingResult); int[] mediaQueueItemIds = new int[] {1}; @@ -658,6 +752,11 @@ public class CastPlayerTest { updateTimeLine(mediaItems, mediaQueueItemIds); } + private void clearMediaItemsAndUpdateTimeline() { + castPlayer.clearMediaItems(); + updateTimeLine(ImmutableList.of(), new int[0]); + } + private void updateTimeLine(List mediaItems, int[] mediaQueueItemIds) { List queueItems = new ArrayList<>(); DefaultMediaItemConverter converter = new DefaultMediaItemConverter();