From a552e35f6a3fdd5b948e33dbf03a7c89fd6b8613 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Mon, 5 Oct 2020 15:54:44 +0100 Subject: [PATCH] Add getter and callbacks for static metadata retrieval. Issue:#7266 PiperOrigin-RevId: 335416280 --- RELEASENOTES.md | 2 + .../exoplayer2/ext/cast/CastPlayer.java | 8 +++ .../android/exoplayer2/ExoPlayerImpl.java | 27 ++++++++-- .../exoplayer2/ExoPlayerImplInternal.java | 24 ++++++++- .../android/exoplayer2/PlaybackInfo.java | 24 ++++++++- .../com/google/android/exoplayer2/Player.java | 39 +++++++++++--- .../android/exoplayer2/SimpleExoPlayer.java | 6 +++ .../analytics/AnalyticsCollector.java | 8 +++ .../analytics/AnalyticsListener.java | 18 +++++++ .../android/exoplayer2/util/EventLogger.java | 11 ++++ .../android/exoplayer2/ExoPlayerTest.java | 51 +++++++++++++++++++ .../exoplayer2/MediaPeriodQueueTest.java | 1 + .../exoplayer2/ui/StyledPlayerView.java | 13 ++--- .../exoplayer2/testutil/StubExoPlayer.java | 6 +++ 14 files changed, 216 insertions(+), 22 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 37551c9575..d2ca5e7f15 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,6 +10,8 @@ still possible until the next major release using `setThrowsWhenUsingWrongThread(false)` ([#4463](https://github.com/google/ExoPlayer/issues/4463)). + * Add a getter and callback for static metadata to the player + ([#7266](https://github.com/google/ExoPlayer/issues/7266)). * Track selection: * Add option to specify multiple preferred audio or text languages. * Data sources: 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 80d9817a46..da42778c34 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 @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; @@ -51,6 +52,7 @@ import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -561,6 +563,12 @@ public final class CastPlayer extends BasePlayer { return currentTrackGroups; } + @Override + public List getCurrentStaticMetadata() { + // CastPlayer does not currently support metadata. + return Collections.emptyList(); + } + @Override public Timeline getCurrentTimeline() { return currentTimeline; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 1b0b34bd7b..304635bb2c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -28,6 +28,7 @@ import android.util.Pair; import androidx.annotation.Nullable; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceFactory; @@ -43,6 +44,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -861,6 +863,11 @@ import java.util.concurrent.TimeoutException; return playbackInfo.trackSelectorResult.selections; } + @Override + public List getCurrentStaticMetadata() { + return playbackInfo.staticMetadata; + } + @Override public Timeline getCurrentTimeline() { return playbackInfo.timeline; @@ -1168,7 +1175,8 @@ import java.util.concurrent.TimeoutException; /* requestedContentPositionUs= */ C.msToUs(maskingWindowPositionMs), /* totalBufferedDurationUs= */ 0, TrackGroupArray.EMPTY, - emptyTrackSelectorResult); + emptyTrackSelectorResult, + ImmutableList.of()); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(dummyMediaPeriodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; return playbackInfo; @@ -1195,7 +1203,8 @@ import java.util.concurrent.TimeoutException; /* requestedContentPositionUs= */ newContentPositionUs, /* totalBufferedDurationUs= */ 0, playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, - playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult); + playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, + playingPeriodChanged ? ImmutableList.of() : playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = newContentPositionUs; } else if (newContentPositionUs == oldContentPositionUs) { @@ -1219,7 +1228,8 @@ import java.util.concurrent.TimeoutException; /* requestedContentPositionUs= */ playbackInfo.positionUs, /* totalBufferedDurationUs= */ maskedBufferedPositionUs - playbackInfo.positionUs, playbackInfo.trackGroups, - playbackInfo.trackSelectorResult); + playbackInfo.trackSelectorResult, + playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } @@ -1241,7 +1251,8 @@ import java.util.concurrent.TimeoutException; /* requestedContentPositionUs= */ newContentPositionUs, maskedTotalBufferedDurationUs, playbackInfo.trackGroups, - playbackInfo.trackSelectorResult); + playbackInfo.trackSelectorResult, + playbackInfo.staticMetadata); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } return playbackInfo; @@ -1348,6 +1359,7 @@ import java.util.concurrent.TimeoutException; private final boolean isLoadingChanged; private final boolean timelineChanged; private final boolean trackSelectorResultChanged; + private final boolean staticMetadataChanged; private final boolean playWhenReadyChanged; private final boolean playbackSuppressionReasonChanged; private final boolean isPlayingChanged; @@ -1387,6 +1399,8 @@ import java.util.concurrent.TimeoutException; timelineChanged = !previousPlaybackInfo.timeline.equals(playbackInfo.timeline); trackSelectorResultChanged = previousPlaybackInfo.trackSelectorResult != playbackInfo.trackSelectorResult; + staticMetadataChanged = + !previousPlaybackInfo.staticMetadata.equals(playbackInfo.staticMetadata); playWhenReadyChanged = previousPlaybackInfo.playWhenReady != playbackInfo.playWhenReady; playbackSuppressionReasonChanged = previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason; @@ -1428,6 +1442,11 @@ import java.util.concurrent.TimeoutException; listener.onTracksChanged( playbackInfo.trackGroups, playbackInfo.trackSelectorResult.selections)); } + if (staticMetadataChanged) { + invokeAll( + listenerSnapshot, + listener -> listener.onStaticMetadataChanged(playbackInfo.staticMetadata)); + } if (isLoadingChanged) { invokeAll( listenerSnapshot, listener -> listener.onIsLoadingChanged(playbackInfo.isLoading)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 45af6d601f..94ffe2db6e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -33,12 +33,14 @@ import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason; import com.google.android.exoplayer2.Player.PlaybackSuppressionReason; import com.google.android.exoplayer2.Player.RepeatMode; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.BandwidthMeter; @@ -49,6 +51,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -1338,6 +1341,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /* isLoading= */ false, resetTrackInfo ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, resetTrackInfo ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, + resetTrackInfo ? ImmutableList.of() : playbackInfo.staticMetadata, mediaPeriodId, playbackInfo.playWhenReady, playbackInfo.playbackSuppressionReason, @@ -2096,6 +2100,7 @@ import java.util.concurrent.atomic.AtomicBoolean; resetPendingPauseAtEndOfPeriod(); TrackGroupArray trackGroupArray = playbackInfo.trackGroups; TrackSelectorResult trackSelectorResult = playbackInfo.trackSelectorResult; + List staticMetadata = playbackInfo.staticMetadata; if (mediaSourceList.isPrepared()) { @Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); trackGroupArray = @@ -2106,18 +2111,35 @@ import java.util.concurrent.atomic.AtomicBoolean; playingPeriodHolder == null ? emptyTrackSelectorResult : playingPeriodHolder.getTrackSelectorResult(); + staticMetadata = extractMetadataFromTrackSelectionArray(trackSelectorResult.selections); } else if (!mediaPeriodId.equals(playbackInfo.periodId)) { // Reset previously kept track info if unprepared and the period changes. trackGroupArray = TrackGroupArray.EMPTY; trackSelectorResult = emptyTrackSelectorResult; + staticMetadata = ImmutableList.of(); } + return playbackInfo.copyWithNewPosition( mediaPeriodId, positionUs, contentPositionUs, getTotalBufferedDurationUs(), trackGroupArray, - trackSelectorResult); + trackSelectorResult, + staticMetadata); + } + + private ImmutableList extractMetadataFromTrackSelectionArray( + TrackSelectionArray trackSelectionArray) { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (int i = 0; i < trackSelectionArray.length; i++) { + @Nullable TrackSelection trackSelection = trackSelectionArray.get(i); + if (trackSelection != null) { + Format format = trackSelection.getFormat(0); + builder.add(format.metadata == null ? new Metadata() : format.metadata); + } + } + return builder.build(); } private void enableRenderers() throws ExoPlaybackException { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index e7f200d8b7..96d14d0239 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -18,9 +18,12 @@ package com.google.android.exoplayer2; import androidx.annotation.CheckResult; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Player.PlaybackSuppressionReason; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; +import com.google.common.collect.ImmutableList; +import java.util.List; /** * Information about an ongoing playback. @@ -57,6 +60,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final TrackGroupArray trackGroups; /** The result of the current track selection. */ public final TrackSelectorResult trackSelectorResult; + /** The current static metadata of the track selections. */ + public final List staticMetadata; /** The {@link MediaPeriodId} of the currently loading media period in the {@link #timeline}. */ public final MediaPeriodId loadingMediaPeriodId; /** Whether playback should proceed when {@link #playbackState} == {@link Player#STATE_READY}. */ @@ -104,6 +109,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; /* isLoading= */ false, TrackGroupArray.EMPTY, emptyTrackSelectorResult, + /* staticMetadata= */ ImmutableList.of(), PLACEHOLDER_MEDIA_PERIOD_ID, /* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE, @@ -126,6 +132,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * @param isLoading See {@link #isLoading}. * @param trackGroups See {@link #trackGroups}. * @param trackSelectorResult See {@link #trackSelectorResult}. + * @param staticMetadata See {@link #staticMetadata}. * @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}. * @param playWhenReady See {@link #playWhenReady}. * @param playbackSuppressionReason See {@link #playbackSuppressionReason}. @@ -145,6 +152,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; boolean isLoading, TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult, + List staticMetadata, MediaPeriodId loadingMediaPeriodId, boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason, @@ -162,6 +170,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.isLoading = isLoading; this.trackGroups = trackGroups; this.trackSelectorResult = trackSelectorResult; + this.staticMetadata = staticMetadata; this.loadingMediaPeriodId = loadingMediaPeriodId; this.playWhenReady = playWhenReady; this.playbackSuppressionReason = playbackSuppressionReason; @@ -189,6 +198,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * @param trackGroups The track groups for the new position. See {@link #trackGroups}. * @param trackSelectorResult The track selector result for the new position. See {@link * #trackSelectorResult}. + * @param staticMetadata The static metadata for the track selections. See {@link + * #staticMetadata}. * @return Copied playback info with new playing position. */ @CheckResult @@ -198,7 +209,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; long requestedContentPositionUs, long totalBufferedDurationUs, TrackGroupArray trackGroups, - TrackSelectorResult trackSelectorResult) { + TrackSelectorResult trackSelectorResult, + List staticMetadata) { return new PlaybackInfo( timeline, periodId, @@ -208,6 +220,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -236,6 +249,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -264,6 +278,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -292,6 +307,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -320,6 +336,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -348,6 +365,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -380,6 +398,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -408,6 +427,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -437,6 +457,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, @@ -465,6 +486,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; isLoading, trackGroups, trackSelectorResult, + staticMetadata, loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 89a00eb475..16656029db 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -28,12 +28,14 @@ import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.device.DeviceInfo; import com.google.android.exoplayer2.device.DeviceListener; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.util.StableApiCandidate; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; @@ -491,6 +493,22 @@ public interface Player { default void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + /** + * Called when the static metadata changes. + * + *

