From b125a2b0b3a0910f057713844e5508d111c71050 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 7 Sep 2022 20:40:10 -0600 Subject: [PATCH 1/4] Add support for ID3v2.4 multi-value tags Add support for multi-value tags based on null terminators. These are specific to ID3v2.4, but is backwards compatible with ID3v2.3, so no version checks are needed. --- .../exoplayer2/metadata/id3/Id3Decoder.java | 36 +++++++++-- .../metadata/id3/TextInformationFrame.java | 64 +++++++++++++------ 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 46bd482178..473cf12ae7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -29,8 +29,10 @@ import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; +import org.w3c.dom.Text; /** Decodes ID3 tags. */ public final class Id3Decoder extends SimpleMetadataDecoder { @@ -458,11 +460,23 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); + // Text information frames can contain multiple values delimited by a null terminator. + // Thus, we after each "end of stream" marker we actually need to keep looking for more + // data, at least until the index is equal to the data length. + ArrayList values = new ArrayList<>(); + int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); - String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); + while (valueStartIndex < valueEndIndex) { + String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); + values.add(value); - return new TextInformationFrame("TXXX", description, value); + valueStartIndex = valueEndIndex + delimiterLength(encoding); + valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + } + + + return new TextInformationFrame("TXXX", description, values.toArray(new String[0])); } @Nullable @@ -479,10 +493,22 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int valueEndIndex = indexOfEos(data, 0, encoding); - String value = new String(data, 0, valueEndIndex, charset); + // Text information frames can contain multiple values delimited by a null terminator. + // Thus, we after each "end of stream" marker we actually need to keep looking for more + // data, at least until the index is equal to the data length. + ArrayList values = new ArrayList<>(); - return new TextInformationFrame(id, null, value); + int valueStartIndex = 0; + int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + while (valueStartIndex < valueEndIndex) { + String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); + values.add(value); + + valueStartIndex = valueEndIndex + delimiterLength(encoding); + valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + } + + return new TextInformationFrame(id, null, values.toArray(new String[0])); } @Nullable diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index 7bab697331..fe2f5652a7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -23,48 +23,71 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** Text information ID3 frame. */ public final class TextInformationFrame extends Id3Frame { + private final static String MULTI_VALUE_DELIMITER = ", "; @Nullable public final String description; + + /** @deprecated Use {@code values} instead. */ + @Deprecated public final String value; - public TextInformationFrame(String id, @Nullable String description, String value) { + public final String[] values; + + public TextInformationFrame(String id, @Nullable String description, String[] values) { super(id); + if (values.length == 0) { + throw new IllegalArgumentException("A text information frame must have at least one value."); + } + this.description = description; - this.value = value; + this.values = values; + this.value = values[0]; + } + + /** @deprecated Use {@code TextInformationFrame(String id, String description, String[] values} instead */ + @Deprecated + public TextInformationFrame(String id, @Nullable String description, String value) { + this(id, description, new String[] {value } ); } /* package */ TextInformationFrame(Parcel in) { super(castNonNull(in.readString())); description = in.readString(); - value = castNonNull(in.readString()); + values = new String[] {}; + in.readStringArray(values); + this.value = values[0]; } @Override public void populateMediaMetadata(MediaMetadata.Builder builder) { + // Depending on the context this frame is in, we either take the first value of a multi-value + // frame because multiple values make no sense, or we join the values together with a comma + // when multiple values do make sense. switch (id) { case "TT2": case "TIT2": - builder.setTitle(value); + builder.setTitle(values[0]); break; case "TP1": case "TPE1": - builder.setArtist(value); + builder.setArtist(String.join(MULTI_VALUE_DELIMITER, values)); break; case "TP2": case "TPE2": - builder.setAlbumArtist(value); + builder.setAlbumArtist(String.join(MULTI_VALUE_DELIMITER, values)); break; case "TAL": case "TALB": - builder.setAlbumTitle(value); + builder.setAlbumTitle(values[0]); break; case "TRK": case "TRCK": - String[] trackNumbers = Util.split(value, "/"); + String[] trackNumbers = Util.split(values[0], "/"); try { int trackNumber = Integer.parseInt(trackNumbers[0]); @Nullable @@ -78,7 +101,7 @@ public final class TextInformationFrame extends Id3Frame { case "TYE": case "TYER": try { - builder.setRecordingYear(Integer.parseInt(value)); + builder.setRecordingYear(Integer.parseInt(values[0])); } catch (NumberFormatException e) { // Do nothing, invalid input. } @@ -86,15 +109,16 @@ public final class TextInformationFrame extends Id3Frame { case "TDA": case "TDAT": try { - int month = Integer.parseInt(value.substring(2, 4)); - int day = Integer.parseInt(value.substring(0, 2)); + String date = values[0]; + int month = Integer.parseInt(date.substring(2, 4)); + int day = Integer.parseInt(date.substring(0, 2)); builder.setRecordingMonth(month).setRecordingDay(day); } catch (NumberFormatException | StringIndexOutOfBoundsException e) { // Do nothing, invalid input. } break; case "TDRC": - List recordingDate = parseId3v2point4TimestampFrameForDate(value); + List recordingDate = parseId3v2point4TimestampFrameForDate(values[0]); switch (recordingDate.size()) { case 3: builder.setRecordingDay(recordingDate.get(2)); @@ -112,7 +136,7 @@ public final class TextInformationFrame extends Id3Frame { } break; case "TDRL": - List releaseDate = parseId3v2point4TimestampFrameForDate(value); + List releaseDate = parseId3v2point4TimestampFrameForDate(values[0]); switch (releaseDate.size()) { case 3: builder.setReleaseDay(releaseDate.get(2)); @@ -131,15 +155,15 @@ public final class TextInformationFrame extends Id3Frame { break; case "TCM": case "TCOM": - builder.setComposer(value); + builder.setComposer(String.join(MULTI_VALUE_DELIMITER, values)); break; case "TP3": case "TPE3": - builder.setConductor(value); + builder.setConductor(String.join(MULTI_VALUE_DELIMITER, values)); break; case "TXT": case "TEXT": - builder.setWriter(value); + builder.setWriter(String.join(MULTI_VALUE_DELIMITER, values)); break; default: break; @@ -157,7 +181,7 @@ public final class TextInformationFrame extends Id3Frame { TextInformationFrame other = (TextInformationFrame) obj; return Util.areEqual(id, other.id) && Util.areEqual(description, other.description) - && Util.areEqual(value, other.value); + && Util.areEqual(values, other.values); } @Override @@ -165,13 +189,13 @@ public final class TextInformationFrame extends Id3Frame { int result = 17; result = 31 * result + id.hashCode(); result = 31 * result + (description != null ? description.hashCode() : 0); - result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + Arrays.hashCode(values); return result; } @Override public String toString() { - return id + ": description=" + description + ": value=" + value; + return id + ": description=" + description + ": values=" + String.join(MULTI_VALUE_DELIMITER, values); } // Parcelable implementation. @@ -180,7 +204,7 @@ public final class TextInformationFrame extends Id3Frame { public void writeToParcel(Parcel dest, int flags) { dest.writeString(id); dest.writeString(description); - dest.writeString(value); + dest.writeStringArray(values); } public static final Parcelable.Creator CREATOR = From 0cdece3a3f9beb237e7558ab8c48ca4d74c75d40 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 7 Sep 2022 20:57:53 -0600 Subject: [PATCH 2/4] Clarify comments Clarify that null-termianted multi-value separators are specific to ID3v2.4. --- .../exoplayer2/metadata/id3/Id3Decoder.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 473cf12ae7..bf0ab5a5ca 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -459,10 +459,10 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - - // Text information frames can contain multiple values delimited by a null terminator. - // Thus, we after each "end of stream" marker we actually need to keep looking for more - // data, at least until the index is equal to the data length. + + // In ID3v2.4, text information frames can contain multiple values delimited by a null + // terminator. Thus, we after each "end of stream" marker we actually need to keep looking + // for more data, at least until the index is equal to the data length. ArrayList values = new ArrayList<>(); int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); @@ -475,7 +475,6 @@ public final class Id3Decoder extends SimpleMetadataDecoder { valueEndIndex = indexOfEos(data, valueStartIndex, encoding); } - return new TextInformationFrame("TXXX", description, values.toArray(new String[0])); } @@ -493,9 +492,9 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - // Text information frames can contain multiple values delimited by a null terminator. - // Thus, we after each "end of stream" marker we actually need to keep looking for more - // data, at least until the index is equal to the data length. + // In ID3v2.4, text information frames can contain multiple values delimited by a null + // terminator. Thus, we after each "end of stream" marker we actually need to keep looking + // for more data, at least until the index is equal to the data length. ArrayList values = new ArrayList<>(); int valueStartIndex = 0; From b054813e6da86c9d17b12af309d6c8c232011863 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 7 Sep 2022 22:11:06 -0600 Subject: [PATCH 3/4] Rename eos terminology Rename indexOfEos to indexOfTerminator to better reflect how a terminator != the end of a frame now. --- .../exoplayer2/metadata/id3/Id3Decoder.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index bf0ab5a5ca..bfe745b88f 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -29,10 +29,8 @@ import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Locale; -import org.w3c.dom.Text; /** Decodes ID3 tags. */ public final class Id3Decoder extends SimpleMetadataDecoder { @@ -457,22 +455,22 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - + // In ID3v2.4, text information frames can contain multiple values delimited by a null // terminator. Thus, we after each "end of stream" marker we actually need to keep looking // for more data, at least until the index is equal to the data length. ArrayList values = new ArrayList<>(); int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); - int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); values.add(value); valueStartIndex = valueEndIndex + delimiterLength(encoding); - valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); } return new TextInformationFrame("TXXX", description, values.toArray(new String[0])); @@ -498,13 +496,13 @@ public final class Id3Decoder extends SimpleMetadataDecoder { ArrayList values = new ArrayList<>(); int valueStartIndex = 0; - int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); values.add(value); valueStartIndex = valueEndIndex + delimiterLength(encoding); - valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); } return new TextInformationFrame(id, null, values.toArray(new String[0])); @@ -524,7 +522,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); @@ -571,11 +569,11 @@ public final class Id3Decoder extends SimpleMetadataDecoder { String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); int filenameStartIndex = mimeTypeEndIndex + 1; - int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); + int filenameEndIndex = indexOfTerminator(data, filenameStartIndex, encoding); String filename = decodeStringIfValid(data, filenameStartIndex, filenameEndIndex, charset); int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = decodeStringIfValid(data, descriptionStartIndex, descriptionEndIndex, charset); @@ -613,7 +611,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; int descriptionStartIndex = mimeTypeEndIndex + 2; - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = new String( data, descriptionStartIndex, descriptionEndIndex - descriptionStartIndex, charset); @@ -642,11 +640,11 @@ public final class Id3Decoder extends SimpleMetadataDecoder { data = new byte[frameSize - 4]; id3Data.readBytes(data, 0, frameSize - 4); - int descriptionEndIndex = indexOfEos(data, 0, encoding); + int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int textStartIndex = descriptionEndIndex + delimiterLength(encoding); - int textEndIndex = indexOfEos(data, textStartIndex, encoding); + int textEndIndex = indexOfTerminator(data, textStartIndex, encoding); String text = decodeStringIfValid(data, textStartIndex, textEndIndex, charset); return new CommentFrame(language, description, text); @@ -823,7 +821,7 @@ public final class Id3Decoder extends SimpleMetadataDecoder { : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); } - private static int indexOfEos(byte[] data, int fromIndex, int encoding) { + private static int indexOfTerminator(byte[] data, int fromIndex, int encoding) { int terminationPos = indexOfZeroByte(data, fromIndex); // For single byte encoding charsets, we're done. From d8729e552cda2f3c748f722e6e21cf96221343c8 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 20 Nov 2022 18:02:45 -0700 Subject: [PATCH 4/4] Fix second-order issues Fix second-order issues stemming from the addition of multi-value tags. --- .../ImaServerSideAdInsertionMediaSource.java | 2 +- .../metadata/MetadataRendererTest.java | 2 +- .../extractor/mp3/Mp3Extractor.java | 2 +- .../exoplayer2/metadata/id3/Id3Decoder.java | 8 +++++++ .../metadata/id3/TextInformationFrame.java | 22 ++++++++++--------- .../metadata/id3/Id3DecoderTest.java | 11 +++++----- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java index c06b39da59..a592efbcb4 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaServerSideAdInsertionMediaSource.java @@ -824,7 +824,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou if (entry instanceof TextInformationFrame) { TextInformationFrame textFrame = (TextInformationFrame) entry; if ("TXXX".equals(textFrame.id)) { - streamPlayer.triggerUserTextReceived(textFrame.value); + streamPlayer.triggerUserTextReceived(textFrame.values[0]); } } else if (entry instanceof EventMessage) { EventMessage eventMessage = (EventMessage) entry; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java index 42dcaa572d..85e49d5834 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java @@ -107,7 +107,7 @@ public class MetadataRendererTest { assertThat(metadata).hasSize(1); assertThat(metadata.get(0).length()).isEqualTo(1); TextInformationFrame expectedId3Frame = - new TextInformationFrame("TXXX", "Test description", "Test value"); + new TextInformationFrame("TXXX", "Test description", new String[] { "Test value" }); assertThat(metadata.get(0).get(0)).isEqualTo(expectedId3Frame); } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 245e99c2e4..f0621ab1c2 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -592,7 +592,7 @@ public final class Mp3Extractor implements Extractor { Metadata.Entry entry = metadata.get(i); if (entry instanceof TextInformationFrame && ((TextInformationFrame) entry).id.equals("TLEN")) { - return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).value)); + return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).values[0])); } } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index bfe745b88f..17d53a5c85 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -464,6 +464,10 @@ public final class Id3Decoder extends SimpleMetadataDecoder { ArrayList values = new ArrayList<>(); int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); + if (valueStartIndex >= data.length) { + return new TextInformationFrame("TXXX", description, new String[0]); + } + int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); @@ -496,6 +500,10 @@ public final class Id3Decoder extends SimpleMetadataDecoder { ArrayList values = new ArrayList<>(); int valueStartIndex = 0; + if (valueStartIndex >= data.length) { + return new TextInformationFrame(id, null, new String[0]); + } + int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index fe2f5652a7..fc1f26d7d9 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.os.Parcel; import android.os.Parcelable; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.util.Util; @@ -36,17 +37,19 @@ public final class TextInformationFrame extends Id3Frame { @Deprecated public final String value; + @NonNull public final String[] values; - public TextInformationFrame(String id, @Nullable String description, String[] values) { + public TextInformationFrame(String id, @Nullable String description, @NonNull String[] values) { super(id); - if (values.length == 0) { - throw new IllegalArgumentException("A text information frame must have at least one value."); - } - this.description = description; this.values = values; - this.value = values[0]; + + if (values.length > 0) { + this.value = values[0]; + } else { + this.value = null; + } } /** @deprecated Use {@code TextInformationFrame(String id, String description, String[] values} instead */ @@ -58,8 +61,7 @@ public final class TextInformationFrame extends Id3Frame { /* package */ TextInformationFrame(Parcel in) { super(castNonNull(in.readString())); description = in.readString(); - values = new String[] {}; - in.readStringArray(values); + values = in.createStringArray(); this.value = values[0]; } @@ -181,7 +183,7 @@ public final class TextInformationFrame extends Id3Frame { TextInformationFrame other = (TextInformationFrame) obj; return Util.areEqual(id, other.id) && Util.areEqual(description, other.description) - && Util.areEqual(values, other.values); + && Arrays.equals(values, other.values); } @Override @@ -195,7 +197,7 @@ public final class TextInformationFrame extends Id3Frame { @Override public String toString() { - return id + ": description=" + description + ": values=" + String.join(MULTI_VALUE_DELIMITER, values); + return id + ": description=" + description + ": value=" + String.join(MULTI_VALUE_DELIMITER, values); } // Parcelable implementation. diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 833a290cea..ae53ac3c43 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -52,7 +52,7 @@ public final class Id3DecoderTest { TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); - assertThat(textInformationFrame.value).isEqualTo("mdialog_VINDICO1527664_start"); + assertThat(textInformationFrame.values[0]).isEqualTo("mdialog_VINDICO1527664_start"); // Test UTF-16. rawId3 = @@ -67,7 +67,7 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEqualTo("Hello World"); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).isEmpty(); // Test empty. rawId3 = buildSingleFrameTag("TXXX", new byte[0]); @@ -81,7 +81,7 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TXXX"); assertThat(textInformationFrame.description).isEmpty(); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).isEmpty(); } @Test @@ -95,7 +95,8 @@ public final class Id3DecoderTest { TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); - assertThat(textInformationFrame.value).isEqualTo("Hello World"); + assertThat(textInformationFrame.values.length).isEqualTo(1); + assertThat(textInformationFrame.values[0]).isEqualTo("Hello World"); // Test empty. rawId3 = buildSingleFrameTag("TIT2", new byte[0]); @@ -109,7 +110,7 @@ public final class Id3DecoderTest { textInformationFrame = (TextInformationFrame) metadata.get(0); assertThat(textInformationFrame.id).isEqualTo("TIT2"); assertThat(textInformationFrame.description).isNull(); - assertThat(textInformationFrame.value).isEmpty(); + assertThat(textInformationFrame.values).isEmpty(); } @Test