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 279934d9df9..446c76c6e3c 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 @@ -143,6 +143,7 @@ public final class CastPlayer extends BasePlayer { private int pendingSeekWindowIndex; private long pendingSeekPositionMs; @Nullable private PositionInfo pendingMediaItemRemovalPosition; + private MediaMetadata mediaMetadata; /** * Creates a new cast player. @@ -210,6 +211,7 @@ public CastPlayer( playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT); playbackState = STATE_IDLE; currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE; + mediaMetadata = MediaMetadata.EMPTY; currentTracks = Tracks.EMPTY; availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build(); pendingSeekWindowIndex = C.INDEX_UNSET; @@ -423,6 +425,13 @@ public void seekTo(int mediaItemIndex, long positionMs) { Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, MEDIA_ITEM_TRANSITION_REASON_SEEK)); + MediaMetadata oldMediaMetadata = mediaMetadata; + mediaMetadata = getMediaMetadataInternal(); + if (!oldMediaMetadata.equals(mediaMetadata)) { + listeners.queueEvent( + Player.EVENT_MEDIA_METADATA_CHANGED, + listener -> listener.onMediaMetadataChanged(mediaMetadata)); + } } updateAvailableCommandsAndNotifyIfChanged(); } else if (pendingSeekCount == 0) { @@ -560,8 +569,12 @@ public void setTrackSelectionParameters(TrackSelectionParameters parameters) {} @Override public MediaMetadata getMediaMetadata() { - // CastPlayer does not currently support metadata. - return MediaMetadata.EMPTY; + return mediaMetadata; + } + + public MediaMetadata getMediaMetadataInternal() { + MediaItem currentMediaItem = getCurrentMediaItem(); + return currentMediaItem != null ? currentMediaItem.mediaMetadata : MediaMetadata.EMPTY; } @Override @@ -758,6 +771,7 @@ private void updateInternalStateAndNotifyIfChanged() { return; } int oldWindowIndex = this.currentWindowIndex; + MediaMetadata oldMediaMetadata = mediaMetadata; @Nullable Object oldPeriodUid = !getCurrentTimeline().isEmpty() @@ -769,6 +783,7 @@ private void updateInternalStateAndNotifyIfChanged() { boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged(); Timeline currentTimeline = getCurrentTimeline(); currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline); + mediaMetadata = getMediaMetadataInternal(); @Nullable Object currentPeriodUid = !currentTimeline.isEmpty() @@ -822,6 +837,11 @@ private void updateInternalStateAndNotifyIfChanged() { listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks)); } + if (!oldMediaMetadata.equals(mediaMetadata)) { + listeners.queueEvent( + Player.EVENT_MEDIA_METADATA_CHANGED, + listener -> listener.onMediaMetadataChanged(mediaMetadata)); + } updateAvailableCommandsAndNotifyIfChanged(); listeners.flushEvents(); } 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 02b178521de..275b5e57a7f 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 @@ -67,6 +67,7 @@ import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Player.Listener; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -107,7 +108,7 @@ public class CastPlayerTest { @Mock private CastContext mockCastContext; @Mock private SessionManager mockSessionManager; @Mock private CastSession mockCastSession; - @Mock private Player.Listener mockListener; + @Mock private Listener mockListener; @Mock private PendingResult mockPendingResult; @Captor @@ -1042,7 +1043,9 @@ public void seekTo_otherWindow_notifiesMediaItemTransition() { castPlayer.addMediaItems(mediaItems); updateTimeLine(mediaItems, mediaQueueItemIds, /* currentItemId= */ 1); + MediaMetadata firstMediaMetadata = castPlayer.getMediaMetadata(); castPlayer.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1234); + MediaMetadata secondMediaMetadata = castPlayer.getMediaMetadata(); InOrder inOrder = Mockito.inOrder(mockListener); inOrder @@ -1053,6 +1056,8 @@ public void seekTo_otherWindow_notifiesMediaItemTransition() { .verify(mockListener) .onMediaItemTransition(eq(mediaItem2), eq(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK)); inOrder.verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + assertThat(firstMediaMetadata).isEqualTo(mediaItem1.mediaMetadata); + assertThat(secondMediaMetadata).isEqualTo(mediaItem2.mediaMetadata); } @Test @@ -1773,6 +1778,108 @@ public void setRepeatMode_one_doesNotNotifyAvailableCommandsChanged() { verify(mockListener).onAvailableCommandsChanged(any()); } + @Test + public void setMediaItems_doesNotifyOnMetadataChanged() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(MediaMetadata.class); + String uri1 = "http://www.google.com/video1"; + String uri2 = "http://www.google.com/video2"; + ImmutableList firstPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setUri(uri1) + .setMimeType(MimeTypes.APPLICATION_MPD) + .setMediaMetadata(new MediaMetadata.Builder().setArtist("foo").build()) + .setTag(1) + .build()); + ImmutableList secondPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setTag(2) + .setMediaMetadata(new MediaMetadata.Builder().setArtist("bar").build()) + .setMimeType(MimeTypes.APPLICATION_MPD) + .build(), + new MediaItem.Builder() + .setUri(uri2) + .setMimeType(MimeTypes.APPLICATION_MP4) + .setMediaMetadata(new MediaMetadata.Builder().setArtist("foobar").build()) + .setTag(3) + .build()); + castPlayer.addListener(mockListener); + + MediaMetadata intitalMetadata = castPlayer.getMediaMetadata(); + castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 2000L); + updateTimeLine(firstPlaylist, /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1); + MediaMetadata firstMetadata = castPlayer.getMediaMetadata(); + // Replacing existing playlist. + castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 0L); + updateTimeLine( + secondPlaylist, /* mediaQueueItemIds= */ new int[] {2, 3}, /* currentItemId= */ 3); + MediaMetadata secondMetadata = castPlayer.getMediaMetadata(); + castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0); + MediaMetadata thirdMetadata = castPlayer.getMediaMetadata(); + + verify(mockListener, times(3)).onMediaItemTransition(mediaItemCaptor.capture(), anyInt()); + assertThat(mediaItemCaptor.getAllValues()) + .containsExactly(firstPlaylist.get(0), secondPlaylist.get(1), secondPlaylist.get(0)) + .inOrder(); + verify(mockListener, times(3)).onMediaMetadataChanged(metadataCaptor.capture()); + assertThat(metadataCaptor.getAllValues()) + .containsExactly( + firstPlaylist.get(0).mediaMetadata, + secondPlaylist.get(1).mediaMetadata, + secondPlaylist.get(0).mediaMetadata) + .inOrder(); + assertThat(intitalMetadata).isEqualTo(MediaMetadata.EMPTY); + assertThat(ImmutableList.of(firstMetadata, secondMetadata, thirdMetadata)) + .containsExactly( + firstPlaylist.get(0).mediaMetadata, + secondPlaylist.get(1).mediaMetadata, + secondPlaylist.get(0).mediaMetadata) + .inOrder(); + } + + @Test + public void setMediaItems_equalMetadata_doesNotNotifyOnMediaMetadataChanged() { + when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null))) + .thenReturn(mockPendingResult); + String uri1 = "http://www.google.com/video1"; + String uri2 = "http://www.google.com/video2"; + ImmutableList firstPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setUri(uri1) + .setMimeType(MimeTypes.APPLICATION_MPD) + .setTag(1) + .build()); + ImmutableList secondPlaylist = + ImmutableList.of( + new MediaItem.Builder() + .setMediaMetadata(MediaMetadata.EMPTY) + .setUri(Uri.EMPTY) + .setTag(2) + .setMimeType(MimeTypes.APPLICATION_MPD) + .build(), + new MediaItem.Builder() + .setUri(uri2) + .setMimeType(MimeTypes.APPLICATION_MP4) + .setTag(3) + .build()); + castPlayer.addListener(mockListener); + + castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 2000L); + updateTimeLine(firstPlaylist, /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1); + castPlayer.setMediaItems(secondPlaylist, /* startIndex= */ 1, /* startPositionMs= */ 0L); + updateTimeLine( + secondPlaylist, /* mediaQueueItemIds= */ new int[] {2, 3}, /* currentItemId= */ 3); + castPlayer.seekTo(/* mediaItemIndex= */ 0, /* positionMs= */ 0); + + verify(mockListener, times(3)).onMediaItemTransition(any(), anyInt()); + verify(mockListener, never()).onMediaMetadataChanged(any()); + } + private int[] createMediaQueueItemIds(int numberOfIds) { int[] mediaQueueItemIds = new int[numberOfIds]; for (int i = 0; i < numberOfIds; i++) { @@ -1792,7 +1899,8 @@ private List createMediaItems(int[] mediaQueueItemIds) { private MediaItem createMediaItem(int mediaQueueItemId) { return new MediaItem.Builder() .setUri("http://www.google.com/video" + mediaQueueItemId) - .setMediaMetadata(new MediaMetadata.Builder().setArtist("Foo Bar").build()) + .setMediaMetadata( + new MediaMetadata.Builder().setArtist("Foo Bar - " + mediaQueueItemId).build()) .setMimeType(MimeTypes.APPLICATION_MPD) .setTag(mediaQueueItemId) .build();