The provided {@code metadataList} is an immutable list of {@link Metadata} instances, + * where the elements correspond to the {@link #getCurrentTrackSelections() current track + * selections}, or an empty list if there are no track selections or the implementation does not + * support metadata. + * + *

The metadata is considered static in the sense that it comes from the tracks' declared + * Formats, rather than being timed (or dynamic) metadata, which is represented within a + * metadata track. + * + * @param metadataList The static metadata. + */ + default void onStaticMetadataChanged(List metadataList) {} + /** * Called when the player starts or stops loading the source. * @@ -1226,16 +1244,25 @@ public interface Player { @Nullable TrackSelector getTrackSelector(); - /** - * Returns the available track groups. - */ + /** Returns the available track groups. */ TrackGroupArray getCurrentTrackGroups(); - /** - * Returns the current track selections for each renderer. - */ + /** Returns the current track selections for each renderer. */ TrackSelectionArray getCurrentTrackSelections(); + /** + * Returns the current static metadata for the track selections. + * + *

The returned {@code metadataList} is an immutable list of {@link Metadata} instances, where + * the elements correspond to the {@link #getCurrentTrackSelections() current track selections}, + * or an empty list if there are no track selections or the implementation does not support + * metadata. + * + *

This metadata is considered static in that it comes from the tracks' declared Formats, + * rather than being timed (or dynamic) metadata, which is represented within a metadata track. + */ + List getCurrentStaticMetadata(); + /** * Returns the current manifest. The type depends on the type of media being played. May be null. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index baa1400143..088bfad0d2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -1761,6 +1761,12 @@ public class SimpleExoPlayer extends BasePlayer return player.getCurrentTrackSelections(); } + @Override + public List getCurrentStaticMetadata() { + verifyApplicationThread(); + return player.getCurrentStaticMetadata(); + } + @Override public Timeline getCurrentTimeline() { verifyApplicationThread(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 30321c5972..a5535d7456 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -470,6 +470,14 @@ public class AnalyticsCollector } } + @Override + public final void onStaticMetadataChanged(List metadataList) { + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); + for (AnalyticsListener listener : listeners) { + listener.onStaticMetadataChanged(eventTime, metadataList); + } + } + @Override public final void onIsLoadingChanged(boolean isLoading) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 7e5abbd803..65e2abb582 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.common.base.Objects; import java.io.IOException; +import java.util.List; /** * A listener for analytics events. @@ -337,6 +338,23 @@ public interface AnalyticsListener { default void onTracksChanged( EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + /** + * Called when the static metadata changes. + * + *

The provided {@code metadataList} is an immutable list of {@link Metadata} instances, where + * the elements correspond to the current track selections (as returned by {@link + * #onTracksChanged(EventTime, TrackGroupArray, TrackSelectionArray)}, or an empty list if there + * are no track selections or the implementation does not support metadata. + * + *

The metadata is considered static in the sense that it comes from the tracks' declared + * Formats, rather than being timed (or dynamic) metadata, which is represented within a metadata + * track. + * + * @param eventTime The event time. + * @param metadataList The static metadata. + */ + default void onStaticMetadataChanged(EventTime eventTime, List metadataList) {} + /** * Called when a media source started loading data. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index a441e81bc4..45b7050c6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import java.io.IOException; import java.text.NumberFormat; +import java.util.List; import java.util.Locale; /** Logs events from {@link Player} and other core components using {@link Log}. */ @@ -295,6 +296,16 @@ public class EventLogger implements AnalyticsListener { logd("]"); } + @Override + public void onStaticMetadataChanged(EventTime eventTime, List metadataList) { + logd("staticMetadata [" + getEventTimeString(eventTime)); + for (int i = 0; i < metadataList.size(); i++) { + logd(" " + i); + printMetadata(metadataList.get(i), " "); + } + logd("]"); + } + @Override public void onMetadata(EventTime eventTime, Metadata metadata) { logd("metadata [" + getEventTimeString(eventTime)); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 4c64ffd781..1e21f25d3d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -56,6 +56,8 @@ import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; @@ -104,6 +106,7 @@ import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; @@ -8309,6 +8312,54 @@ public final class ExoPlayerTest { runUntilPlaybackState(player, Player.STATE_ENDED); } + @Test + public void staticMetadata_callbackIsCalledCorrectlyAndMatchesGetter() throws Exception { + Format videoFormat = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_H264) + .setWidth(1920) + .setHeight(720) + .setMetadata( + new Metadata( + new TextInformationFrame( + /* id= */ "TT2", + /* description= */ "Video", + /* value= */ "Video track name"))) + .build(); + + Format audioFormat = + new Format.Builder() + .setSampleMimeType(MimeTypes.AUDIO_AAC) + .setSampleRate(44_000) + .setMetadata( + new Metadata( + new TextInformationFrame( + /* id= */ "TT2", + /* description= */ "Audio", + /* value= */ "Audio track name"))) + .build(); + + EventListener eventListener = mock(EventListener.class); + + Timeline fakeTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 100000)); + SimpleExoPlayer player = new TestExoPlayer.Builder(context).build(); + + player.setMediaSource(new FakeMediaSource(fakeTimeline, videoFormat, audioFormat)); + player.addListener(eventListener); + player.prepare(); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + + assertThat(player.getCurrentStaticMetadata()) + .containsExactly(videoFormat.metadata, audioFormat.metadata) + .inOrder(); + verify(eventListener) + .onStaticMetadataChanged(ImmutableList.of(videoFormat.metadata, audioFormat.metadata)); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index ff4cdb7340..6b90dfab15 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -432,6 +432,7 @@ public final class MediaPeriodQueueTest { /* isLoading= */ false, /* trackGroups= */ null, /* trackSelectorResult= */ null, + /* staticMetadata= */ ImmutableList.of(), /* loadingMediaPeriodId= */ null, /* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE, diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 8b6c5983c6..230bb9fbf5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -58,7 +58,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextOutput; -import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.spherical.SingleTapListener; @@ -1369,15 +1368,9 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro closeShutter(); // Display artwork if enabled and available, else hide it. if (useArtwork()) { - for (int i = 0; i < selections.length; i++) { - @Nullable TrackSelection selection = selections.get(i); - if (selection != null) { - for (int j = 0; j < selection.length(); j++) { - @Nullable Metadata metadata = selection.getFormat(j).metadata; - if (metadata != null && setArtworkFromMetadata(metadata)) { - return; - } - } + for (Metadata metadata : player.getCurrentStaticMetadata()) { + if (setArtworkFromMetadata(metadata)) { + return; } } if (setDrawableArtwork(defaultArtwork)) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 7a96e1c797..858f7319b8 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -375,6 +376,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public List getCurrentStaticMetadata() { + throw new UnsupportedOperationException(); + } + @Override public Timeline getCurrentTimeline() { throw new UnsupportedOperationException();