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 aba39359db..3852255c90 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 @@ -15,9 +15,12 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + import android.net.Uri; import android.os.Bundle; import androidx.annotation.IntDef; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Util; @@ -53,7 +56,12 @@ public final class MediaMetadata implements Bundleable { @Nullable private Integer totalTrackCount; @Nullable @FolderType private Integer folderType; @Nullable private Boolean isPlayable; - @Nullable private Integer year; + @Nullable private Integer recordingYear; + @Nullable private Integer recordingMonth; + @Nullable private Integer recordingDay; + @Nullable private Integer releaseYear; + @Nullable private Integer releaseMonth; + @Nullable private Integer releaseDay; @Nullable private CharSequence writer; @Nullable private CharSequence composer; @Nullable private CharSequence conductor; @@ -80,7 +88,12 @@ public final class MediaMetadata implements Bundleable { this.totalTrackCount = mediaMetadata.totalTrackCount; this.folderType = mediaMetadata.folderType; this.isPlayable = mediaMetadata.isPlayable; - this.year = mediaMetadata.year; + this.recordingYear = mediaMetadata.recordingYear; + this.recordingMonth = mediaMetadata.recordingMonth; + this.recordingDay = mediaMetadata.recordingDay; + this.releaseYear = mediaMetadata.releaseYear; + this.releaseMonth = mediaMetadata.releaseMonth; + this.releaseDay = mediaMetadata.releaseDay; this.writer = mediaMetadata.writer; this.composer = mediaMetadata.composer; this.conductor = mediaMetadata.conductor; @@ -189,9 +202,74 @@ public final class MediaMetadata implements Bundleable { return this; } - /** Sets the year. */ + /** @deprecated Use {@link #setRecordingYear(Integer)} instead. */ + @Deprecated public Builder setYear(@Nullable Integer year) { - this.year = year; + return setRecordingYear(year); + } + + /** Sets the year of the recording date. */ + public Builder setRecordingYear(@Nullable Integer recordingYear) { + this.recordingYear = recordingYear; + return this; + } + + /** + * Sets the month of the recording date. + * + *

Value must be between 1 and 12. + */ + public Builder setRecordingMonth( + @Nullable @IntRange(from = 1, to = 12) Integer recordingMonth) { + if (recordingMonth != null) { + checkArgument(recordingMonth >= 1 && recordingMonth <= 12); + } + this.recordingMonth = recordingMonth; + return this; + } + + /** + * Sets the day of the recording date. + * + *

Value must be between 1 and 31. + */ + public Builder setRecordingDay(@Nullable @IntRange(from = 1, to = 31) Integer recordingDay) { + if (recordingDay != null) { + checkArgument(recordingDay >= 1 && recordingDay <= 31); + } + this.recordingDay = recordingDay; + return this; + } + + /** Sets the year of the release date. */ + public Builder setReleaseYear(@Nullable Integer releaseYear) { + this.releaseYear = releaseYear; + return this; + } + + /** + * Sets the month of the release date. + * + *

Value must be between 1 and 12. + */ + public Builder setReleaseMonth(@Nullable @IntRange(from = 1, to = 12) Integer releaseMonth) { + if (releaseMonth != null) { + checkArgument(releaseMonth >= 1 && releaseMonth <= 12); + } + this.releaseMonth = releaseMonth; + return this; + } + + /** + * Sets the day of the release date. + * + *

Value must be between 1 and 31. + */ + public Builder setReleaseDay(@Nullable @IntRange(from = 1, to = 31) Integer releaseDay) { + if (releaseDay != null) { + checkArgument(releaseDay >= 1 && releaseDay <= 31); + } + this.releaseDay = releaseDay; return this; } @@ -352,8 +430,37 @@ public final class MediaMetadata implements Bundleable { @Nullable @FolderType public final Integer folderType; /** Optional boolean for media playability. */ @Nullable public final Boolean isPlayable; - /** Optional year. */ - @Nullable public final Integer year; + /** @deprecated Use {@link #recordingYear} instead. */ + @Deprecated @Nullable public final Integer year; + /** Optional year of the recording date. */ + @Nullable public final Integer recordingYear; + /** + * Optional month of the recording date. + * + *

Note that there is no guarantee that the month and day are a valid combination. + */ + @Nullable public final Integer recordingMonth; + /** + * Optional day of the recording date. + * + *

Note that there is no guarantee that the month and day are a valid combination. + */ + @Nullable public final Integer recordingDay; + + /** Optional year of the release date. */ + @Nullable public final Integer releaseYear; + /** + * Optional month of the release date. + * + *

Note that there is no guarantee that the month and day are a valid combination. + */ + @Nullable public final Integer releaseMonth; + /** + * Optional day of the release date. + * + *

Note that there is no guarantee that the month and day are a valid combination. + */ + @Nullable public final Integer releaseDay; /** Optional writer. */ @Nullable public final CharSequence writer; /** Optional composer. */ @@ -390,7 +497,13 @@ public final class MediaMetadata implements Bundleable { this.totalTrackCount = builder.totalTrackCount; this.folderType = builder.folderType; this.isPlayable = builder.isPlayable; - this.year = builder.year; + this.year = builder.recordingYear; + this.recordingYear = builder.recordingYear; + this.recordingMonth = builder.recordingMonth; + this.recordingDay = builder.recordingDay; + this.releaseYear = builder.releaseYear; + this.releaseMonth = builder.releaseMonth; + this.releaseDay = builder.releaseDay; this.writer = builder.writer; this.composer = builder.composer; this.conductor = builder.conductor; @@ -429,7 +542,12 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(totalTrackCount, that.totalTrackCount) && Util.areEqual(folderType, that.folderType) && Util.areEqual(isPlayable, that.isPlayable) - && Util.areEqual(year, that.year) + && Util.areEqual(recordingYear, that.recordingYear) + && Util.areEqual(recordingMonth, that.recordingMonth) + && Util.areEqual(recordingDay, that.recordingDay) + && Util.areEqual(releaseYear, that.releaseYear) + && Util.areEqual(releaseMonth, that.releaseMonth) + && Util.areEqual(releaseDay, that.releaseDay) && Util.areEqual(writer, that.writer) && Util.areEqual(composer, that.composer) && Util.areEqual(conductor, that.conductor) @@ -456,7 +574,12 @@ public final class MediaMetadata implements Bundleable { totalTrackCount, folderType, isPlayable, - year, + recordingYear, + recordingMonth, + recordingDay, + releaseYear, + releaseMonth, + releaseDay, writer, composer, conductor, @@ -485,7 +608,12 @@ public final class MediaMetadata implements Bundleable { FIELD_TOTAL_TRACK_COUNT, FIELD_FOLDER_TYPE, FIELD_IS_PLAYABLE, - FIELD_YEAR, + FIELD_RECORDING_YEAR, + FIELD_RECORDING_MONTH, + FIELD_RECORDING_DAY, + FIELD_RELEASE_YEAR, + FIELD_RELEASE_MONTH, + FIELD_RELEASE_DAY, FIELD_WRITER, FIELD_COMPOSER, FIELD_CONDUCTOR, @@ -511,12 +639,17 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_TOTAL_TRACK_COUNT = 13; private static final int FIELD_FOLDER_TYPE = 14; private static final int FIELD_IS_PLAYABLE = 15; - private static final int FIELD_YEAR = 16; - private static final int FIELD_WRITER = 17; - private static final int FIELD_COMPOSER = 18; - private static final int FIELD_CONDUCTOR = 19; - private static final int FIELD_DISC_NUMBER = 20; - private static final int FIELD_TOTAL_DISC_COUNT = 21; + private static final int FIELD_RECORDING_YEAR = 16; + private static final int FIELD_RECORDING_MONTH = 17; + private static final int FIELD_RECORDING_DAY = 18; + private static final int FIELD_RELEASE_YEAR = 19; + private static final int FIELD_RELEASE_MONTH = 20; + private static final int FIELD_RELEASE_DAY = 21; + private static final int FIELD_WRITER = 22; + private static final int FIELD_COMPOSER = 23; + private static final int FIELD_CONDUCTOR = 24; + private static final int FIELD_DISC_NUMBER = 25; + private static final int FIELD_TOTAL_DISC_COUNT = 26; private static final int FIELD_EXTRAS = 1000; @Override @@ -554,8 +687,23 @@ public final class MediaMetadata implements Bundleable { if (isPlayable != null) { bundle.putBoolean(keyForField(FIELD_IS_PLAYABLE), isPlayable); } - if (year != null) { - bundle.putInt(keyForField(FIELD_YEAR), year); + if (recordingYear != null) { + bundle.putInt(keyForField(FIELD_RECORDING_YEAR), recordingYear); + } + if (recordingMonth != null) { + bundle.putInt(keyForField(FIELD_RECORDING_MONTH), recordingMonth); + } + if (recordingDay != null) { + bundle.putInt(keyForField(FIELD_RECORDING_DAY), recordingDay); + } + if (releaseYear != null) { + bundle.putInt(keyForField(FIELD_RELEASE_YEAR), releaseYear); + } + if (releaseMonth != null) { + bundle.putInt(keyForField(FIELD_RELEASE_MONTH), releaseMonth); + } + if (releaseDay != null) { + bundle.putInt(keyForField(FIELD_RELEASE_DAY), releaseDay); } if (discNumber != null) { bundle.putInt(keyForField(FIELD_DISC_NUMBER), discNumber); @@ -614,8 +762,23 @@ public final class MediaMetadata implements Bundleable { if (bundle.containsKey(keyForField(FIELD_IS_PLAYABLE))) { builder.setIsPlayable(bundle.getBoolean(keyForField(FIELD_IS_PLAYABLE))); } - if (bundle.containsKey(keyForField(FIELD_YEAR))) { - builder.setYear(bundle.getInt(keyForField(FIELD_YEAR))); + if (bundle.containsKey(keyForField(FIELD_RECORDING_YEAR))) { + builder.setRecordingYear(bundle.getInt(keyForField(FIELD_RECORDING_YEAR))); + } + if (bundle.containsKey(keyForField(FIELD_RECORDING_MONTH))) { + builder.setRecordingMonth(bundle.getInt(keyForField(FIELD_RECORDING_MONTH))); + } + if (bundle.containsKey(keyForField(FIELD_RECORDING_DAY))) { + builder.setRecordingDay(bundle.getInt(keyForField(FIELD_RECORDING_DAY))); + } + if (bundle.containsKey(keyForField(FIELD_RELEASE_YEAR))) { + builder.setReleaseYear(bundle.getInt(keyForField(FIELD_RELEASE_YEAR))); + } + if (bundle.containsKey(keyForField(FIELD_RELEASE_MONTH))) { + builder.setReleaseMonth(bundle.getInt(keyForField(FIELD_RELEASE_MONTH))); + } + if (bundle.containsKey(keyForField(FIELD_RELEASE_DAY))) { + builder.setReleaseDay(bundle.getInt(keyForField(FIELD_RELEASE_DAY))); } if (bundle.containsKey(keyForField(FIELD_DISC_NUMBER))) { builder.setDiscNumber(bundle.getInt(keyForField(FIELD_DISC_NUMBER))); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index bd083764da..7bab697331 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -22,6 +22,8 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; /** Text information ID3 frame. */ public final class TextInformationFrame extends Id3Frame { @@ -76,11 +78,57 @@ public final class TextInformationFrame extends Id3Frame { case "TYE": case "TYER": try { - builder.setYear(Integer.parseInt(value)); + builder.setRecordingYear(Integer.parseInt(value)); } catch (NumberFormatException e) { // Do nothing, invalid input. } break; + case "TDA": + case "TDAT": + try { + int month = Integer.parseInt(value.substring(2, 4)); + int day = Integer.parseInt(value.substring(0, 2)); + builder.setRecordingMonth(month).setRecordingDay(day); + } catch (NumberFormatException | StringIndexOutOfBoundsException e) { + // Do nothing, invalid input. + } + break; + case "TDRC": + List recordingDate = parseId3v2point4TimestampFrameForDate(value); + switch (recordingDate.size()) { + case 3: + builder.setRecordingDay(recordingDate.get(2)); + // fall through + case 2: + builder.setRecordingMonth(recordingDate.get(1)); + // fall through + case 1: + builder.setRecordingYear(recordingDate.get(0)); + // fall through + break; + default: + // Do nothing. + break; + } + break; + case "TDRL": + List releaseDate = parseId3v2point4TimestampFrameForDate(value); + switch (releaseDate.size()) { + case 3: + builder.setReleaseDay(releaseDate.get(2)); + // fall through + case 2: + builder.setReleaseMonth(releaseDate.get(1)); + // fall through + case 1: + builder.setReleaseYear(releaseDate.get(0)); + // fall through + break; + default: + // Do nothing. + break; + } + break; case "TCM": case "TCOM": builder.setComposer(value); @@ -148,4 +196,28 @@ public final class TextInformationFrame extends Id3Frame { return new TextInformationFrame[size]; } }; + + // Private methods + + private static List parseId3v2point4TimestampFrameForDate(String value) { + // Timestamp string format is ISO-8601, can be `yyyy-MM-ddTHH:mm:ss`, or reduced precision + // at each point, for example `yyyy-MM` or `yyyy-MM-ddTHH:mm`. + List dates = new ArrayList<>(); + try { + if (value.length() >= 10) { + dates.add(Integer.parseInt(value.substring(0, 4))); + dates.add(Integer.parseInt(value.substring(5, 7))); + dates.add(Integer.parseInt(value.substring(8, 10))); + } else if (value.length() >= 7) { + dates.add(Integer.parseInt(value.substring(0, 4))); + dates.add(Integer.parseInt(value.substring(5, 7))); + } else if (value.length() >= 4) { + dates.add(Integer.parseInt(value.substring(0, 4))); + } + } catch (NumberFormatException e) { + // Invalid output, return. + return new ArrayList<>(); + } + return dates; + } } 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 46e8a4fcb2..02f858a7bc 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 @@ -54,7 +54,12 @@ public class MediaMetadataTest { assertThat(mediaMetadata.totalTrackCount).isNull(); assertThat(mediaMetadata.folderType).isNull(); assertThat(mediaMetadata.isPlayable).isNull(); - assertThat(mediaMetadata.year).isNull(); + assertThat(mediaMetadata.recordingYear).isNull(); + assertThat(mediaMetadata.recordingMonth).isNull(); + assertThat(mediaMetadata.recordingDay).isNull(); + assertThat(mediaMetadata.releaseYear).isNull(); + assertThat(mediaMetadata.releaseMonth).isNull(); + assertThat(mediaMetadata.releaseDay).isNull(); assertThat(mediaMetadata.composer).isNull(); assertThat(mediaMetadata.conductor).isNull(); assertThat(mediaMetadata.writer).isNull(); @@ -105,7 +110,12 @@ public class MediaMetadataTest { .setTotalTrackCount(12) .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) .setIsPlayable(true) - .setYear(2000) + .setRecordingYear(2000) + .setRecordingMonth(11) + .setRecordingDay(23) + .setReleaseYear(2001) + .setReleaseMonth(1) + .setReleaseDay(2) .setComposer("Composer") .setConductor("Conductor") .setWriter("Writer") @@ -126,7 +136,10 @@ public class MediaMetadataTest { String albumTitle = "album title"; String albumArtist = "album Artist"; String trackNumberInfo = "11/17"; - String year = "2000"; + String recordingYear = "2000"; + String recordingMonth = "07"; + String recordingDay = "10"; + String releaseDate = "2001-01-02T00:00:00"; String composer = "composer"; String conductor = "conductor"; String writer = "writer"; @@ -141,7 +154,14 @@ public class MediaMetadataTest { /* id= */ "TP2", /* description= */ null, /* value= */ albumArtist), new TextInformationFrame( /* id= */ "TRK", /* description= */ null, /* value= */ trackNumberInfo), - new TextInformationFrame(/* id= */ "TYE", /* description= */ null, /* value= */ year), + new TextInformationFrame( + /* id= */ "TYE", /* description= */ null, /* value= */ recordingYear), + new TextInformationFrame( + /* id= */ "TDA", + /* description= */ null, + /* value= */ recordingDay + recordingMonth), + new TextInformationFrame( + /* id= */ "TDRL", /* description= */ null, /* value= */ releaseDate), new TextInformationFrame( /* id= */ "TCM", /* description= */ null, /* value= */ composer), new TextInformationFrame( @@ -162,7 +182,12 @@ public class MediaMetadataTest { assertThat(mediaMetadata.albumArtist.toString()).isEqualTo(albumArtist); assertThat(mediaMetadata.trackNumber).isEqualTo(11); assertThat(mediaMetadata.totalTrackCount).isEqualTo(17); - assertThat(mediaMetadata.year).isEqualTo(2000); + assertThat(mediaMetadata.recordingYear).isEqualTo(2000); + assertThat(mediaMetadata.recordingMonth).isEqualTo(7); + assertThat(mediaMetadata.recordingDay).isEqualTo(10); + assertThat(mediaMetadata.releaseYear).isEqualTo(2001); + assertThat(mediaMetadata.releaseMonth).isEqualTo(1); + assertThat(mediaMetadata.releaseDay).isEqualTo(2); assertThat(mediaMetadata.composer.toString()).isEqualTo(composer); assertThat(mediaMetadata.conductor.toString()).isEqualTo(conductor); assertThat(mediaMetadata.writer.toString()).isEqualTo(writer);