Change MediaMetadata update priority to favour MediaItem values.

The static and dynamic metadata now build up in a list, such that when
the MediaMetadata is built, they are applied in an event order. This
means that newer/fresher values will overwrite older ones. The MediaItem
values are then applied at the end, as they take priority over any other.

#minor-release

PiperOrigin-RevId: 405383177
This commit is contained in:
samrobinson 2021-10-25 14:22:30 +01:00 committed by Oliver Woodman
parent 2b97455a8c
commit cd6c2e989f
6 changed files with 241 additions and 38 deletions

View File

@ -25,6 +25,9 @@
* Fix `mediaMetadata` being reset when media is repeated
([#9458](https://github.com/google/ExoPlayer/issues/9458)).
* Remove final dependency on `jcenter()`.
* Adjust `ExoPlayer` `MediaMetadata` update priority, such that values
input through the `MediaItem.MediaMetadata` are used above media
derived values.
* Video:
* Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a
released `Surface` when playing without an app-provided `Surface`

View File

@ -383,6 +383,108 @@ public final class MediaMetadata implements Bundleable {
return this;
}
/** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */
public Builder populate(@Nullable MediaMetadata mediaMetadata) {
if (mediaMetadata == null) {
return this;
}
if (mediaMetadata.title != null) {
setTitle(mediaMetadata.title);
}
if (mediaMetadata.artist != null) {
setArtist(mediaMetadata.artist);
}
if (mediaMetadata.albumTitle != null) {
setAlbumTitle(mediaMetadata.albumTitle);
}
if (mediaMetadata.albumArtist != null) {
setAlbumArtist(mediaMetadata.albumArtist);
}
if (mediaMetadata.displayTitle != null) {
setDisplayTitle(mediaMetadata.displayTitle);
}
if (mediaMetadata.subtitle != null) {
setSubtitle(mediaMetadata.subtitle);
}
if (mediaMetadata.description != null) {
setDescription(mediaMetadata.description);
}
if (mediaMetadata.mediaUri != null) {
setMediaUri(mediaMetadata.mediaUri);
}
if (mediaMetadata.userRating != null) {
setUserRating(mediaMetadata.userRating);
}
if (mediaMetadata.overallRating != null) {
setOverallRating(mediaMetadata.overallRating);
}
if (mediaMetadata.artworkData != null) {
setArtworkData(mediaMetadata.artworkData, mediaMetadata.artworkDataType);
}
if (mediaMetadata.artworkUri != null) {
setArtworkUri(mediaMetadata.artworkUri);
}
if (mediaMetadata.trackNumber != null) {
setTrackNumber(mediaMetadata.trackNumber);
}
if (mediaMetadata.totalTrackCount != null) {
setTotalTrackCount(mediaMetadata.totalTrackCount);
}
if (mediaMetadata.folderType != null) {
setFolderType(mediaMetadata.folderType);
}
if (mediaMetadata.isPlayable != null) {
setIsPlayable(mediaMetadata.isPlayable);
}
if (mediaMetadata.year != null) {
setRecordingYear(mediaMetadata.year);
}
if (mediaMetadata.recordingYear != null) {
setRecordingYear(mediaMetadata.recordingYear);
}
if (mediaMetadata.recordingMonth != null) {
setRecordingMonth(mediaMetadata.recordingMonth);
}
if (mediaMetadata.recordingDay != null) {
setRecordingDay(mediaMetadata.recordingDay);
}
if (mediaMetadata.releaseYear != null) {
setReleaseYear(mediaMetadata.releaseYear);
}
if (mediaMetadata.releaseMonth != null) {
setReleaseMonth(mediaMetadata.releaseMonth);
}
if (mediaMetadata.releaseDay != null) {
setReleaseDay(mediaMetadata.releaseDay);
}
if (mediaMetadata.writer != null) {
setWriter(mediaMetadata.writer);
}
if (mediaMetadata.composer != null) {
setComposer(mediaMetadata.composer);
}
if (mediaMetadata.conductor != null) {
setConductor(mediaMetadata.conductor);
}
if (mediaMetadata.discNumber != null) {
setDiscNumber(mediaMetadata.discNumber);
}
if (mediaMetadata.totalDiscCount != null) {
setTotalDiscCount(mediaMetadata.totalDiscCount);
}
if (mediaMetadata.genre != null) {
setGenre(mediaMetadata.genre);
}
if (mediaMetadata.compilation != null) {
setCompilation(mediaMetadata.compilation);
}
if (mediaMetadata.extras != null) {
setExtras(mediaMetadata.extras);
}
return this;
}
/** Returns a new {@link MediaMetadata} instance with the current builder values. */
public MediaMetadata build() {
return new MediaMetadata(/* builder= */ this);

View File

@ -2065,7 +2065,9 @@ public interface Player {
*
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections'
* formats} and {@link Listener#onMetadata(Metadata)}.
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
* dynamic metadata.
*/
MediaMetadata getMediaMetadata();

View File

@ -27,6 +27,9 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class MediaMetadataTest {
private static final String EXTRAS_KEY = "exampleKey";
private static final String EXTRAS_VALUE = "exampleValue";
@Test
public void builder_minimal_correctDefaults() {
MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
@ -91,41 +94,62 @@ public class MediaMetadataTest {
}
@Test
public void roundTripViaBundle_yieldsEqualInstance() {
Bundle extras = new Bundle();
extras.putString("exampleKey", "exampleValue");
public void populate_populatesEveryField() {
MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata();
MediaMetadata populated = new MediaMetadata.Builder().populate(mediaMetadata).build();
MediaMetadata mediaMetadata =
new MediaMetadata.Builder()
.setTitle("title")
.setAlbumArtist("the artist")
.setMediaUri(Uri.parse("https://www.google.com"))
.setUserRating(new HeartRating(false))
.setOverallRating(new PercentageRating(87.4f))
.setArtworkData(
new byte[] {-88, 12, 3, 2, 124, -54, -33, 69}, MediaMetadata.PICTURE_TYPE_MEDIA)
.setTrackNumber(4)
.setTotalTrackCount(12)
.setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS)
.setIsPlayable(true)
.setRecordingYear(2000)
.setRecordingMonth(11)
.setRecordingDay(23)
.setReleaseYear(2001)
.setReleaseMonth(1)
.setReleaseDay(2)
.setComposer("Composer")
.setConductor("Conductor")
.setWriter("Writer")
.setDiscNumber(1)
.setTotalDiscCount(3)
.setGenre("Pop")
.setCompilation("Amazing songs.")
.setExtras(extras) // Extras is not implemented in MediaMetadata.equals(Object o).
.build();
// If this assertion fails, it's likely that a field is not being updated in
// MediaMetadata.Builder#populate(MediaMetadata).
assertThat(populated).isEqualTo(mediaMetadata);
assertThat(populated.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
}
@Test
public void roundTripViaBundle_yieldsEqualInstance() {
MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata();
MediaMetadata fromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle());
assertThat(fromBundle).isEqualTo(mediaMetadata);
assertThat(fromBundle.extras.getString("exampleKey")).isEqualTo("exampleValue");
// Extras is not implemented in MediaMetadata.equals(Object o).
assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
}
private static MediaMetadata getFullyPopulatedMediaMetadata() {
Bundle extras = new Bundle();
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
return new MediaMetadata.Builder()
.setTitle("title")
.setArtist("artist")
.setAlbumTitle("album title")
.setAlbumArtist("album artist")
.setDisplayTitle("display title")
.setSubtitle("subtitle")
.setDescription("description")
.setMediaUri(Uri.parse("https://www.google.com"))
.setUserRating(new HeartRating(false))
.setOverallRating(new PercentageRating(87.4f))
.setArtworkData(
new byte[] {-88, 12, 3, 2, 124, -54, -33, 69}, MediaMetadata.PICTURE_TYPE_MEDIA)
.setArtworkUri(Uri.parse("https://www.google.com"))
.setTrackNumber(4)
.setTotalTrackCount(12)
.setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS)
.setIsPlayable(true)
.setRecordingYear(2000)
.setRecordingMonth(11)
.setRecordingDay(23)
.setReleaseYear(2001)
.setReleaseMonth(1)
.setReleaseDay(2)
.setComposer("Composer")
.setConductor("Conductor")
.setWriter("Writer")
.setDiscNumber(1)
.setTotalDiscCount(3)
.setGenre("Pop")
.setCompilation("Amazing songs.")
.setExtras(extras)
.build();
}
}

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
@ -111,6 +112,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
private MediaMetadata mediaMetadata;
private MediaMetadata playlistMetadata;
// MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata
// sources.
private MediaMetadata staticAndDynamicMediaMetadata;
// Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo;
@ -229,6 +234,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
.build();
mediaMetadata = MediaMetadata.EMPTY;
playlistMetadata = MediaMetadata.EMPTY;
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
maskingWindowIndex = C.INDEX_UNSET;
playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
playbackInfoUpdateListener =
@ -986,8 +992,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
public void onMetadata(Metadata metadata) {
MediaMetadata newMediaMetadata =
mediaMetadata.buildUpon().populateFromMetadata(metadata).build();
staticAndDynamicMediaMetadata =
staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build();
MediaMetadata newMediaMetadata = buildUpdatedMediaMetadata();
if (newMediaMetadata.equals(mediaMetadata)) {
return;
}
@ -1235,12 +1244,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
.windowIndex;
mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem;
}
newMediaMetadata = mediaItem != null ? mediaItem.mediaMetadata : MediaMetadata.EMPTY;
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
}
if (mediaItemTransitioned
|| !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
newMediaMetadata =
newMediaMetadata.buildUpon().populateFromMetadata(newPlaybackInfo.staticMetadata).build();
staticAndDynamicMediaMetadata =
staticAndDynamicMediaMetadata
.buildUpon()
.populateFromMetadata(newPlaybackInfo.staticMetadata)
.build();
newMediaMetadata = buildUpdatedMediaMetadata();
}
boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata);
mediaMetadata = newMediaMetadata;
@ -1794,6 +1808,24 @@ import java.util.concurrent.CopyOnWriteArraySet;
return positionUs;
}
/**
* Builds a {@link MediaMetadata} from the main sources.
*
* <p>{@link MediaItem} {@link MediaMetadata} is prioritized, with any gaps/missing fields
* populated by metadata from static ({@link TrackGroup} {@link Format}) and dynamic ({@link
* #onMetadata(Metadata)}) sources.
*/
private MediaMetadata buildUpdatedMediaMetadata() {
@Nullable MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem == null) {
return staticAndDynamicMediaMetadata;
}
// MediaItem metadata is prioritized over metadata within the media.
return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build();
}
private static boolean isPlaying(PlaybackInfo playbackInfo) {
return playbackInfo.playbackState == Player.STATE_READY
&& playbackInfo.playWhenReady

View File

@ -11181,6 +11181,46 @@ public final class ExoPlayerTest {
assertThat(videoRenderer.get().positionResetCount).isEqualTo(1);
}
@Test
public void setMediaItem_withMediaMetadata_updatesMediaMetadata() {
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("the title").build();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.setMediaItem(
new MediaItem.Builder()
.setMediaId("id")
.setUri(Uri.EMPTY)
.setMediaMetadata(mediaMetadata)
.build());
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
}
@Test
public void playingMedia_withNoMetadata_doesNotUpdateMediaMetadata() throws Exception {
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("the title").build();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
MediaItem mediaItem =
new MediaItem.Builder()
.setMediaId("id")
.setUri(Uri.parse("asset://android_asset/media/mp4/sample.mp4"))
.setMediaMetadata(mediaMetadata)
.build();
player.setMediaItem(mediaItem);
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
player.prepare();
TestPlayerRunHelper.playUntilPosition(
player, /* windowIndex= */ 0, /* positionMs= */ 5 * C.MILLIS_PER_SECOND);
player.stop();
shadowOf(Looper.getMainLooper()).idle();
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
}
// Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {