From f2f1058066af4f9160ad56a9724288afaa350a16 Mon Sep 17 00:00:00 2001 From: Philip Simpson Date: Mon, 16 Jan 2017 17:40:02 +1030 Subject: [PATCH 1/3] 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/3] 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/3] 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; } }