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 2b8dce0d16..e76edee173 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 @@ -49,6 +49,7 @@ public final class MediaMetadata implements Bundleable { @Nullable private Rating userRating; @Nullable private Rating overallRating; @Nullable private byte[] artworkData; + @Nullable @PictureType private Integer artworkDataType; @Nullable private Uri artworkUri; @Nullable private Integer trackNumber; @Nullable private Integer totalTrackCount; @@ -83,6 +84,7 @@ public final class MediaMetadata implements Bundleable { this.userRating = mediaMetadata.userRating; this.overallRating = mediaMetadata.overallRating; this.artworkData = mediaMetadata.artworkData; + this.artworkDataType = mediaMetadata.artworkDataType; this.artworkUri = mediaMetadata.artworkUri; this.trackNumber = mediaMetadata.trackNumber; this.totalTrackCount = mediaMetadata.totalTrackCount; @@ -168,9 +170,41 @@ public final class MediaMetadata implements Bundleable { return this; } - /** Sets the artwork data as a compressed byte array. */ + /** + * @deprecated Use {@link #setArtworkData(byte[] data, Integer pictureType)} or {@link + * #maybeSetArtworkData(byte[] data, int pictureType)}, providing a {@link PictureType}. + */ + @Deprecated public Builder setArtworkData(@Nullable byte[] artworkData) { + return setArtworkData(artworkData, /* artworkDataType= */ null); + } + + /** + * Sets the artwork data as a compressed byte array with an associated {@link PictureType + * artworkDataType}. + */ + public Builder setArtworkData( + @Nullable byte[] artworkData, @Nullable @PictureType Integer artworkDataType) { this.artworkData = artworkData == null ? null : artworkData.clone(); + this.artworkDataType = artworkDataType; + return this; + } + + /** + * Sets the artwork data as a compressed byte array in the event that the associated {@link + * PictureType} is {@link #PICTURE_TYPE_FRONT_COVER}, the existing {@link PictureType} is not + * {@link #PICTURE_TYPE_FRONT_COVER}, or the current artworkData is not set. + * + *

Use {@link #setArtworkData(byte[], Integer)} to set the artwork data without checking the + * {@link PictureType}. + */ + public Builder maybeSetArtworkData(byte[] artworkData, @PictureType int artworkDataType) { + if (this.artworkData == null + || Util.areEqual(artworkDataType, PICTURE_TYPE_FRONT_COVER) + || !Util.areEqual(this.artworkDataType, PICTURE_TYPE_FRONT_COVER)) { + this.artworkData = artworkData.clone(); + this.artworkDataType = artworkDataType; + } return this; } @@ -393,6 +427,61 @@ public final class MediaMetadata implements Bundleable { /** Type for a folder containing media categorized by year. */ public static final int FOLDER_TYPE_YEARS = 6; + /** + * The picture type of the artwork. + * + *

Values sourced from the ID3 v2.4 specification (See section 4.14 of + * https://id3.org/id3v2.4.0-frames). + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + PICTURE_TYPE_OTHER, + PICTURE_TYPE_FILE_ICON, + PICTURE_TYPE_FILE_ICON_OTHER, + PICTURE_TYPE_FRONT_COVER, + PICTURE_TYPE_BACK_COVER, + PICTURE_TYPE_LEAFLET_PAGE, + PICTURE_TYPE_MEDIA, + PICTURE_TYPE_LEAD_ARTIST_PERFORMER, + PICTURE_TYPE_ARTIST_PERFORMER, + PICTURE_TYPE_CONDUCTOR, + PICTURE_TYPE_BAND_ORCHESTRA, + PICTURE_TYPE_COMPOSER, + PICTURE_TYPE_LYRICIST, + PICTURE_TYPE_RECORDING_LOCATION, + PICTURE_TYPE_DURING_RECORDING, + PICTURE_TYPE_DURING_PERFORMANCE, + PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, + PICTURE_TYPE_A_BRIGHT_COLORED_FISH, + PICTURE_TYPE_ILLUSTRATION, + PICTURE_TYPE_BAND_ARTIST_LOGO, + PICTURE_TYPE_PUBLISHER_STUDIO_LOGO + }) + public @interface PictureType {} + + public static final int PICTURE_TYPE_OTHER = 0x00; + public static final int PICTURE_TYPE_FILE_ICON = 0x01; + public static final int PICTURE_TYPE_FILE_ICON_OTHER = 0x02; + public static final int PICTURE_TYPE_FRONT_COVER = 0x03; + public static final int PICTURE_TYPE_BACK_COVER = 0x04; + public static final int PICTURE_TYPE_LEAFLET_PAGE = 0x05; + public static final int PICTURE_TYPE_MEDIA = 0x06; + public static final int PICTURE_TYPE_LEAD_ARTIST_PERFORMER = 0x07; + public static final int PICTURE_TYPE_ARTIST_PERFORMER = 0x08; + public static final int PICTURE_TYPE_CONDUCTOR = 0x09; + public static final int PICTURE_TYPE_BAND_ORCHESTRA = 0x0A; + public static final int PICTURE_TYPE_COMPOSER = 0x0B; + public static final int PICTURE_TYPE_LYRICIST = 0x0C; + public static final int PICTURE_TYPE_RECORDING_LOCATION = 0x0D; + public static final int PICTURE_TYPE_DURING_RECORDING = 0x0E; + public static final int PICTURE_TYPE_DURING_PERFORMANCE = 0x0F; + public static final int PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE = 0x10; + public static final int PICTURE_TYPE_A_BRIGHT_COLORED_FISH = 0x11; + public static final int PICTURE_TYPE_ILLUSTRATION = 0x12; + public static final int PICTURE_TYPE_BAND_ARTIST_LOGO = 0x13; + public static final int PICTURE_TYPE_PUBLISHER_STUDIO_LOGO = 0x14; + /** Empty {@link MediaMetadata}. */ public static final MediaMetadata EMPTY = new MediaMetadata.Builder().build(); @@ -422,6 +511,8 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Rating overallRating; /** Optional artwork data as a compressed byte array. */ @Nullable public final byte[] artworkData; + /** Optional {@link PictureType} of the artwork data. */ + @Nullable @PictureType public final Integer artworkDataType; /** Optional artwork {@link Uri}. */ @Nullable public final Uri artworkUri; /** Optional track number. */ @@ -498,6 +589,7 @@ public final class MediaMetadata implements Bundleable { this.userRating = builder.userRating; this.overallRating = builder.overallRating; this.artworkData = builder.artworkData; + this.artworkDataType = builder.artworkDataType; this.artworkUri = builder.artworkUri; this.trackNumber = builder.trackNumber; this.totalTrackCount = builder.totalTrackCount; @@ -545,6 +637,7 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(userRating, that.userRating) && Util.areEqual(overallRating, that.overallRating) && Arrays.equals(artworkData, that.artworkData) + && Util.areEqual(artworkDataType, that.artworkDataType) && Util.areEqual(artworkUri, that.artworkUri) && Util.areEqual(trackNumber, that.trackNumber) && Util.areEqual(totalTrackCount, that.totalTrackCount) @@ -579,6 +672,7 @@ public final class MediaMetadata implements Bundleable { userRating, overallRating, Arrays.hashCode(artworkData), + artworkDataType, artworkUri, trackNumber, totalTrackCount, @@ -615,6 +709,7 @@ public final class MediaMetadata implements Bundleable { FIELD_USER_RATING, FIELD_OVERALL_RATING, FIELD_ARTWORK_DATA, + FIELD_ARTWORK_DATA_TYPE, FIELD_ARTWORK_URI, FIELD_TRACK_NUMBER, FIELD_TOTAL_TRACK_COUNT, @@ -666,6 +761,7 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_TOTAL_DISC_COUNT = 26; private static final int FIELD_GENRE = 27; private static final int FIELD_COMPILATION = 28; + private static final int FIELD_ARTWORK_DATA_TYPE = 29; private static final int FIELD_EXTRAS = 1000; @Override @@ -729,6 +825,9 @@ public final class MediaMetadata implements Bundleable { if (totalDiscCount != null) { bundle.putInt(keyForField(FIELD_TOTAL_DISC_COUNT), totalDiscCount); } + if (artworkDataType != null) { + bundle.putInt(keyForField(FIELD_ARTWORK_DATA_TYPE), artworkDataType); + } if (extras != null) { bundle.putBundle(keyForField(FIELD_EXTRAS), extras); } @@ -749,7 +848,11 @@ public final class MediaMetadata implements Bundleable { .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) .setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI))) - .setArtworkData(bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA))) + .setArtworkData( + bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)), + bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE)) + ? bundle.getInt(keyForField(FIELD_ARTWORK_DATA_TYPE)) + : null) .setArtworkUri(bundle.getParcelable(keyForField(FIELD_ARTWORK_URI))) .setWriter(bundle.getCharSequence(keyForField(FIELD_WRITER))) .setComposer(bundle.getCharSequence(keyForField(FIELD_COMPOSER))) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/flac/PictureFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/flac/PictureFrame.java index 02c8dd7533..8d62be52c6 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/flac/PictureFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/flac/PictureFrame.java @@ -76,7 +76,7 @@ public final class PictureFrame implements Metadata.Entry { @Override public void populateMediaMetadata(MediaMetadata.Builder builder) { - builder.setArtworkData(pictureData); + builder.maybeSetArtworkData(pictureData, pictureType); } @Override 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 ee1fac9553..84d426c6b9 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 @@ -53,7 +53,7 @@ public final class ApicFrame extends Id3Frame { @Override public void populateMediaMetadata(MediaMetadata.Builder builder) { - builder.setArtworkData(pictureData); + builder.maybeSetArtworkData(pictureData, pictureType); } @Override 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 c7120e806d..1405bbc1cf 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 @@ -20,10 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import android.os.Bundle; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.MediaMetadata.PictureType; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.util.MimeTypes; -import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,6 +46,7 @@ public class MediaMetadataTest { assertThat(mediaMetadata.userRating).isNull(); assertThat(mediaMetadata.overallRating).isNull(); assertThat(mediaMetadata.artworkData).isNull(); + assertThat(mediaMetadata.artworkDataType).isNull(); assertThat(mediaMetadata.artworkUri).isNull(); assertThat(mediaMetadata.trackNumber).isNull(); assertThat(mediaMetadata.totalTrackCount).isNull(); @@ -79,9 +80,10 @@ public class MediaMetadataTest { @Test public void builderSetArtworkData_setsArtworkData() { byte[] bytes = new byte[] {35, 12, 6, 77}; - MediaMetadata mediaMetadata = new MediaMetadata.Builder().setArtworkData(bytes).build(); + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setArtworkData(new byte[] {35, 12, 6, 77}, null).build(); - assertThat(Arrays.equals(mediaMetadata.artworkData, bytes)).isTrue(); + assertThat(mediaMetadata.artworkData).isEqualTo(bytes); } @Test @@ -104,7 +106,8 @@ public class MediaMetadataTest { .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}) + .setArtworkData( + new byte[] {-88, 12, 3, 2, 124, -54, -33, 69}, MediaMetadata.PICTURE_TYPE_MEDIA) .setTrackNumber(4) .setTotalTrackCount(12) .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) @@ -133,11 +136,12 @@ public class MediaMetadataTest { @Test public void builderPopulatedFromApicFrameEntry_setsArtwork() { byte[] pictureData = new byte[] {-12, 52, 33, 85, 34, 22, 1, -55}; + @PictureType int pictureType = MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE; Metadata.Entry entry = new ApicFrame( /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, /* description= */ "an image", - /* pictureType= */ 0x03, + pictureType, pictureData); MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon(); @@ -145,5 +149,65 @@ public class MediaMetadataTest { MediaMetadata mediaMetadata = builder.build(); assertThat(mediaMetadata.artworkData).isEqualTo(pictureData); + assertThat(mediaMetadata.artworkDataType).isEqualTo(pictureType); + } + + @Test + public void builderPopulatedFromApicFrameEntry_considersTypePriority() { + byte[] data1 = new byte[] {1, 1, 1, 1}; + Metadata.Entry entry1 = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO, + data1); + byte[] data2 = new byte[] {2, 2, 2, 2}; + Metadata.Entry entry2 = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, + data2); + byte[] data3 = new byte[] {3, 3, 3, 3}; + Metadata.Entry entry3 = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + MediaMetadata.PICTURE_TYPE_FRONT_COVER, + data3); + byte[] data4 = new byte[] {4, 4, 4, 4}; + Metadata.Entry entry4 = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + MediaMetadata.PICTURE_TYPE_ILLUSTRATION, + data4); + byte[] data5 = new byte[] {5, 5, 5, 5}; + Metadata.Entry entry5 = + new ApicFrame( + /* mimeType= */ MimeTypes.BASE_TYPE_IMAGE, + /* description= */ "an image", + MediaMetadata.PICTURE_TYPE_FRONT_COVER, + data5); + MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon(); + + entry1.populateMediaMetadata(builder); + assertThat(builder.build().artworkData).isEqualTo(data1); + + // Data updates when any type is given, if the current type is not front cover. + entry2.populateMediaMetadata(builder); + assertThat(builder.build().artworkData).isEqualTo(data2); + + // Data updates because this entry picture type is front cover. + entry3.populateMediaMetadata(builder); + assertThat(builder.build().artworkData).isEqualTo(data3); + + // Data does not update because the current type is front cover, and this entry type is not. + entry4.populateMediaMetadata(builder); + assertThat(builder.build().artworkData).isEqualTo(data3); + + // Data updates because this entry picture type is front cover. + entry5.populateMediaMetadata(builder); + assertThat(builder.build().artworkData).isEqualTo(data5); } }