diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 9053f8990a..a84bb7bd36 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -35,7 +35,6 @@ import com.google.android.exoplayer2.metadata.id3.GeobFrame; import com.google.android.exoplayer2.metadata.id3.Id3Frame; import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; -import com.google.android.exoplayer2.metadata.id3.TxxxFrame; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.TrackGroup; @@ -359,10 +358,10 @@ import java.util.Locale; private void printMetadata(Metadata metadata, String prefix) { for (int i = 0; i < metadata.length(); i++) { Metadata.Entry entry = metadata.get(i); - if (entry instanceof TxxxFrame) { - TxxxFrame txxxFrame = (TxxxFrame) entry; - Log.d(TAG, prefix + String.format("%s: description=%s, value=%s", txxxFrame.id, - txxxFrame.description, txxxFrame.value)); + if (entry instanceof TextInformationFrame) { + TextInformationFrame textInformationFrame = (TextInformationFrame) entry; + Log.d(TAG, prefix + String.format("%s: value=%s", textInformationFrame.id, + textInformationFrame.value)); } else if (entry instanceof PrivFrame) { PrivFrame privFrame = (PrivFrame) entry; Log.d(TAG, prefix + String.format("%s: owner=%s", privFrame.id, privFrame.owner)); @@ -374,10 +373,6 @@ import java.util.Locale; ApicFrame apicFrame = (ApicFrame) entry; Log.d(TAG, prefix + String.format("%s: mimeType=%s, description=%s", apicFrame.id, apicFrame.mimeType, apicFrame.description)); - } else if (entry instanceof TextInformationFrame) { - TextInformationFrame textInformationFrame = (TextInformationFrame) entry; - Log.d(TAG, prefix + String.format("%s: description=%s", textInformationFrame.id, - textInformationFrame.description)); } else if (entry instanceof CommentFrame) { CommentFrame commentFrame = (CommentFrame) entry; Log.d(TAG, prefix + String.format("%s: language=%s description=%s", commentFrame.id, diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java index c8c1b4ed93..e13afceb40 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java @@ -59,8 +59,8 @@ public final class FormatTest extends TestCase { DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2); byte[] projectionData = new byte[] {1, 2, 3}; Metadata metadata = new Metadata( - new TextInformationFrame("id1", "description1"), - new TextInformationFrame("id2", "description2")); + new TextInformationFrame("id1", "description1", "value1"), + new TextInformationFrame("id2", "description2", "value2")); Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java new file mode 100644 index 0000000000..182ae6f1c9 --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import android.os.Parcel; +import junit.framework.TestCase; + +/** + * Test for {@link ChapterFrame}. + */ +public final class ChapterFrameTest extends TestCase { + + public void testParcelable() { + Id3Frame[] subFrames = new Id3Frame[] { + new TextInformationFrame("TIT2", null, "title"), + new UrlLinkFrame("WXXX", "description", "url") + }; + ChapterFrame chapterFrameToParcel = new ChapterFrame("id", 0, 1, 2, 3, subFrames); + + Parcel parcel = Parcel.obtain(); + chapterFrameToParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + ChapterFrame chapterFrameFromParcel = ChapterFrame.CREATOR.createFromParcel(parcel); + assertEquals(chapterFrameToParcel, chapterFrameFromParcel); + + parcel.recycle(); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrameTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrameTest.java new file mode 100644 index 0000000000..b0819ff427 --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrameTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import android.os.Parcel; +import junit.framework.TestCase; + +/** + * Test for {@link ChapterTOCFrame}. + */ +public final class ChapterTOCFrameTest extends TestCase { + + public void testParcelable() { + String[] children = new String[] {"child0", "child1"}; + Id3Frame[] subFrames = new Id3Frame[] { + new TextInformationFrame("TIT2", null, "title"), + new UrlLinkFrame("WXXX", "description", "url") + }; + ChapterTOCFrame chapterTOCFrameToParcel = new ChapterTOCFrame("id", false, true, children, + subFrames); + + Parcel parcel = Parcel.obtain(); + chapterTOCFrameToParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + ChapterTOCFrame chapterTOCFrameFromParcel = ChapterTOCFrame.CREATOR.createFromParcel(parcel); + assertEquals(chapterTOCFrameToParcel, chapterTOCFrameFromParcel); + + parcel.recycle(); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 20b026d670..e271108ce4 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -32,9 +32,10 @@ public final class Id3DecoderTest extends TestCase { Id3Decoder decoder = new Id3Decoder(); Metadata metadata = decoder.decode(rawId3, rawId3.length); assertEquals(1, metadata.length()); - TxxxFrame txxxFrame = (TxxxFrame) metadata.get(0); - assertEquals("", txxxFrame.description); - assertEquals("mdialog_VINDICO1527664_start", txxxFrame.value); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); + assertEquals("TXXX", textInformationFrame.id); + assertEquals("", textInformationFrame.description); + assertEquals("mdialog_VINDICO1527664_start", textInformationFrame.value); } public void testDecodeApicFrame() throws MetadataDecoderException { @@ -60,7 +61,8 @@ public final class Id3DecoderTest extends TestCase { assertEquals(1, metadata.length()); TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); assertEquals("TIT2", textInformationFrame.id); - assertEquals("Hello World", textInformationFrame.description); + assertNull(textInformationFrame.description); + assertEquals("Hello World", textInformationFrame.value); } public void testDecodePrivFrame() throws MetadataDecoderException { diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index e99dab053b..fed1694925 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -188,7 +188,7 @@ import com.google.android.exoplayer2.util.Util; if (atomType == Atom.TYPE_data) { data.skipBytes(8); // version (1), flags (3), empty (4) String value = data.readNullTerminatedString(atomSize - 16); - return new TextInformationFrame(id, value); + return new TextInformationFrame(id, null, value); } Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type)); return null; @@ -213,7 +213,7 @@ import com.google.android.exoplayer2.util.Util; value = Math.min(1, value); } if (value >= 0) { - return isTextInformationFrame ? new TextInformationFrame(id, Integer.toString(value)) + return isTextInformationFrame ? new TextInformationFrame(id, null, Integer.toString(value)) : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); } Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); @@ -228,12 +228,12 @@ import com.google.android.exoplayer2.util.Util; data.skipBytes(10); // version (1), flags (3), empty (4), empty (2) int index = data.readUnsignedShort(); if (index > 0) { - String description = "" + index; + String value = "" + index; int count = data.readUnsignedShort(); if (count > 0) { - description += "/" + count; + value += "/" + count; } - return new TextInformationFrame(attributeName, description); + return new TextInformationFrame(attributeName, null, value); } } Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type)); @@ -245,7 +245,7 @@ import com.google.android.exoplayer2.util.Util; String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) ? STANDARD_GENRES[genreCode - 1] : null; if (genreString != null) { - return new TextInformationFrame("TCON", genreString); + return new TextInformationFrame("TCON", null, genreString); } Log.w(TAG, "Failed to parse standard genre code"); return null; diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java similarity index 59% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java index 0a032b3d88..22fd0d5fe4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java @@ -16,13 +16,13 @@ package com.google.android.exoplayer2.metadata.id3; import android.os.Parcel; - import com.google.android.exoplayer2.util.Util; +import java.util.Arrays; /** - * Chapter information "CHAP" ID3 frame. + * Chapter information ID3 frame. */ -public final class ChapFrame extends Id3Frame { +public final class ChapterFrame extends Id3Frame { public static final String ID = "CHAP"; @@ -31,33 +31,31 @@ public final class ChapFrame extends Id3Frame { public final int endTime; public final int startOffset; public final int endOffset; - public final String title; - public final String url; - public final ApicFrame image; + private final Id3Frame[] subFrames; - public ChapFrame(String chapterId, int startTime, int endTime, int startOffset, int endOffset, - String title, String url, ApicFrame image) { + public ChapterFrame(String chapterId, int startTime, int endTime, int startOffset, int endOffset, + Id3Frame[] subFrames) { super(ID); this.chapterId = chapterId; this.startTime = startTime; this.endTime = endTime; this.startOffset = startOffset; this.endOffset = endOffset; - this.title = title; - this.url = url; - this.image = image; + this.subFrames = subFrames; } - /* package */ ChapFrame(Parcel in) { + /* package */ ChapterFrame(Parcel in) { super(ID); this.chapterId = in.readString(); this.startTime = in.readInt(); this.endTime = in.readInt(); this.startOffset = in.readInt(); this.endOffset = in.readInt(); - this.title = in.readString(); - this.url = in.readString(); - this.image = in.readParcelable(ApicFrame.class.getClassLoader()); + int subFrameCount = in.readInt(); + subFrames = new Id3Frame[subFrameCount]; + for (int i = 0; i < subFrameCount; i++) { + subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader()); + } } @Override @@ -68,28 +66,23 @@ public final class ChapFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - ChapFrame other = (ChapFrame) obj; + ChapterFrame other = (ChapterFrame) obj; return startTime == other.startTime - && endTime == other.endTime - && startOffset == other.startOffset - && endOffset == other.endOffset - && Util.areEqual(chapterId, other.chapterId) - && Util.areEqual(title, other.title) - && Util.areEqual(url, other.url) - && Util.areEqual(image, other.image); + && endTime == other.endTime + && startOffset == other.startOffset + && endOffset == other.endOffset + && Util.areEqual(chapterId, other.chapterId) + && Arrays.equals(subFrames, other.subFrames); } @Override public int hashCode() { int result = 17; - result = 31 * result + (chapterId != null ? chapterId.hashCode() : 0); result = 31 * result + startTime; result = 31 * result + endTime; result = 31 * result + startOffset; result = 31 * result + endOffset; - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (url != null ? url.hashCode() : 0); - result = 31 * result + (image != null ? image.hashCode() : 0); + result = 31 * result + (chapterId != null ? chapterId.hashCode() : 0); return result; } @@ -100,10 +93,10 @@ public final class ChapFrame extends Id3Frame { dest.writeInt(endTime); dest.writeInt(startOffset); dest.writeInt(endOffset); - dest.writeString(title); - dest.writeString(url); - dest.writeString(title); - dest.writeParcelable(image, flags); + dest.writeInt(subFrames.length); + for (int i = 0; i < subFrames.length; i++) { + dest.writeParcelable(subFrames[i], 0); + } } @Override @@ -111,15 +104,18 @@ public final class ChapFrame extends Id3Frame { return 0; } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { + @Override - public ChapFrame createFromParcel(Parcel in) { - return new ChapFrame(in); + public ChapterFrame createFromParcel(Parcel in) { + return new ChapterFrame(in); } @Override - public ChapFrame[] newArray(int size) { - return new ChapFrame[size]; + public ChapterFrame[] newArray(int size) { + return new ChapterFrame[size]; } + }; + } diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java similarity index 59% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java index 1511763682..6dfcf9f104 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java @@ -22,9 +22,9 @@ import com.google.android.exoplayer2.util.Util; import java.util.Arrays; /** - * Chapter table of contents information "CTOC" ID3 frame. + * Chapter table of contents ID3 frame. */ -public final class CtocFrame extends Id3Frame { +public final class ChapterTOCFrame extends Id3Frame { public static final String ID = "CTOC"; @@ -32,24 +32,29 @@ public final class CtocFrame extends Id3Frame { public final boolean isRoot; public final boolean isOrdered; public final String[] children; - public final String title; + public final Id3Frame[] subFrames; - public CtocFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, String title) { + public ChapterTOCFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, + Id3Frame[] subFrames) { super(ID); this.elementId = elementId; this.isRoot = isRoot; this.isOrdered = isOrdered; this.children = children; - this.title = title; + this.subFrames = subFrames; } - /* package */ CtocFrame(Parcel in) { + /* package */ ChapterTOCFrame(Parcel in) { super(ID); this.elementId = in.readString(); this.isRoot = in.readByte() != 0; this.isOrdered = in.readByte() != 0; this.children = in.createStringArray(); - this.title = in.readString(); + int subFrameCount = in.readInt(); + subFrames = new Id3Frame[subFrameCount]; + for (int i = 0; i < subFrameCount; i++) { + subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader()); + } } @Override @@ -60,43 +65,47 @@ public final class CtocFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - CtocFrame other = (CtocFrame) obj; + ChapterTOCFrame other = (ChapterTOCFrame) obj; return isRoot == other.isRoot - && isOrdered == other.isOrdered - && Util.areEqual(elementId, other.elementId) - && Util.areEqual(title, other.title) - && Arrays.equals(children, other.children); + && isOrdered == other.isOrdered + && Util.areEqual(elementId, other.elementId) + && Arrays.equals(children, other.children) + && Arrays.equals(subFrames, other.subFrames); } @Override public int hashCode() { int result = 17; - result = 31 * result + (elementId != null ? elementId.hashCode() : 0); result = 31 * result + (isRoot ? 1 : 0); result = 31 * result + (isOrdered ? 1 : 0); - result = 31 * result + Arrays.hashCode(children); - result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + (elementId != null ? elementId.hashCode() : 0); return result; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(elementId); - dest.writeByte((byte)(isRoot ? 1 : 0)); - dest.writeByte((byte)(isOrdered ? 1 : 0)); + dest.writeByte((byte) (isRoot ? 1 : 0)); + dest.writeByte((byte) (isOrdered ? 1 : 0)); dest.writeStringArray(children); - dest.writeString(title); + dest.writeInt(subFrames.length); + for (int i = 0; i < subFrames.length; i++) { + dest.writeParcelable(subFrames[i], 0); + } } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { + @Override - public CtocFrame createFromParcel(Parcel in) { - return new CtocFrame(in); + public ChapterTOCFrame createFromParcel(Parcel in) { + return new ChapterTOCFrame(in); } @Override - public CtocFrame[] newArray(int size) { - return new CtocFrame[size]; + public ChapterTOCFrame[] newArray(int size) { + return new ChapterTOCFrame[size]; } + }; + } diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 6a7cef4d50..3bcb4cfa08 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -98,7 +98,8 @@ public final class Id3Decoder implements MetadataDecoder { int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; while (id3Data.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize); + Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize); if (frame != null) { id3Frames.add(frame); } @@ -204,7 +205,7 @@ public final class Id3Decoder implements MetadataDecoder { } private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data, - boolean unsignedIntFrameSizeHack, int frameHeaderSize) { + boolean unsignedIntFrameSizeHack, int frameHeaderSize) { int frameId0 = id3Data.readUnsignedByte(); int frameId1 = id3Data.readUnsignedByte(); int frameId2 = id3Data.readUnsignedByte(); @@ -280,6 +281,19 @@ public final class Id3Decoder implements MetadataDecoder { if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X' && (majorVersion == 2 || frameId3 == 'X')) { frame = decodeTxxxFrame(id3Data, frameSize); + } else if (frameId0 == 'T') { + String id = majorVersion == 2 + ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + frame = decodeTextInformationFrame(id3Data, frameSize, id); + } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' + && (majorVersion == 2 || frameId3 == 'X')) { + frame = decodeWxxxFrame(id3Data, frameSize); + } else if (frameId0 == 'W') { + String id = majorVersion == 2 + ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + frame = decodeUrlLinkFrame(id3Data, frameSize, id); } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { frame = decodePrivFrame(id3Data, frameSize); } else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' @@ -288,23 +302,15 @@ public final class Id3Decoder implements MetadataDecoder { } else if (majorVersion == 2 ? (frameId0 == 'P' && frameId1 == 'I' && frameId2 == 'C') : (frameId0 == 'A' && frameId1 == 'P' && frameId2 == 'I' && frameId3 == 'C')) { frame = decodeApicFrame(id3Data, frameSize, majorVersion); - } else if (frameId0 == 'T') { - String id = majorVersion == 2 - ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) - : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); - frame = decodeTextInformationFrame(id3Data, frameSize, id); } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' && (frameId3 == 'M' || majorVersion == 2)) { frame = decodeCommentFrame(id3Data, frameSize); - } else if (majorVersion == 2 ? (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X') - : (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' && frameId3 == 'X')) { - frame = decodeWxxxFrame(id3Data, frameSize); } else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') { - frame = decodeChapFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, - frameHeaderSize); + frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize); } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { - frame = decodeCtocFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, - frameHeaderSize); + frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize); } else { String id = majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) @@ -320,7 +326,7 @@ public final class Id3Decoder implements MetadataDecoder { } } - private static TxxxFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) + private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); @@ -340,7 +346,65 @@ public final class Id3Decoder implements MetadataDecoder { value = ""; } - return new TxxxFrame(description, value); + return new TextInformationFrame("TXXX", description, value); + } + + private static TextInformationFrame decodeTextInformationFrame(ParsableByteArray id3Data, + int frameSize, String id) throws UnsupportedEncodingException { + if (frameSize <= 1) { + // Frame is empty or contains only the text encoding byte. + return new TextInformationFrame(id, null, ""); + } + + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + 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); + + return new TextInformationFrame(id, null, value); + } + + private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, + int frameSize) throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + String url; + int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); + if (urlStartIndex < data.length) { + int urlEndIndex = indexOfZeroByte(data, 0); + url = new String(data, urlStartIndex, urlEndIndex - urlStartIndex, "ISO-8859-1"); + } else { + url = ""; + } + + return new UrlLinkFrame("WXXX", description, url); + } + + private static UrlLinkFrame decodeUrlLinkFrame(ParsableByteArray id3Data, int frameSize, + String id) throws UnsupportedEncodingException { + if (frameSize == 0) { + // Frame is empty. + return new UrlLinkFrame(id, null, ""); + } + + byte[] data = new byte[frameSize]; + id3Data.readBytes(data, 0, frameSize); + + int urlEndIndex = indexOfZeroByte(data, 0); + String url = new String(data, 0, urlEndIndex, "ISO-8859-1"); + + return new UrlLinkFrame(id, null, url); } private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) @@ -448,132 +512,69 @@ public final class Id3Decoder implements MetadataDecoder { return new CommentFrame(language, description, text); } - private static TextInformationFrame decodeTextInformationFrame(ParsableByteArray id3Data, - int frameSize, String id) throws UnsupportedEncodingException { - if (frameSize <= 1) { - // Frame is empty or contains only the text encoding byte. - return new TextInformationFrame(id, ""); - } - - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int descriptionEndIndex = indexOfEos(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); - - return new TextInformationFrame(id, description); - } - - private static WxxxFrame decodeWxxxFrame(ParsableByteArray id3Data, - int frameSize) throws UnsupportedEncodingException { - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int descriptionEndIndex = indexOfEos(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); - - String url; - int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); - if (urlStartIndex < data.length) { - int urlEndIndex = indexOfEos(data, urlStartIndex, encoding); - url = new String(data, urlStartIndex, urlEndIndex - urlStartIndex, charset); - } else { - url = ""; - } - - return new WxxxFrame(description, url); - } - - private static ChapFrame decodeChapFrame(ParsableByteArray id3Data, int frameSize, + private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize, int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize) throws UnsupportedEncodingException { - byte[] frameBytes = new byte[frameSize]; - id3Data.readBytes(frameBytes, 0, frameSize - 1); + int framePosition = id3Data.getPosition(); + int chapterIdEndIndex = indexOfZeroByte(id3Data.data, framePosition); + String chapterId = new String(id3Data.data, framePosition, chapterIdEndIndex - framePosition, + "ISO-8859-1"); + id3Data.setPosition(chapterIdEndIndex + 1); - ParsableByteArray chapterData = new ParsableByteArray(frameBytes); + int startTime = id3Data.readUnsignedByte(); + int endTime = id3Data.readUnsignedByte(); + int startOffset = id3Data.readUnsignedByte(); + int endOffset = id3Data.readUnsignedByte(); - int chapterIdEndIndex = indexOfZeroByte(frameBytes, 0) + 1; - String chapterId = chapterData.readNullTerminatedString(chapterIdEndIndex); - - chapterData.setPosition(chapterIdEndIndex); - int startTime = chapterData.readInt(); - int endTime = chapterData.readInt(); - int startOffset = chapterData.readInt(); - int endOffset = chapterData.readInt(); - - String title = null; - String url = null; - ApicFrame image = null; - - while (chapterData.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(majorVersion, chapterData, unsignedIntFrameSizeHack, - frameHeaderSize); - if (frame == null) { - continue; - } - if (frame instanceof TextInformationFrame) { - TextInformationFrame textFrame = (TextInformationFrame)frame; - if ("TIT2".equals(textFrame.id)) { - title = textFrame.description; - } - } - else if (frame instanceof WxxxFrame) { - WxxxFrame linkFrame = (WxxxFrame)frame; - url = linkFrame.url; - } - else if (frame instanceof ApicFrame) { - image = (ApicFrame)frame; + ArrayList subFrames = new ArrayList<>(); + int limit = framePosition + frameSize; + while (id3Data.getPosition() < limit) { + Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize); + if (frame != null) { + subFrames.add(frame); } } - return new ChapFrame(chapterId, startTime, endTime, startOffset, endOffset, title, url, image); + Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()]; + subFrames.toArray(subFrameArray); + return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, subFrameArray); } - private static CtocFrame decodeCtocFrame(ParsableByteArray id3Data, int frameSize, + private static ChapterTOCFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize, int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize) throws UnsupportedEncodingException { - byte[] frameBytes = new byte[frameSize]; - id3Data.readBytes(frameBytes, 0, frameSize - 1); + int framePosition = id3Data.getPosition(); + int elementIdEndIndex = indexOfZeroByte(id3Data.data, framePosition); + String elementId = new String(id3Data.data, framePosition, elementIdEndIndex - framePosition, + "ISO-8859-1"); + id3Data.setPosition(elementIdEndIndex + 1); - ParsableByteArray tocData = new ParsableByteArray(frameBytes); + int ctocFlags = id3Data.readUnsignedByte(); + boolean isRoot = (ctocFlags & 0x0002) != 0; + boolean isOrdered = (ctocFlags & 0x0001) != 0; - int idEndIndex = indexOfZeroByte(frameBytes, 0) + 1; - String id = tocData.readNullTerminatedString(idEndIndex); - tocData.setPosition(idEndIndex); - - int flags = tocData.readUnsignedByte(); - boolean isRoot = (flags & 0x0002) != 0; - boolean isOrdered = (flags & 0x0001) != 0; - - int entryCount = tocData.readUnsignedByte(); - String[] children = new String[entryCount]; - for (int i = 0; i < entryCount; i++) { - int startIndex = tocData.getPosition(); - int endIndex = indexOfZeroByte(frameBytes, startIndex) + 1; - int stringLength = endIndex - startIndex; - String childId = tocData.readNullTerminatedString(stringLength); - children[i] = childId; + int childCount = id3Data.readUnsignedByte(); + String[] children = new String[childCount]; + for (int i = 0; i < childCount; i++) { + int startIndex = id3Data.getPosition(); + int endIndex = indexOfZeroByte(id3Data.data, startIndex); + children[i] = new String(id3Data.data, startIndex, endIndex - startIndex, "ISO-8859-1"); } - String title = null; - while (tocData.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(majorVersion, tocData, unsignedIntFrameSizeHack, - frameHeaderSize); - if (frame instanceof TextInformationFrame) { - TextInformationFrame textFrame = (TextInformationFrame)frame; - if ("TIT2".equals(textFrame.id)) { - title = textFrame.description; - } + ArrayList subFrames = new ArrayList<>(); + int limit = framePosition + frameSize; + while (id3Data.getPosition() < limit) { + Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack, + frameHeaderSize); + if (frame != null) { + subFrames.add(frame); } } - return new CtocFrame(id, isRoot, isOrdered, children, title); + Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()]; + subFrames.toArray(subFrameArray); + return new ChapterTOCFrame(elementId, isRoot, isOrdered, children, subFrameArray); } private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, @@ -585,8 +586,8 @@ public final class Id3Decoder implements MetadataDecoder { } /** - * Performs in-place removal of unsynchronization for {@code length} bytes starting from - * {@link ParsableByteArray#getPosition()} + * Performs in-place removal of unsynchronization for {@code length} bytes starting from {@link + * ParsableByteArray#getPosition()} * * @param data Contains the data to be processed. * @param length The length of the data to be processed. @@ -605,6 +606,7 @@ public final class Id3Decoder implements MetadataDecoder { /** * Maps encoding byte from ID3v2 frame to a Charset. + * * @param encodingByte The value of encoding byte from ID3v2 frame. * @return Charset name. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java index b8c061fd0a..6c27b7f232 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java @@ -25,15 +25,18 @@ import com.google.android.exoplayer2.util.Util; public final class TextInformationFrame extends Id3Frame { public final String description; + public final String value; - public TextInformationFrame(String id, String description) { + public TextInformationFrame(String id, String description, String value) { super(id); this.description = description; + this.value = value; } /* package */ TextInformationFrame(Parcel in) { super(in.readString()); description = in.readString(); + value = in.readString(); } @Override @@ -45,7 +48,8 @@ public final class TextInformationFrame extends Id3Frame { return false; } TextInformationFrame other = (TextInformationFrame) obj; - return id.equals(other.id) && Util.areEqual(description, other.description); + return id.equals(other.id) && Util.areEqual(description, other.description) + && Util.areEqual(value, other.value); } @Override @@ -53,6 +57,7 @@ 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); return result; } @@ -60,6 +65,7 @@ public final class TextInformationFrame extends Id3Frame { public void writeToParcel(Parcel dest, int flags) { dest.writeString(id); dest.writeString(description); + dest.writeString(value); } public static final Parcelable.Creator CREATOR = diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java deleted file mode 100644 index 5c24e70ef4..0000000000 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.metadata.id3; - -import android.os.Parcel; -import android.os.Parcelable; -import com.google.android.exoplayer2.util.Util; - -/** - * TXXX (User defined text information) ID3 frame. - */ -public final class TxxxFrame extends Id3Frame { - - public static final String ID = "TXXX"; - - public final String description; - public final String value; - - public TxxxFrame(String description, String value) { - super(ID); - this.description = description; - this.value = value; - } - - /* package */ TxxxFrame(Parcel in) { - super(ID); - description = in.readString(); - value = in.readString(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TxxxFrame other = (TxxxFrame) obj; - return Util.areEqual(description, other.description) && Util.areEqual(value, other.value); - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (description != null ? description.hashCode() : 0); - result = 31 * result + (value != null ? value.hashCode() : 0); - return result; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(description); - dest.writeString(value); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - - @Override - public TxxxFrame createFromParcel(Parcel in) { - return new TxxxFrame(in); - } - - @Override - public TxxxFrame[] newArray(int size) { - return new TxxxFrame[size]; - } - - }; - -} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java similarity index 66% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java index 725e1d779a..e3cc4baa38 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java @@ -21,23 +21,21 @@ import android.os.Parcelable; import com.google.android.exoplayer2.util.Util; /** - * Url Frame "WXX" ID3 frame. + * WXXX (User defined URL link) ID3 frame. */ -public final class WxxxFrame extends Id3Frame { - - public static final String ID = "WXXX"; +public final class UrlLinkFrame extends Id3Frame { public final String description; public final String url; - public WxxxFrame(String description, String url) { - super(ID); + public UrlLinkFrame(String id, String description, String url) { + super(id); this.description = description; this.url = url; } - /* package */ WxxxFrame(Parcel in) { - super(ID); + /* package */ UrlLinkFrame(Parcel in) { + super(in.readString()); description = in.readString(); url = in.readString(); } @@ -50,14 +48,15 @@ public final class WxxxFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - WxxxFrame other = (WxxxFrame) obj; - return Util.areEqual(description, other.description) + UrlLinkFrame other = (UrlLinkFrame) obj; + return id.equals(other.id) && Util.areEqual(description, other.description) && Util.areEqual(url, other.url); } @Override public int hashCode() { int result = 17; + result = 31 * result + id.hashCode(); result = 31 * result + (description != null ? description.hashCode() : 0); result = 31 * result + (url != null ? url.hashCode() : 0); return result; @@ -65,23 +64,24 @@ public final class WxxxFrame extends Id3Frame { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); dest.writeString(description); dest.writeString(url); } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { - @Override - public WxxxFrame createFromParcel(Parcel in) { - return new WxxxFrame(in); - } + @Override + public UrlLinkFrame createFromParcel(Parcel in) { + return new UrlLinkFrame(in); + } - @Override - public WxxxFrame[] newArray(int size) { - return new WxxxFrame[size]; - } + @Override + public UrlLinkFrame[] newArray(int size) { + return new UrlLinkFrame[size]; + } - }; + }; }