diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java index a346827911..7b2cdd8794 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java @@ -25,6 +25,7 @@ import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.List; /** @@ -46,6 +47,8 @@ public final class MediaMetadata implements Bundleable { @Nullable private Uri mediaUri; @Nullable private Rating userRating; @Nullable private Rating overallRating; + @Nullable private byte[] artworkData; + @Nullable private Uri artworkUri; public Builder() {} @@ -60,6 +63,8 @@ public final class MediaMetadata implements Bundleable { this.mediaUri = mediaMetadata.mediaUri; this.userRating = mediaMetadata.userRating; this.overallRating = mediaMetadata.overallRating; + this.artworkData = mediaMetadata.artworkData; + this.artworkUri = mediaMetadata.artworkUri; } /** Sets the title. */ @@ -126,6 +131,18 @@ public final class MediaMetadata implements Bundleable { return this; } + /** Sets the artwork data as a compressed byte array. */ + public Builder setArtworkData(@Nullable byte[] artworkData) { + this.artworkData = artworkData == null ? null : artworkData.clone(); + return this; + } + + /** Sets the artwork {@link Uri}. */ + public Builder setArtworkUri(@Nullable Uri artworkUri) { + this.artworkUri = artworkUri; + return this; + } + /** * Sets all fields supported by the {@link Metadata.Entry entries} within the {@link Metadata}. * @@ -197,6 +214,10 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Rating userRating; /** Optional overall {@link Rating}. */ @Nullable public final Rating overallRating; + /** Optional artwork data as a compressed byte array. */ + @Nullable public final byte[] artworkData; + /** Optional artwork {@link Uri}. */ + @Nullable public final Uri artworkUri; private MediaMetadata(Builder builder) { this.title = builder.title; @@ -209,6 +230,8 @@ public final class MediaMetadata implements Bundleable { this.mediaUri = builder.mediaUri; this.userRating = builder.userRating; this.overallRating = builder.overallRating; + this.artworkData = builder.artworkData; + this.artworkUri = builder.artworkUri; } /** Returns a new {@link Builder} instance with the current {@link MediaMetadata} fields. */ @@ -234,7 +257,9 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(description, that.description) && Util.areEqual(mediaUri, that.mediaUri) && Util.areEqual(userRating, that.userRating) - && Util.areEqual(overallRating, that.overallRating); + && Util.areEqual(overallRating, that.overallRating) + && Arrays.equals(artworkData, that.artworkData) + && Util.areEqual(artworkUri, that.artworkUri); } @Override @@ -249,7 +274,9 @@ public final class MediaMetadata implements Bundleable { description, mediaUri, userRating, - overallRating); + overallRating, + Arrays.hashCode(artworkData), + artworkUri); } // Bundleable implementation. @@ -267,6 +294,8 @@ public final class MediaMetadata implements Bundleable { FIELD_MEDIA_URI, FIELD_USER_RATING, FIELD_OVERALL_RATING, + FIELD_ARTWORK_DATA, + FIELD_ARTWORK_URI }) private @interface FieldNumber {} @@ -280,6 +309,8 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_MEDIA_URI = 7; private static final int FIELD_USER_RATING = 8; private static final int FIELD_OVERALL_RATING = 9; + private static final int FIELD_ARTWORK_DATA = 10; + private static final int FIELD_ARTWORK_URI = 11; @Override public Bundle toBundle() { @@ -292,6 +323,8 @@ public final class MediaMetadata implements Bundleable { bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri); + bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); + bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); if (userRating != null) { bundle.putBundle(keyForField(FIELD_USER_RATING), userRating.toBundle()); @@ -316,7 +349,9 @@ public final class MediaMetadata implements Bundleable { .setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE))) .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) - .setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI))); + .setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI))) + .setArtworkData(bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA))) + .setArtworkUri(bundle.getParcelable(keyForField(FIELD_ARTWORK_URI))); if (bundle.containsKey(keyForField(FIELD_USER_RATING))) { @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_USER_RATING)); @@ -327,7 +362,7 @@ public final class MediaMetadata implements Bundleable { if (bundle.containsKey(keyForField(FIELD_OVERALL_RATING))) { @Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_OVERALL_RATING)); if (fieldBundle != null) { - builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle)); + builder.setOverallRating(Rating.CREATOR.fromBundle(fieldBundle)); } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java index 13b0e5345d..ee1fac9553 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java @@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; @@ -50,6 +51,11 @@ public final class ApicFrame extends Id3Frame { pictureData = castNonNull(in.createByteArray()); } + @Override + public void populateMediaMetadata(MediaMetadata.Builder builder) { + builder.setArtworkData(pictureData); + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java index b8a0509221..4c15e2a546 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java @@ -17,9 +17,13 @@ package com.google.android.exoplayer2; import static com.google.common.truth.Truth.assertThat; +import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; +import com.google.android.exoplayer2.util.MimeTypes; +import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +36,17 @@ public class MediaMetadataTest { MediaMetadata mediaMetadata = new MediaMetadata.Builder().build(); assertThat(mediaMetadata.title).isNull(); + assertThat(mediaMetadata.artist).isNull(); + assertThat(mediaMetadata.albumTitle).isNull(); + assertThat(mediaMetadata.albumArtist).isNull(); + assertThat(mediaMetadata.displayTitle).isNull(); + assertThat(mediaMetadata.subtitle).isNull(); + assertThat(mediaMetadata.description).isNull(); + assertThat(mediaMetadata.mediaUri).isNull(); + assertThat(mediaMetadata.userRating).isNull(); + assertThat(mediaMetadata.overallRating).isNull(); + assertThat(mediaMetadata.artworkData).isNull(); + assertThat(mediaMetadata.artworkUri).isNull(); } @Test @@ -44,14 +59,39 @@ public class MediaMetadataTest { } @Test - public void roundTripViaBundle_yieldsEqualInstance() { - MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); + public void builderSetArtworkData_setsArtworkData() { + byte[] bytes = new byte[] {35, 12, 6, 77}; + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setArtworkData(bytes).build(); - assertThat(MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle())).isEqualTo(mediaMetadata); + assertThat(Arrays.equals(mediaMetadata.artworkData, bytes)).isTrue(); } @Test - public void builderPopulatedFromMetadataEntry_setsTitleCorrectly() { + public void builderSetArworkUri_setsArtworkUri() { + Uri uri = Uri.parse("https://www.google.com"); + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setArtworkUri(uri).build(); + + assertThat(mediaMetadata.artworkUri).isEqualTo(uri); + } + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + 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}) + .build(); + + MediaMetadata fromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle()); + assertThat(fromBundle).isEqualTo(mediaMetadata); + } + + @Test + public void builderPopulatedFromTextInformationFrameEntry_setsTitle() { String title = "the title"; Metadata.Entry entry = new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ title); @@ -60,4 +100,21 @@ public class MediaMetadataTest { entry.populateMediaMetadata(builder); assertThat(builder.build().title.toString()).isEqualTo(title); } + + @Test + public void builderPopulatedFromApicFrameEntry_setsArtwork() { + byte[] pictureData = new byte[] {-12, 52, 33, 85, 34, 22, 1, -55}; + Metadata.Entry entry = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + /* pictureType= */ 0x03, + pictureData); + + MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon(); + entry.populateMediaMetadata(builder); + + MediaMetadata mediaMetadata = builder.build(); + assertThat(mediaMetadata.artworkData).isEqualTo(pictureData); + } }