From f2f1058066af4f9160ad56a9724288afaa350a16 Mon Sep 17 00:00:00 2001 From: Philip Simpson Date: Mon, 16 Jan 2017 17:40:02 +1030 Subject: [PATCH 1/4] Added ID3 chapter support. --- .../exoplayer2/metadata/id3/ChapterFrame.java | 125 ++++++++++++++++++ .../metadata/id3/ChapterTOCFrame.java | 100 ++++++++++++++ .../exoplayer2/metadata/id3/Id3Decoder.java | 112 ++++++++++++++++ .../exoplayer2/metadata/id3/UrlLinkFrame.java | 87 ++++++++++++ 4 files changed, 424 insertions(+) create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java new file mode 100644 index 0000000000..291af3a8fe --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java @@ -0,0 +1,125 @@ +/* + * 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 com.google.android.exoplayer2.util.Util; + +/** + * Chapter information "CHAP" ID3 frame. + */ +public final class ChapterFrame extends Id3Frame { + + public static final String ID = "CHAP"; + + public final String chapterId; + public final int startTime; + public final int endTime; + public final int startOffset; + public final int endOffset; + public final String title; + public final String url; + public final ApicFrame image; + + public ChapterFrame(String chapterId, int startTime, int endTime, int startOffset, int endOffset, + String title, String url, ApicFrame image) { + 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; + } + + /* 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()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ChapterFrame other = (ChapterFrame) obj; + return Util.areEqual(chapterId, other.chapterId) + && startTime == other.startTime + && endTime == other.endTime + && startOffset == other.startOffset + && endOffset == other.endOffset + && title != null ? title.equals(other.title) : other.title == null + && url != null ? url.equals(other.url) : other.url == null + && image != null ? image.equals(other.image) : other.image == null; + } + + @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); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(chapterId); + dest.writeInt(startTime); + dest.writeInt(endTime); + dest.writeInt(startOffset); + dest.writeInt(endOffset); + dest.writeString(title); + dest.writeString(url); + dest.writeString(title); + dest.writeParcelable(this.image, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public ChapterFrame createFromParcel(Parcel in) { + return new ChapterFrame(in); + } + + @Override + public ChapterFrame[] newArray(int size) { + return new ChapterFrame[size]; + } + }; +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java new file mode 100644 index 0000000000..d93edc00c6 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java @@ -0,0 +1,100 @@ +/* + * 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 java.util.Arrays; + +/** + * Chapter table of contents information "CTOC" ID3 frame. + */ +public class ChapterTOCFrame extends Id3Frame { + + public static final String ID = "CTOC"; + + public final String elementId; + public final boolean isRoot; + public final boolean isOrdered; + public final String[] children; + public final String title; + + public ChapterTOCFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, String title) { + super(ID); + this.elementId = elementId; + this.isRoot = isRoot; + this.isOrdered = isOrdered; + this.children = children; + this.title = title; + } + + /* 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(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ChapterTOCFrame other = (ChapterTOCFrame) obj; + return elementId != null ? elementId.equals(other.elementId) : other.elementId == null + && isRoot == other.isRoot + && isOrdered == other.isOrdered + && Arrays.equals(children, other.children) + && title != null ? title.equals(other.title) : other.title == null; + } + + @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); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.elementId); + dest.writeByte((byte)(this.isRoot ? 1 : 0)); + dest.writeByte((byte)(this.isOrdered ? 1 : 0)); + dest.writeStringArray(this.children); + dest.writeString(this.title); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ChapterTOCFrame createFromParcel(Parcel in) { + return new ChapterTOCFrame(in); + } + + @Override + 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 810260e9e8..ece7d2dc58 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 @@ -282,6 +282,12 @@ public final class Id3Decoder implements MetadataDecoder { } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' && (frameId3 == 'M' || majorVersion == 2)) { frame = decodeCommentFrame(id3Data, frameSize); + } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' && frameId3 == 'X') { + frame = decodeUrlLinkFrame(id3Data, frameSize); + } else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') { + frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack); + } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { + frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack); } else { String id = majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) @@ -444,6 +450,112 @@ public final class Id3Decoder implements MetadataDecoder { return new TextInformationFrame(id, description); } + private static UrlLinkFrame decodeUrlLinkFrame(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); + + int urlStartIndex = descriptionEndIndex + 1; + int urlEndIndex = indexOfEos(data, urlStartIndex, encoding); + int urlLength = urlEndIndex - urlStartIndex; + String url = new String(data, urlStartIndex, urlLength, charset); + + return new UrlLinkFrame(description, url); + } + + private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack) throws UnsupportedEncodingException { + byte[] frameBytes = new byte[frameSize]; + id3Data.readBytes(frameBytes, 0, frameSize - 1); + + ParsableByteArray chapterData = new ParsableByteArray(frameBytes); + + 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; + + int frameHeaderSize = majorVersion == 2 ? 6 : 10; + while (chapterData.bytesLeft() >= frameHeaderSize) { + Id3Frame frame = decodeFrame(majorVersion, chapterData, unsignedIntFrameSizeHack); + if (frame == null) { + continue; + } + if (frame instanceof TextInformationFrame) { + TextInformationFrame textFrame = (TextInformationFrame)frame; + if (textFrame.id != null && textFrame.id.equals("TIT2")) { + title = textFrame.description; + } + } + else if (frame instanceof UrlLinkFrame) { + UrlLinkFrame linkFrame = (UrlLinkFrame)frame; + url = linkFrame.url; + } + else if (frame instanceof ApicFrame) { + image = (ApicFrame)frame; + } + } + + return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, title, url, image); + } + + private static ChapterTOCFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack) throws UnsupportedEncodingException { + byte[] frameBytes = new byte[frameSize]; + id3Data.readBytes(frameBytes, 0, frameSize - 1); + + ParsableByteArray tocData = new ParsableByteArray(frameBytes); + + 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 && tocData.bytesLeft() > 0; i++) { + int startIndex = tocData.getPosition(); + int endIndex = indexOfZeroByte(frameBytes, startIndex) + 1; + int stringLength = endIndex - startIndex; + String childId = tocData.readNullTerminatedString(stringLength); + children[i] = childId; + } + + String title = null; + int frameHeaderSize = majorVersion == 2 ? 6 : 10; + while (tocData.bytesLeft() >= frameHeaderSize) { + Id3Frame frame = decodeFrame(majorVersion, tocData, unsignedIntFrameSizeHack); + if (frame == null) { + continue; + } + if (frame instanceof TextInformationFrame) { + TextInformationFrame textFrame = (TextInformationFrame)frame; + if (textFrame.id != null && textFrame.id.equals("TIT2")) { + title = textFrame.description; + } + } + } + + return new ChapterTOCFrame(id, isRoot, isOrdered, children, title); + } + private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, String id) { byte[] frame = new byte[frameSize]; diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java new file mode 100644 index 0000000000..ca05c43d18 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java @@ -0,0 +1,87 @@ +/* + * 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; + +/** + * Url Frame "WXX" ID3 frame. + */ +public class UrlLinkFrame extends Id3Frame { + + public static final String ID = "WXXX"; + + public final String description; + public final String url; + + public UrlLinkFrame(String description, String url) { + super(ID); + this.description = description; + this.url = url; + } + + /* package */ UrlLinkFrame(Parcel in) { + super(ID); + description = in.readString(); + url = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + UrlLinkFrame other = (UrlLinkFrame) obj; + return Util.areEqual(description, other.description) + && Util.areEqual(url, other.url); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (url != null ? url.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(description); + dest.writeString(url); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public UrlLinkFrame createFromParcel(Parcel in) { + return new UrlLinkFrame(in); + } + + @Override + public UrlLinkFrame[] newArray(int size) { + return new UrlLinkFrame[size]; + } + + }; + +} From 5153e9e977e1425558a4135859c0f17621d534fd Mon Sep 17 00:00:00 2001 From: Philip Simpson Date: Tue, 17 Jan 2017 12:00:48 +1030 Subject: [PATCH 2/4] Improved ID3 chapter parsing code from feedback given. --- .../id3/{ChapterFrame.java => ChapFrame.java} | 34 +++++----- .../{ChapterTOCFrame.java => CtocFrame.java} | 40 ++++++------ .../exoplayer2/metadata/id3/Id3Decoder.java | 64 ++++++++++--------- .../id3/{UrlLinkFrame.java => WxxxFrame.java} | 22 +++---- 4 files changed, 84 insertions(+), 76 deletions(-) rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{ChapterFrame.java => ChapFrame.java} (75%) rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{ChapterTOCFrame.java => CtocFrame.java} (66%) rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{UrlLinkFrame.java => WxxxFrame.java} (76%) diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java similarity index 75% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java index 291af3a8fe..0a032b3d88 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -22,7 +22,7 @@ import com.google.android.exoplayer2.util.Util; /** * Chapter information "CHAP" ID3 frame. */ -public final class ChapterFrame extends Id3Frame { +public final class ChapFrame extends Id3Frame { public static final String ID = "CHAP"; @@ -35,8 +35,8 @@ public final class ChapterFrame extends Id3Frame { public final String url; public final ApicFrame image; - public ChapterFrame(String chapterId, int startTime, int endTime, int startOffset, int endOffset, - String title, String url, ApicFrame image) { + public ChapFrame(String chapterId, int startTime, int endTime, int startOffset, int endOffset, + String title, String url, ApicFrame image) { super(ID); this.chapterId = chapterId; this.startTime = startTime; @@ -48,7 +48,7 @@ public final class ChapterFrame extends Id3Frame { this.image = image; } - /* package */ ChapterFrame(Parcel in) { + /* package */ ChapFrame(Parcel in) { super(ID); this.chapterId = in.readString(); this.startTime = in.readInt(); @@ -68,15 +68,15 @@ public final class ChapterFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - ChapterFrame other = (ChapterFrame) obj; - return Util.areEqual(chapterId, other.chapterId) - && startTime == other.startTime + ChapFrame other = (ChapFrame) obj; + return startTime == other.startTime && endTime == other.endTime && startOffset == other.startOffset && endOffset == other.endOffset - && title != null ? title.equals(other.title) : other.title == null - && url != null ? url.equals(other.url) : other.url == null - && image != null ? image.equals(other.image) : other.image == null; + && Util.areEqual(chapterId, other.chapterId) + && Util.areEqual(title, other.title) + && Util.areEqual(url, other.url) + && Util.areEqual(image, other.image); } @Override @@ -103,7 +103,7 @@ public final class ChapterFrame extends Id3Frame { dest.writeString(title); dest.writeString(url); dest.writeString(title); - dest.writeParcelable(this.image, flags); + dest.writeParcelable(image, flags); } @Override @@ -111,15 +111,15 @@ public final class ChapterFrame extends Id3Frame { return 0; } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { @Override - public ChapterFrame createFromParcel(Parcel in) { - return new ChapterFrame(in); + public ChapFrame createFromParcel(Parcel in) { + return new ChapFrame(in); } @Override - public ChapterFrame[] newArray(int size) { - return new ChapterFrame[size]; + public ChapFrame[] newArray(int size) { + return new ChapFrame[size]; } }; } diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java similarity index 66% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java index d93edc00c6..1511763682 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/CtocFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -17,12 +17,14 @@ package com.google.android.exoplayer2.metadata.id3; import android.os.Parcel; +import com.google.android.exoplayer2.util.Util; + import java.util.Arrays; /** * Chapter table of contents information "CTOC" ID3 frame. */ -public class ChapterTOCFrame extends Id3Frame { +public final class CtocFrame extends Id3Frame { public static final String ID = "CTOC"; @@ -32,7 +34,7 @@ public class ChapterTOCFrame extends Id3Frame { public final String[] children; public final String title; - public ChapterTOCFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, String title) { + public CtocFrame(String elementId, boolean isRoot, boolean isOrdered, String[] children, String title) { super(ID); this.elementId = elementId; this.isRoot = isRoot; @@ -41,7 +43,7 @@ public class ChapterTOCFrame extends Id3Frame { this.title = title; } - /* package */ ChapterTOCFrame(Parcel in) { + /* package */ CtocFrame(Parcel in) { super(ID); this.elementId = in.readString(); this.isRoot = in.readByte() != 0; @@ -58,12 +60,12 @@ public class ChapterTOCFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - ChapterTOCFrame other = (ChapterTOCFrame) obj; - return elementId != null ? elementId.equals(other.elementId) : other.elementId == null - && isRoot == other.isRoot + CtocFrame other = (CtocFrame) obj; + return isRoot == other.isRoot && isOrdered == other.isOrdered - && Arrays.equals(children, other.children) - && title != null ? title.equals(other.title) : other.title == null; + && Util.areEqual(elementId, other.elementId) + && Util.areEqual(title, other.title) + && Arrays.equals(children, other.children); } @Override @@ -79,22 +81,22 @@ public class ChapterTOCFrame extends Id3Frame { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(this.elementId); - dest.writeByte((byte)(this.isRoot ? 1 : 0)); - dest.writeByte((byte)(this.isOrdered ? 1 : 0)); - dest.writeStringArray(this.children); - dest.writeString(this.title); + dest.writeString(elementId); + dest.writeByte((byte)(isRoot ? 1 : 0)); + dest.writeByte((byte)(isOrdered ? 1 : 0)); + dest.writeStringArray(children); + dest.writeString(title); } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { @Override - public ChapterTOCFrame createFromParcel(Parcel in) { - return new ChapterTOCFrame(in); + public CtocFrame createFromParcel(Parcel in) { + return new CtocFrame(in); } @Override - public ChapterTOCFrame[] newArray(int size) { - return new ChapterTOCFrame[size]; + public CtocFrame[] newArray(int size) { + return new CtocFrame[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 ece7d2dc58..48f2adaa06 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 @@ -84,7 +84,7 @@ public final class Id3Decoder implements MetadataDecoder { int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; while (id3Data.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack); + Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize); if (frame != null) { id3Frames.add(frame); } @@ -190,7 +190,7 @@ public final class Id3Decoder implements MetadataDecoder { } private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data, - boolean unsignedIntFrameSizeHack) { + boolean unsignedIntFrameSizeHack, int frameHeaderSize) { int frameId0 = id3Data.readUnsignedByte(); int frameId1 = id3Data.readUnsignedByte(); int frameId2 = id3Data.readUnsignedByte(); @@ -282,12 +282,15 @@ public final class Id3Decoder implements MetadataDecoder { } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' && (frameId3 == 'M' || majorVersion == 2)) { frame = decodeCommentFrame(id3Data, frameSize); - } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' && frameId3 == 'X') { - frame = decodeUrlLinkFrame(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 = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack); + frame = decodeChapFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize); } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { - frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack); + frame = decodeCtocFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, + frameHeaderSize); } else { String id = majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) @@ -450,7 +453,7 @@ public final class Id3Decoder implements MetadataDecoder { return new TextInformationFrame(id, description); } - private static UrlLinkFrame decodeUrlLinkFrame(ParsableByteArray id3Data, + private static WxxxFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); @@ -461,16 +464,21 @@ public final class Id3Decoder implements MetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - int urlStartIndex = descriptionEndIndex + 1; - int urlEndIndex = indexOfEos(data, urlStartIndex, encoding); - int urlLength = urlEndIndex - urlStartIndex; - String url = new String(data, urlStartIndex, urlLength, 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 UrlLinkFrame(description, url); + return new WxxxFrame(description, url); } - private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize, - int majorVersion, boolean unsignedIntFrameSizeHack) throws UnsupportedEncodingException { + private static ChapFrame decodeChapFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize) + throws UnsupportedEncodingException { byte[] frameBytes = new byte[frameSize]; id3Data.readBytes(frameBytes, 0, frameSize - 1); @@ -489,9 +497,9 @@ public final class Id3Decoder implements MetadataDecoder { String url = null; ApicFrame image = null; - int frameHeaderSize = majorVersion == 2 ? 6 : 10; while (chapterData.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(majorVersion, chapterData, unsignedIntFrameSizeHack); + Id3Frame frame = decodeFrame(majorVersion, chapterData, unsignedIntFrameSizeHack, + frameHeaderSize); if (frame == null) { continue; } @@ -501,8 +509,8 @@ public final class Id3Decoder implements MetadataDecoder { title = textFrame.description; } } - else if (frame instanceof UrlLinkFrame) { - UrlLinkFrame linkFrame = (UrlLinkFrame)frame; + else if (frame instanceof WxxxFrame) { + WxxxFrame linkFrame = (WxxxFrame)frame; url = linkFrame.url; } else if (frame instanceof ApicFrame) { @@ -510,11 +518,12 @@ public final class Id3Decoder implements MetadataDecoder { } } - return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, title, url, image); + return new ChapFrame(chapterId, startTime, endTime, startOffset, endOffset, title, url, image); } - private static ChapterTOCFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize, - int majorVersion, boolean unsignedIntFrameSizeHack) throws UnsupportedEncodingException { + private static CtocFrame decodeCtocFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize) + throws UnsupportedEncodingException { byte[] frameBytes = new byte[frameSize]; id3Data.readBytes(frameBytes, 0, frameSize - 1); @@ -530,7 +539,7 @@ public final class Id3Decoder implements MetadataDecoder { int entryCount = tocData.readUnsignedByte(); String[] children = new String[entryCount]; - for (int i = 0; i < entryCount && tocData.bytesLeft() > 0; i++) { + for (int i = 0; i < entryCount; i++) { int startIndex = tocData.getPosition(); int endIndex = indexOfZeroByte(frameBytes, startIndex) + 1; int stringLength = endIndex - startIndex; @@ -539,21 +548,18 @@ public final class Id3Decoder implements MetadataDecoder { } String title = null; - int frameHeaderSize = majorVersion == 2 ? 6 : 10; while (tocData.bytesLeft() >= frameHeaderSize) { - Id3Frame frame = decodeFrame(majorVersion, tocData, unsignedIntFrameSizeHack); - if (frame == null) { - continue; - } + Id3Frame frame = decodeFrame(majorVersion, tocData, unsignedIntFrameSizeHack, + frameHeaderSize); if (frame instanceof TextInformationFrame) { TextInformationFrame textFrame = (TextInformationFrame)frame; - if (textFrame.id != null && textFrame.id.equals("TIT2")) { + if ("TIT2".equals(textFrame.id)) { title = textFrame.description; } } } - return new ChapterTOCFrame(id, isRoot, isOrdered, children, title); + return new CtocFrame(id, isRoot, isOrdered, children, title); } private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java similarity index 76% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java rename to library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java index ca05c43d18..725e1d779a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/id3/WxxxFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -23,20 +23,20 @@ import com.google.android.exoplayer2.util.Util; /** * Url Frame "WXX" ID3 frame. */ -public class UrlLinkFrame extends Id3Frame { +public final class WxxxFrame extends Id3Frame { public static final String ID = "WXXX"; public final String description; public final String url; - public UrlLinkFrame(String description, String url) { + public WxxxFrame(String description, String url) { super(ID); this.description = description; this.url = url; } - /* package */ UrlLinkFrame(Parcel in) { + /* package */ WxxxFrame(Parcel in) { super(ID); description = in.readString(); url = in.readString(); @@ -50,7 +50,7 @@ public class UrlLinkFrame extends Id3Frame { if (obj == null || getClass() != obj.getClass()) { return false; } - UrlLinkFrame other = (UrlLinkFrame) obj; + WxxxFrame other = (WxxxFrame) obj; return Util.areEqual(description, other.description) && Util.areEqual(url, other.url); } @@ -69,17 +69,17 @@ public class UrlLinkFrame extends Id3Frame { dest.writeString(url); } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { @Override - public UrlLinkFrame createFromParcel(Parcel in) { - return new UrlLinkFrame(in); + public WxxxFrame createFromParcel(Parcel in) { + return new WxxxFrame(in); } @Override - public UrlLinkFrame[] newArray(int size) { - return new UrlLinkFrame[size]; + public WxxxFrame[] newArray(int size) { + return new WxxxFrame[size]; } }; From f6ecaddc881b6b7765cc19a93507d17e4f810443 Mon Sep 17 00:00:00 2001 From: Philip Simpson Date: Tue, 17 Jan 2017 12:03:18 +1030 Subject: [PATCH 3/4] Improved ID3 chapter parsing code from feedback given. --- .../com/google/android/exoplayer2/metadata/id3/Id3Decoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 48f2adaa06..4ce323d8ca 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 @@ -505,7 +505,7 @@ public final class Id3Decoder implements MetadataDecoder { } if (frame instanceof TextInformationFrame) { TextInformationFrame textFrame = (TextInformationFrame)frame; - if (textFrame.id != null && textFrame.id.equals("TIT2")) { + if ("TIT2".equals(textFrame.id)) { title = textFrame.description; } } From 5aff31c0615dd05cabb4a92b17d658aeff742da7 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 17 Jan 2017 15:29:07 +0000 Subject: [PATCH 4/4] Further improve ID3 parsing --- .../android/exoplayer2/demo/EventLogger.java | 13 +- .../google/android/exoplayer2/FormatTest.java | 4 +- .../metadata/id3/ChapterFrameTest.java | 43 +++ .../metadata/id3/ChapterTOCFrameTest.java | 45 ++++ .../metadata/id3/Id3DecoderTest.java | 10 +- .../extractor/mp4/MetadataUtil.java | 12 +- .../id3/{ChapFrame.java => ChapterFrame.java} | 68 +++-- .../{CtocFrame.java => ChapterTOCFrame.java} | 55 ++-- .../exoplayer2/metadata/id3/Id3Decoder.java | 254 +++++++++--------- .../metadata/id3/TextInformationFrame.java | 10 +- .../exoplayer2/metadata/id3/TxxxFrame.java | 84 ------ .../id3/{WxxxFrame.java => UrlLinkFrame.java} | 42 +-- 12 files changed, 327 insertions(+), 313 deletions(-) create mode 100644 library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java create mode 100644 library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTOCFrameTest.java rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{ChapFrame.java => ChapterFrame.java} (59%) rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{CtocFrame.java => ChapterTOCFrame.java} (59%) delete mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/id3/TxxxFrame.java rename library/src/main/java/com/google/android/exoplayer2/metadata/id3/{WxxxFrame.java => UrlLinkFrame.java} (66%) 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]; + } - }; + }; }