summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ApicFrame.java108
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java83
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java145
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java127
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/CommentFrame.java101
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/GeobFrame.java112
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java842
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Frame.java44
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/InternalFrame.java97
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/MlltFrame.java114
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/PrivFrame.java94
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java96
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java96
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/package-info.java19
14 files changed, 2078 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ApicFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
new file mode 100644
index 0000000000..f151707e4b
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ApicFrame.java
@@ -0,0 +1,108 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/**
+ * APIC (Attached Picture) ID3 frame.
+ */
+public final class ApicFrame extends Id3Frame {
+
+ public static final String ID = "APIC";
+
+ public final String mimeType;
+ @Nullable public final String description;
+ public final int pictureType;
+ public final byte[] pictureData;
+
+ public ApicFrame(
+ String mimeType, @Nullable String description, int pictureType, byte[] pictureData) {
+ super(ID);
+ this.mimeType = mimeType;
+ this.description = description;
+ this.pictureType = pictureType;
+ this.pictureData = pictureData;
+ }
+
+ /* package */ ApicFrame(Parcel in) {
+ super(ID);
+ mimeType = castNonNull(in.readString());
+ description = in.readString();
+ pictureType = in.readInt();
+ pictureData = castNonNull(in.createByteArray());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ApicFrame other = (ApicFrame) obj;
+ return pictureType == other.pictureType && Util.areEqual(mimeType, other.mimeType)
+ && Util.areEqual(description, other.description)
+ && Arrays.equals(pictureData, other.pictureData);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + pictureType;
+ result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
+ result = 31 * result + (description != null ? description.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(pictureData);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": mimeType=" + mimeType + ", description=" + description;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mimeType);
+ dest.writeString(description);
+ dest.writeInt(pictureType);
+ dest.writeByteArray(pictureData);
+ }
+
+ public static final Parcelable.Creator<ApicFrame> CREATOR = new Parcelable.Creator<ApicFrame>() {
+
+ @Override
+ public ApicFrame createFromParcel(Parcel in) {
+ return new ApicFrame(in);
+ }
+
+ @Override
+ public ApicFrame[] newArray(int size) {
+ return new ApicFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
new file mode 100644
index 0000000000..adc66ccdfe
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java
@@ -0,0 +1,83 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import java.util.Arrays;
+
+/**
+ * Binary ID3 frame.
+ */
+public final class BinaryFrame extends Id3Frame {
+
+ public final byte[] data;
+
+ public BinaryFrame(String id, byte[] data) {
+ super(id);
+ this.data = data;
+ }
+
+ /* package */ BinaryFrame(Parcel in) {
+ super(castNonNull(in.readString()));
+ data = castNonNull(in.createByteArray());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ BinaryFrame other = (BinaryFrame) obj;
+ return id.equals(other.id) && Arrays.equals(data, other.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + id.hashCode();
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeByteArray(data);
+ }
+
+ public static final Parcelable.Creator<BinaryFrame> CREATOR =
+ new Parcelable.Creator<BinaryFrame>() {
+
+ @Override
+ public BinaryFrame createFromParcel(Parcel in) {
+ return new BinaryFrame(in);
+ }
+
+ @Override
+ public BinaryFrame[] newArray(int size) {
+ return new BinaryFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java
new file mode 100644
index 0000000000..348781dddf
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java
@@ -0,0 +1,145 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/**
+ * Chapter information ID3 frame.
+ */
+public final class ChapterFrame extends Id3Frame {
+
+ public static final String ID = "CHAP";
+
+ public final String chapterId;
+ public final int startTimeMs;
+ public final int endTimeMs;
+ /**
+ * The byte offset of the start of the chapter, or {@link C#POSITION_UNSET} if not set.
+ */
+ public final long startOffset;
+ /**
+ * The byte offset of the end of the chapter, or {@link C#POSITION_UNSET} if not set.
+ */
+ public final long endOffset;
+ private final Id3Frame[] subFrames;
+
+ public ChapterFrame(String chapterId, int startTimeMs, int endTimeMs, long startOffset,
+ long endOffset, Id3Frame[] subFrames) {
+ super(ID);
+ this.chapterId = chapterId;
+ this.startTimeMs = startTimeMs;
+ this.endTimeMs = endTimeMs;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.subFrames = subFrames;
+ }
+
+ /* package */ ChapterFrame(Parcel in) {
+ super(ID);
+ this.chapterId = castNonNull(in.readString());
+ this.startTimeMs = in.readInt();
+ this.endTimeMs = in.readInt();
+ this.startOffset = in.readLong();
+ this.endOffset = in.readLong();
+ int subFrameCount = in.readInt();
+ subFrames = new Id3Frame[subFrameCount];
+ for (int i = 0; i < subFrameCount; i++) {
+ subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader());
+ }
+ }
+
+ /**
+ * Returns the number of sub-frames.
+ */
+ public int getSubFrameCount() {
+ return subFrames.length;
+ }
+
+ /**
+ * Returns the sub-frame at {@code index}.
+ */
+ public Id3Frame getSubFrame(int index) {
+ return subFrames[index];
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ChapterFrame other = (ChapterFrame) obj;
+ return startTimeMs == other.startTimeMs
+ && endTimeMs == other.endTimeMs
+ && 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 + startTimeMs;
+ result = 31 * result + endTimeMs;
+ result = 31 * result + (int) startOffset;
+ result = 31 * result + (int) endOffset;
+ result = 31 * result + (chapterId != null ? chapterId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(chapterId);
+ dest.writeInt(startTimeMs);
+ dest.writeInt(endTimeMs);
+ dest.writeLong(startOffset);
+ dest.writeLong(endOffset);
+ dest.writeInt(subFrames.length);
+ for (Id3Frame subFrame : subFrames) {
+ dest.writeParcelable(subFrame, 0);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ChapterFrame> CREATOR = new Creator<ChapterFrame>() {
+
+ @Override
+ public ChapterFrame createFromParcel(Parcel in) {
+ return new ChapterFrame(in);
+ }
+
+ @Override
+ public ChapterFrame[] newArray(int size) {
+ return new ChapterFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java
new file mode 100644
index 0000000000..9451151c16
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java
@@ -0,0 +1,127 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/**
+ * Chapter table of contents ID3 frame.
+ */
+public final 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;
+ private final Id3Frame[] subFrames;
+
+ 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.subFrames = subFrames;
+ }
+
+ /* package */
+ ChapterTocFrame(Parcel in) {
+ super(ID);
+ this.elementId = castNonNull(in.readString());
+ this.isRoot = in.readByte() != 0;
+ this.isOrdered = in.readByte() != 0;
+ this.children = castNonNull(in.createStringArray());
+ int subFrameCount = in.readInt();
+ subFrames = new Id3Frame[subFrameCount];
+ for (int i = 0; i < subFrameCount; i++) {
+ subFrames[i] = in.readParcelable(Id3Frame.class.getClassLoader());
+ }
+ }
+
+ /**
+ * Returns the number of sub-frames.
+ */
+ public int getSubFrameCount() {
+ return subFrames.length;
+ }
+
+ /**
+ * Returns the sub-frame at {@code index}.
+ */
+ public Id3Frame getSubFrame(int index) {
+ return subFrames[index];
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ChapterTocFrame other = (ChapterTocFrame) obj;
+ return isRoot == other.isRoot
+ && 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 + (isRoot ? 1 : 0);
+ result = 31 * result + (isOrdered ? 1 : 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.writeStringArray(children);
+ dest.writeInt(subFrames.length);
+ for (Id3Frame subFrame : subFrames) {
+ dest.writeParcelable(subFrame, 0);
+ }
+ }
+
+ public static final Creator<ChapterTocFrame> CREATOR = new Creator<ChapterTocFrame>() {
+
+ @Override
+ public ChapterTocFrame createFromParcel(Parcel in) {
+ return new ChapterTocFrame(in);
+ }
+
+ @Override
+ public ChapterTocFrame[] newArray(int size) {
+ return new ChapterTocFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/CommentFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
new file mode 100644
index 0000000000..98b8c79a96
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/CommentFrame.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+
+/**
+ * Comment ID3 frame.
+ */
+public final class CommentFrame extends Id3Frame {
+
+ public static final String ID = "COMM";
+
+ public final String language;
+ public final String description;
+ public final String text;
+
+ public CommentFrame(String language, String description, String text) {
+ super(ID);
+ this.language = language;
+ this.description = description;
+ this.text = text;
+ }
+
+ /* package */ CommentFrame(Parcel in) {
+ super(ID);
+ language = castNonNull(in.readString());
+ description = castNonNull(in.readString());
+ text = castNonNull(in.readString());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ CommentFrame other = (CommentFrame) obj;
+ return Util.areEqual(description, other.description) && Util.areEqual(language, other.language)
+ && Util.areEqual(text, other.text);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (language != null ? language.hashCode() : 0);
+ result = 31 * result + (description != null ? description.hashCode() : 0);
+ result = 31 * result + (text != null ? text.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": language=" + language + ", description=" + description;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(language);
+ dest.writeString(text);
+ }
+
+ public static final Parcelable.Creator<CommentFrame> CREATOR =
+ new Parcelable.Creator<CommentFrame>() {
+
+ @Override
+ public CommentFrame createFromParcel(Parcel in) {
+ return new CommentFrame(in);
+ }
+
+ @Override
+ public CommentFrame[] newArray(int size) {
+ return new CommentFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/GeobFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
new file mode 100644
index 0000000000..58a208a76a
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/GeobFrame.java
@@ -0,0 +1,112 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/**
+ * GEOB (General Encapsulated Object) ID3 frame.
+ */
+public final class GeobFrame extends Id3Frame {
+
+ public static final String ID = "GEOB";
+
+ public final String mimeType;
+ public final String filename;
+ public final String description;
+ public final byte[] data;
+
+ public GeobFrame(String mimeType, String filename, String description, byte[] data) {
+ super(ID);
+ this.mimeType = mimeType;
+ this.filename = filename;
+ this.description = description;
+ this.data = data;
+ }
+
+ /* package */ GeobFrame(Parcel in) {
+ super(ID);
+ mimeType = castNonNull(in.readString());
+ filename = castNonNull(in.readString());
+ description = castNonNull(in.readString());
+ data = castNonNull(in.createByteArray());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ GeobFrame other = (GeobFrame) obj;
+ return Util.areEqual(mimeType, other.mimeType) && Util.areEqual(filename, other.filename)
+ && Util.areEqual(description, other.description) && Arrays.equals(data, other.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
+ result = 31 * result + (filename != null ? filename.hashCode() : 0);
+ result = 31 * result + (description != null ? description.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return id
+ + ": mimeType="
+ + mimeType
+ + ", filename="
+ + filename
+ + ", description="
+ + description;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mimeType);
+ dest.writeString(filename);
+ dest.writeString(description);
+ dest.writeByteArray(data);
+ }
+
+ public static final Parcelable.Creator<GeobFrame> CREATOR = new Parcelable.Creator<GeobFrame>() {
+
+ @Override
+ public GeobFrame createFromParcel(Parcel in) {
+ return new GeobFrame(in);
+ }
+
+ @Override
+ public GeobFrame[] newArray(int size) {
+ return new GeobFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
new file mode 100644
index 0000000000..36e004ed52
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
@@ -0,0 +1,842 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.Metadata;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.MetadataDecoder;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.MetadataInputBuffer;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableBitArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Decodes ID3 tags.
+ */
+public final class Id3Decoder implements MetadataDecoder {
+
+ /**
+ * A predicate for determining whether individual frames should be decoded.
+ */
+ public interface FramePredicate {
+
+ /**
+ * Returns whether a frame with the specified parameters should be decoded.
+ *
+ * @param majorVersion The major version of the ID3 tag.
+ * @param id0 The first byte of the frame ID.
+ * @param id1 The second byte of the frame ID.
+ * @param id2 The third byte of the frame ID.
+ * @param id3 The fourth byte of the frame ID.
+ * @return Whether the frame should be decoded.
+ */
+ boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3);
+
+ }
+
+ /** A predicate that indicates no frames should be decoded. */
+ public static final FramePredicate NO_FRAMES_PREDICATE =
+ (majorVersion, id0, id1, id2, id3) -> false;
+
+ private static final String TAG = "Id3Decoder";
+
+ /** The first three bytes of a well formed ID3 tag header. */
+ public static final int ID3_TAG = 0x00494433;
+ /**
+ * Length of an ID3 tag header.
+ */
+ public static final int ID3_HEADER_LENGTH = 10;
+
+ private static final int FRAME_FLAG_V3_IS_COMPRESSED = 0x0080;
+ private static final int FRAME_FLAG_V3_IS_ENCRYPTED = 0x0040;
+ private static final int FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER = 0x0020;
+ private static final int FRAME_FLAG_V4_IS_COMPRESSED = 0x0008;
+ private static final int FRAME_FLAG_V4_IS_ENCRYPTED = 0x0004;
+ private static final int FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER = 0x0040;
+ private static final int FRAME_FLAG_V4_IS_UNSYNCHRONIZED = 0x0002;
+ private static final int FRAME_FLAG_V4_HAS_DATA_LENGTH = 0x0001;
+
+ private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0;
+ private static final int ID3_TEXT_ENCODING_UTF_16 = 1;
+ private static final int ID3_TEXT_ENCODING_UTF_16BE = 2;
+ private static final int ID3_TEXT_ENCODING_UTF_8 = 3;
+
+ @Nullable private final FramePredicate framePredicate;
+
+ public Id3Decoder() {
+ this(null);
+ }
+
+ /**
+ * @param framePredicate Determines which frames are decoded. May be null to decode all frames.
+ */
+ public Id3Decoder(@Nullable FramePredicate framePredicate) {
+ this.framePredicate = framePredicate;
+ }
+
+ @SuppressWarnings("ByteBufferBackingArray")
+ @Override
+ @Nullable
+ public Metadata decode(MetadataInputBuffer inputBuffer) {
+ ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
+ return decode(buffer.array(), buffer.limit());
+ }
+
+ /**
+ * Decodes ID3 tags.
+ *
+ * @param data The bytes to decode ID3 tags from.
+ * @param size Amount of bytes in {@code data} to read.
+ * @return A {@link Metadata} object containing the decoded ID3 tags, or null if the data could
+ * not be decoded.
+ */
+ @Nullable
+ public Metadata decode(byte[] data, int size) {
+ List<Id3Frame> id3Frames = new ArrayList<>();
+ ParsableByteArray id3Data = new ParsableByteArray(data, size);
+
+ Id3Header id3Header = decodeHeader(id3Data);
+ if (id3Header == null) {
+ return null;
+ }
+
+ int startPosition = id3Data.getPosition();
+ int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10;
+ int framesSize = id3Header.framesSize;
+ if (id3Header.isUnsynchronized) {
+ framesSize = removeUnsynchronization(id3Data, id3Header.framesSize);
+ }
+ id3Data.setLimit(startPosition + framesSize);
+
+ boolean unsignedIntFrameSizeHack = false;
+ if (!validateFrames(id3Data, id3Header.majorVersion, frameHeaderSize, false)) {
+ if (id3Header.majorVersion == 4 && validateFrames(id3Data, 4, frameHeaderSize, true)) {
+ unsignedIntFrameSizeHack = true;
+ } else {
+ Log.w(TAG, "Failed to validate ID3 tag with majorVersion=" + id3Header.majorVersion);
+ return null;
+ }
+ }
+
+ while (id3Data.bytesLeft() >= frameHeaderSize) {
+ Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack,
+ frameHeaderSize, framePredicate);
+ if (frame != null) {
+ id3Frames.add(frame);
+ }
+ }
+
+ return new Metadata(id3Frames);
+ }
+
+ /**
+ * @param data A {@link ParsableByteArray} from which the header should be read.
+ * @return The parsed header, or null if the ID3 tag is unsupported.
+ */
+ @Nullable
+ private static Id3Header decodeHeader(ParsableByteArray data) {
+ if (data.bytesLeft() < ID3_HEADER_LENGTH) {
+ Log.w(TAG, "Data too short to be an ID3 tag");
+ return null;
+ }
+
+ int id = data.readUnsignedInt24();
+ if (id != ID3_TAG) {
+ Log.w(TAG, "Unexpected first three bytes of ID3 tag header: 0x" + String.format("%06X", id));
+ return null;
+ }
+
+ int majorVersion = data.readUnsignedByte();
+ data.skipBytes(1); // Skip minor version.
+ int flags = data.readUnsignedByte();
+ int framesSize = data.readSynchSafeInt();
+
+ if (majorVersion == 2) {
+ boolean isCompressed = (flags & 0x40) != 0;
+ if (isCompressed) {
+ Log.w(TAG, "Skipped ID3 tag with majorVersion=2 and undefined compression scheme");
+ return null;
+ }
+ } else if (majorVersion == 3) {
+ boolean hasExtendedHeader = (flags & 0x40) != 0;
+ if (hasExtendedHeader) {
+ int extendedHeaderSize = data.readInt(); // Size excluding size field.
+ data.skipBytes(extendedHeaderSize);
+ framesSize -= (extendedHeaderSize + 4);
+ }
+ } else if (majorVersion == 4) {
+ boolean hasExtendedHeader = (flags & 0x40) != 0;
+ if (hasExtendedHeader) {
+ int extendedHeaderSize = data.readSynchSafeInt(); // Size including size field.
+ data.skipBytes(extendedHeaderSize - 4);
+ framesSize -= extendedHeaderSize;
+ }
+ boolean hasFooter = (flags & 0x10) != 0;
+ if (hasFooter) {
+ framesSize -= 10;
+ }
+ } else {
+ Log.w(TAG, "Skipped ID3 tag with unsupported majorVersion=" + majorVersion);
+ return null;
+ }
+
+ // isUnsynchronized is advisory only in version 4. Frame level flags are used instead.
+ boolean isUnsynchronized = majorVersion < 4 && (flags & 0x80) != 0;
+ return new Id3Header(majorVersion, isUnsynchronized, framesSize);
+ }
+
+ private static boolean validateFrames(ParsableByteArray id3Data, int majorVersion,
+ int frameHeaderSize, boolean unsignedIntFrameSizeHack) {
+ int startPosition = id3Data.getPosition();
+ try {
+ while (id3Data.bytesLeft() >= frameHeaderSize) {
+ // Read the next frame header.
+ int id;
+ long frameSize;
+ int flags;
+ if (majorVersion >= 3) {
+ id = id3Data.readInt();
+ frameSize = id3Data.readUnsignedInt();
+ flags = id3Data.readUnsignedShort();
+ } else {
+ id = id3Data.readUnsignedInt24();
+ frameSize = id3Data.readUnsignedInt24();
+ flags = 0;
+ }
+ // Validate the frame header and skip to the next one.
+ if (id == 0 && frameSize == 0 && flags == 0) {
+ // We've reached zero padding after the end of the final frame.
+ return true;
+ } else {
+ if (majorVersion == 4 && !unsignedIntFrameSizeHack) {
+ // Parse the data size as a synchsafe integer, as per the spec.
+ if ((frameSize & 0x808080L) != 0) {
+ return false;
+ }
+ frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7)
+ | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21);
+ }
+ boolean hasGroupIdentifier = false;
+ boolean hasDataLength = false;
+ if (majorVersion == 4) {
+ hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0;
+ hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0;
+ } else if (majorVersion == 3) {
+ hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0;
+ // A V3 frame has data length if and only if it's compressed.
+ hasDataLength = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0;
+ }
+ int minimumFrameSize = 0;
+ if (hasGroupIdentifier) {
+ minimumFrameSize++;
+ }
+ if (hasDataLength) {
+ minimumFrameSize += 4;
+ }
+ if (frameSize < minimumFrameSize) {
+ return false;
+ }
+ if (id3Data.bytesLeft() < frameSize) {
+ return false;
+ }
+ id3Data.skipBytes((int) frameSize); // flags
+ }
+ }
+ return true;
+ } finally {
+ id3Data.setPosition(startPosition);
+ }
+ }
+
+ @Nullable
+ private static Id3Frame decodeFrame(
+ int majorVersion,
+ ParsableByteArray id3Data,
+ boolean unsignedIntFrameSizeHack,
+ int frameHeaderSize,
+ @Nullable FramePredicate framePredicate) {
+ int frameId0 = id3Data.readUnsignedByte();
+ int frameId1 = id3Data.readUnsignedByte();
+ int frameId2 = id3Data.readUnsignedByte();
+ int frameId3 = majorVersion >= 3 ? id3Data.readUnsignedByte() : 0;
+
+ int frameSize;
+ if (majorVersion == 4) {
+ frameSize = id3Data.readUnsignedIntToInt();
+ if (!unsignedIntFrameSizeHack) {
+ frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7)
+ | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21);
+ }
+ } else if (majorVersion == 3) {
+ frameSize = id3Data.readUnsignedIntToInt();
+ } else /* id3Header.majorVersion == 2 */ {
+ frameSize = id3Data.readUnsignedInt24();
+ }
+
+ int flags = majorVersion >= 3 ? id3Data.readUnsignedShort() : 0;
+ if (frameId0 == 0 && frameId1 == 0 && frameId2 == 0 && frameId3 == 0 && frameSize == 0
+ && flags == 0) {
+ // We must be reading zero padding at the end of the tag.
+ id3Data.setPosition(id3Data.limit());
+ return null;
+ }
+
+ int nextFramePosition = id3Data.getPosition() + frameSize;
+ if (nextFramePosition > id3Data.limit()) {
+ Log.w(TAG, "Frame size exceeds remaining tag data");
+ id3Data.setPosition(id3Data.limit());
+ return null;
+ }
+
+ if (framePredicate != null
+ && !framePredicate.evaluate(majorVersion, frameId0, frameId1, frameId2, frameId3)) {
+ // Filtered by the predicate.
+ id3Data.setPosition(nextFramePosition);
+ return null;
+ }
+
+ // Frame flags.
+ boolean isCompressed = false;
+ boolean isEncrypted = false;
+ boolean isUnsynchronized = false;
+ boolean hasDataLength = false;
+ boolean hasGroupIdentifier = false;
+ if (majorVersion == 3) {
+ isCompressed = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0;
+ isEncrypted = (flags & FRAME_FLAG_V3_IS_ENCRYPTED) != 0;
+ hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0;
+ // A V3 frame has data length if and only if it's compressed.
+ hasDataLength = isCompressed;
+ } else if (majorVersion == 4) {
+ hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0;
+ isCompressed = (flags & FRAME_FLAG_V4_IS_COMPRESSED) != 0;
+ isEncrypted = (flags & FRAME_FLAG_V4_IS_ENCRYPTED) != 0;
+ isUnsynchronized = (flags & FRAME_FLAG_V4_IS_UNSYNCHRONIZED) != 0;
+ hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0;
+ }
+
+ if (isCompressed || isEncrypted) {
+ Log.w(TAG, "Skipping unsupported compressed or encrypted frame");
+ id3Data.setPosition(nextFramePosition);
+ return null;
+ }
+
+ if (hasGroupIdentifier) {
+ frameSize--;
+ id3Data.skipBytes(1);
+ }
+ if (hasDataLength) {
+ frameSize -= 4;
+ id3Data.skipBytes(4);
+ }
+ if (isUnsynchronized) {
+ frameSize = removeUnsynchronization(id3Data, frameSize);
+ }
+
+ try {
+ Id3Frame frame;
+ if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X'
+ && (majorVersion == 2 || frameId3 == 'X')) {
+ frame = decodeTxxxFrame(id3Data, frameSize);
+ } else if (frameId0 == 'T') {
+ String id = getFrameId(majorVersion, 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 = getFrameId(majorVersion, 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'
+ && (frameId3 == 'B' || majorVersion == 2)) {
+ frame = decodeGeobFrame(id3Data, frameSize);
+ } 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 == 'C' && frameId1 == 'O' && frameId2 == 'M'
+ && (frameId3 == 'M' || majorVersion == 2)) {
+ frame = decodeCommentFrame(id3Data, frameSize);
+ } else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') {
+ frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
+ frameHeaderSize, framePredicate);
+ } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') {
+ frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
+ frameHeaderSize, framePredicate);
+ } else if (frameId0 == 'M' && frameId1 == 'L' && frameId2 == 'L' && frameId3 == 'T') {
+ frame = decodeMlltFrame(id3Data, frameSize);
+ } else {
+ String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3);
+ frame = decodeBinaryFrame(id3Data, frameSize, id);
+ }
+ if (frame == null) {
+ Log.w(TAG, "Failed to decode frame: id="
+ + getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3) + ", frameSize="
+ + frameSize);
+ }
+ return frame;
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, "Unsupported character encoding");
+ return null;
+ } finally {
+ id3Data.setPosition(nextFramePosition);
+ }
+ }
+
+ @Nullable
+ private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize)
+ throws UnsupportedEncodingException {
+ if (frameSize < 1) {
+ // Frame is malformed.
+ return null;
+ }
+
+ 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 valueStartIndex = descriptionEndIndex + delimiterLength(encoding);
+ int valueEndIndex = indexOfEos(data, valueStartIndex, encoding);
+ String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset);
+
+ return new TextInformationFrame("TXXX", description, value);
+ }
+
+ @Nullable
+ private static TextInformationFrame decodeTextInformationFrame(
+ ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException {
+ if (frameSize < 1) {
+ // Frame is malformed.
+ return 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);
+ }
+
+ @Nullable
+ private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize)
+ throws UnsupportedEncodingException {
+ if (frameSize < 1) {
+ // Frame is malformed.
+ return null;
+ }
+
+ 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 + delimiterLength(encoding);
+ int urlEndIndex = indexOfZeroByte(data, urlStartIndex);
+ String url = decodeStringIfValid(data, urlStartIndex, urlEndIndex, "ISO-8859-1");
+
+ return new UrlLinkFrame("WXXX", description, url);
+ }
+
+ private static UrlLinkFrame decodeUrlLinkFrame(ParsableByteArray id3Data, int frameSize,
+ String id) throws UnsupportedEncodingException {
+ 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)
+ throws UnsupportedEncodingException {
+ byte[] data = new byte[frameSize];
+ id3Data.readBytes(data, 0, frameSize);
+
+ int ownerEndIndex = indexOfZeroByte(data, 0);
+ String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1");
+
+ int privateDataStartIndex = ownerEndIndex + 1;
+ byte[] privateData = copyOfRangeIfValid(data, privateDataStartIndex, data.length);
+
+ return new PrivFrame(owner, privateData);
+ }
+
+ private static GeobFrame decodeGeobFrame(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 mimeTypeEndIndex = indexOfZeroByte(data, 0);
+ String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1");
+
+ int filenameStartIndex = mimeTypeEndIndex + 1;
+ int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding);
+ String filename = decodeStringIfValid(data, filenameStartIndex, filenameEndIndex, charset);
+
+ int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding);
+ int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding);
+ String description =
+ decodeStringIfValid(data, descriptionStartIndex, descriptionEndIndex, charset);
+
+ int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding);
+ byte[] objectData = copyOfRangeIfValid(data, objectDataStartIndex, data.length);
+
+ return new GeobFrame(mimeType, filename, description, objectData);
+ }
+
+ private static ApicFrame decodeApicFrame(ParsableByteArray id3Data, int frameSize,
+ int majorVersion) throws UnsupportedEncodingException {
+ int encoding = id3Data.readUnsignedByte();
+ String charset = getCharsetName(encoding);
+
+ byte[] data = new byte[frameSize - 1];
+ id3Data.readBytes(data, 0, frameSize - 1);
+
+ String mimeType;
+ int mimeTypeEndIndex;
+ if (majorVersion == 2) {
+ mimeTypeEndIndex = 2;
+ mimeType = "image/" + Util.toLowerInvariant(new String(data, 0, 3, "ISO-8859-1"));
+ if ("image/jpg".equals(mimeType)) {
+ mimeType = "image/jpeg";
+ }
+ } else {
+ mimeTypeEndIndex = indexOfZeroByte(data, 0);
+ mimeType = Util.toLowerInvariant(new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"));
+ if (mimeType.indexOf('/') == -1) {
+ mimeType = "image/" + mimeType;
+ }
+ }
+
+ int pictureType = data[mimeTypeEndIndex + 1] & 0xFF;
+
+ int descriptionStartIndex = mimeTypeEndIndex + 2;
+ int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding);
+ String description = new String(data, descriptionStartIndex,
+ descriptionEndIndex - descriptionStartIndex, charset);
+
+ int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding);
+ byte[] pictureData = copyOfRangeIfValid(data, pictureDataStartIndex, data.length);
+
+ return new ApicFrame(mimeType, description, pictureType, pictureData);
+ }
+
+ @Nullable
+ private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize)
+ throws UnsupportedEncodingException {
+ if (frameSize < 4) {
+ // Frame is malformed.
+ return null;
+ }
+
+ int encoding = id3Data.readUnsignedByte();
+ String charset = getCharsetName(encoding);
+
+ byte[] data = new byte[3];
+ id3Data.readBytes(data, 0, 3);
+ String language = new String(data, 0, 3);
+
+ data = new byte[frameSize - 4];
+ id3Data.readBytes(data, 0, frameSize - 4);
+
+ int descriptionEndIndex = indexOfEos(data, 0, encoding);
+ String description = new String(data, 0, descriptionEndIndex, charset);
+
+ int textStartIndex = descriptionEndIndex + delimiterLength(encoding);
+ int textEndIndex = indexOfEos(data, textStartIndex, encoding);
+ String text = decodeStringIfValid(data, textStartIndex, textEndIndex, charset);
+
+ return new CommentFrame(language, description, text);
+ }
+
+ private static ChapterFrame decodeChapterFrame(
+ ParsableByteArray id3Data,
+ int frameSize,
+ int majorVersion,
+ boolean unsignedIntFrameSizeHack,
+ int frameHeaderSize,
+ @Nullable FramePredicate framePredicate)
+ throws UnsupportedEncodingException {
+ 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);
+
+ int startTime = id3Data.readInt();
+ int endTime = id3Data.readInt();
+ long startOffset = id3Data.readUnsignedInt();
+ if (startOffset == 0xFFFFFFFFL) {
+ startOffset = C.POSITION_UNSET;
+ }
+ long endOffset = id3Data.readUnsignedInt();
+ if (endOffset == 0xFFFFFFFFL) {
+ endOffset = C.POSITION_UNSET;
+ }
+
+ ArrayList<Id3Frame> subFrames = new ArrayList<>();
+ int limit = framePosition + frameSize;
+ while (id3Data.getPosition() < limit) {
+ Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
+ frameHeaderSize, framePredicate);
+ if (frame != null) {
+ subFrames.add(frame);
+ }
+ }
+
+ Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()];
+ subFrames.toArray(subFrameArray);
+ return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, subFrameArray);
+ }
+
+ private static ChapterTocFrame decodeChapterTOCFrame(
+ ParsableByteArray id3Data,
+ int frameSize,
+ int majorVersion,
+ boolean unsignedIntFrameSizeHack,
+ int frameHeaderSize,
+ @Nullable FramePredicate framePredicate)
+ throws UnsupportedEncodingException {
+ 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);
+
+ int ctocFlags = id3Data.readUnsignedByte();
+ boolean isRoot = (ctocFlags & 0x0002) != 0;
+ boolean isOrdered = (ctocFlags & 0x0001) != 0;
+
+ 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");
+ id3Data.setPosition(endIndex + 1);
+ }
+
+ ArrayList<Id3Frame> subFrames = new ArrayList<>();
+ int limit = framePosition + frameSize;
+ while (id3Data.getPosition() < limit) {
+ Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
+ frameHeaderSize, framePredicate);
+ if (frame != null) {
+ subFrames.add(frame);
+ }
+ }
+
+ Id3Frame[] subFrameArray = new Id3Frame[subFrames.size()];
+ subFrames.toArray(subFrameArray);
+ return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray);
+ }
+
+ private static MlltFrame decodeMlltFrame(ParsableByteArray id3Data, int frameSize) {
+ // See ID3v2.4.0 native frames subsection 4.6.
+ int mpegFramesBetweenReference = id3Data.readUnsignedShort();
+ int bytesBetweenReference = id3Data.readUnsignedInt24();
+ int millisecondsBetweenReference = id3Data.readUnsignedInt24();
+ int bitsForBytesDeviation = id3Data.readUnsignedByte();
+ int bitsForMillisecondsDeviation = id3Data.readUnsignedByte();
+
+ ParsableBitArray references = new ParsableBitArray();
+ references.reset(id3Data);
+ int referencesBits = 8 * (frameSize - 10);
+ int bitsPerReference = bitsForBytesDeviation + bitsForMillisecondsDeviation;
+ int referencesCount = referencesBits / bitsPerReference;
+ int[] bytesDeviations = new int[referencesCount];
+ int[] millisecondsDeviations = new int[referencesCount];
+ for (int i = 0; i < referencesCount; i++) {
+ int bytesDeviation = references.readBits(bitsForBytesDeviation);
+ int millisecondsDeviation = references.readBits(bitsForMillisecondsDeviation);
+ bytesDeviations[i] = bytesDeviation;
+ millisecondsDeviations[i] = millisecondsDeviation;
+ }
+
+ return new MlltFrame(
+ mpegFramesBetweenReference,
+ bytesBetweenReference,
+ millisecondsBetweenReference,
+ bytesDeviations,
+ millisecondsDeviations);
+ }
+
+ private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize,
+ String id) {
+ byte[] frame = new byte[frameSize];
+ id3Data.readBytes(frame, 0, frameSize);
+
+ return new BinaryFrame(id, frame);
+ }
+
+ /**
+ * 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.
+ * @return The length of the data after processing.
+ */
+ private static int removeUnsynchronization(ParsableByteArray data, int length) {
+ byte[] bytes = data.data;
+ int startPosition = data.getPosition();
+ for (int i = startPosition; i + 1 < startPosition + length; i++) {
+ if ((bytes[i] & 0xFF) == 0xFF && bytes[i + 1] == 0x00) {
+ int relativePosition = i - startPosition;
+ System.arraycopy(bytes, i + 2, bytes, i + 1, length - relativePosition - 2);
+ length--;
+ }
+ }
+ return length;
+ }
+
+ /**
+ * Maps encoding byte from ID3v2 frame to a Charset.
+ *
+ * @param encodingByte The value of encoding byte from ID3v2 frame.
+ * @return Charset name.
+ */
+ private static String getCharsetName(int encodingByte) {
+ switch (encodingByte) {
+ case ID3_TEXT_ENCODING_UTF_16:
+ return "UTF-16";
+ case ID3_TEXT_ENCODING_UTF_16BE:
+ return "UTF-16BE";
+ case ID3_TEXT_ENCODING_UTF_8:
+ return "UTF-8";
+ case ID3_TEXT_ENCODING_ISO_8859_1:
+ default:
+ return "ISO-8859-1";
+ }
+ }
+
+ private static String getFrameId(int majorVersion, int frameId0, int frameId1, int frameId2,
+ int frameId3) {
+ return majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2)
+ : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3);
+ }
+
+ private static int indexOfEos(byte[] data, int fromIndex, int encoding) {
+ int terminationPos = indexOfZeroByte(data, fromIndex);
+
+ // For single byte encoding charsets, we're done.
+ if (encoding == ID3_TEXT_ENCODING_ISO_8859_1 || encoding == ID3_TEXT_ENCODING_UTF_8) {
+ return terminationPos;
+ }
+
+ // Otherwise ensure an even index and look for a second zero byte.
+ while (terminationPos < data.length - 1) {
+ if (terminationPos % 2 == 0 && data[terminationPos + 1] == (byte) 0) {
+ return terminationPos;
+ }
+ terminationPos = indexOfZeroByte(data, terminationPos + 1);
+ }
+
+ return data.length;
+ }
+
+ private static int indexOfZeroByte(byte[] data, int fromIndex) {
+ for (int i = fromIndex; i < data.length; i++) {
+ if (data[i] == (byte) 0) {
+ return i;
+ }
+ }
+ return data.length;
+ }
+
+ private static int delimiterLength(int encodingByte) {
+ return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8)
+ ? 1 : 2;
+ }
+
+ /**
+ * Copies the specified range of an array, or returns a zero length array if the range is invalid.
+ *
+ * @param data The array from which to copy.
+ * @param from The start of the range to copy (inclusive).
+ * @param to The end of the range to copy (exclusive).
+ * @return The copied data, or a zero length array if the range is invalid.
+ */
+ private static byte[] copyOfRangeIfValid(byte[] data, int from, int to) {
+ if (to <= from) {
+ // Invalid or zero length range.
+ return Util.EMPTY_BYTE_ARRAY;
+ }
+ return Arrays.copyOfRange(data, from, to);
+ }
+
+ /**
+ * Returns a string obtained by decoding the specified range of {@code data} using the specified
+ * {@code charsetName}. An empty string is returned if the range is invalid.
+ *
+ * @param data The array from which to decode the string.
+ * @param from The start of the range.
+ * @param to The end of the range (exclusive).
+ * @param charsetName The name of the Charset to use.
+ * @return The decoded string, or an empty string if the range is invalid.
+ * @throws UnsupportedEncodingException If the Charset is not supported.
+ */
+ private static String decodeStringIfValid(byte[] data, int from, int to, String charsetName)
+ throws UnsupportedEncodingException {
+ if (to <= from || to > data.length) {
+ return "";
+ }
+ return new String(data, from, to - from, charsetName);
+ }
+
+ private static final class Id3Header {
+
+ private final int majorVersion;
+ private final boolean isUnsynchronized;
+ private final int framesSize;
+
+ public Id3Header(int majorVersion, boolean isUnsynchronized, int framesSize) {
+ this.majorVersion = majorVersion;
+ this.isUnsynchronized = isUnsynchronized;
+ this.framesSize = framesSize;
+ }
+
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Frame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
new file mode 100644
index 0000000000..f96b5e752c
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/Id3Frame.java
@@ -0,0 +1,44 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.Metadata;
+
+/**
+ * Base class for ID3 frames.
+ */
+public abstract class Id3Frame implements Metadata.Entry {
+
+ /**
+ * The frame ID.
+ */
+ public final String id;
+
+ public Id3Frame(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/InternalFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/InternalFrame.java
new file mode 100644
index 0000000000..ab8ccff343
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/InternalFrame.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+
+/** Internal ID3 frame that is intended for use by the player. */
+public final class InternalFrame extends Id3Frame {
+
+ public static final String ID = "----";
+
+ public final String domain;
+ public final String description;
+ public final String text;
+
+ public InternalFrame(String domain, String description, String text) {
+ super(ID);
+ this.domain = domain;
+ this.description = description;
+ this.text = text;
+ }
+
+ /* package */ InternalFrame(Parcel in) {
+ super(ID);
+ domain = castNonNull(in.readString());
+ description = castNonNull(in.readString());
+ text = castNonNull(in.readString());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ InternalFrame other = (InternalFrame) obj;
+ return Util.areEqual(description, other.description)
+ && Util.areEqual(domain, other.domain)
+ && Util.areEqual(text, other.text);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (domain != null ? domain.hashCode() : 0);
+ result = 31 * result + (description != null ? description.hashCode() : 0);
+ result = 31 * result + (text != null ? text.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": domain=" + domain + ", description=" + description;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(domain);
+ dest.writeString(text);
+ }
+
+ public static final Creator<InternalFrame> CREATOR =
+ new Creator<InternalFrame>() {
+
+ @Override
+ public InternalFrame createFromParcel(Parcel in) {
+ return new InternalFrame(in);
+ }
+
+ @Override
+ public InternalFrame[] newArray(int size) {
+ return new InternalFrame[size];
+ }
+ };
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/MlltFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/MlltFrame.java
new file mode 100644
index 0000000000..441235d7c9
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/MlltFrame.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import android.os.Parcel;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/** MPEG location lookup table frame. */
+public final class MlltFrame extends Id3Frame {
+
+ public static final String ID = "MLLT";
+
+ public final int mpegFramesBetweenReference;
+ public final int bytesBetweenReference;
+ public final int millisecondsBetweenReference;
+ public final int[] bytesDeviations;
+ public final int[] millisecondsDeviations;
+
+ public MlltFrame(
+ int mpegFramesBetweenReference,
+ int bytesBetweenReference,
+ int millisecondsBetweenReference,
+ int[] bytesDeviations,
+ int[] millisecondsDeviations) {
+ super(ID);
+ this.mpegFramesBetweenReference = mpegFramesBetweenReference;
+ this.bytesBetweenReference = bytesBetweenReference;
+ this.millisecondsBetweenReference = millisecondsBetweenReference;
+ this.bytesDeviations = bytesDeviations;
+ this.millisecondsDeviations = millisecondsDeviations;
+ }
+
+ /* package */
+ MlltFrame(Parcel in) {
+ super(ID);
+ this.mpegFramesBetweenReference = in.readInt();
+ this.bytesBetweenReference = in.readInt();
+ this.millisecondsBetweenReference = in.readInt();
+ this.bytesDeviations = Util.castNonNull(in.createIntArray());
+ this.millisecondsDeviations = Util.castNonNull(in.createIntArray());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ MlltFrame other = (MlltFrame) obj;
+ return mpegFramesBetweenReference == other.mpegFramesBetweenReference
+ && bytesBetweenReference == other.bytesBetweenReference
+ && millisecondsBetweenReference == other.millisecondsBetweenReference
+ && Arrays.equals(bytesDeviations, other.bytesDeviations)
+ && Arrays.equals(millisecondsDeviations, other.millisecondsDeviations);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mpegFramesBetweenReference;
+ result = 31 * result + bytesBetweenReference;
+ result = 31 * result + millisecondsBetweenReference;
+ result = 31 * result + Arrays.hashCode(bytesDeviations);
+ result = 31 * result + Arrays.hashCode(millisecondsDeviations);
+ return result;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mpegFramesBetweenReference);
+ dest.writeInt(bytesBetweenReference);
+ dest.writeInt(millisecondsBetweenReference);
+ dest.writeIntArray(bytesDeviations);
+ dest.writeIntArray(millisecondsDeviations);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<MlltFrame> CREATOR =
+ new Creator<MlltFrame>() {
+
+ @Override
+ public MlltFrame createFromParcel(Parcel in) {
+ return new MlltFrame(in);
+ }
+
+ @Override
+ public MlltFrame[] newArray(int size) {
+ return new MlltFrame[size];
+ }
+ };
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/PrivFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
new file mode 100644
index 0000000000..248d9996dd
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/PrivFrame.java
@@ -0,0 +1,94 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+
+/**
+ * PRIV (Private) ID3 frame.
+ */
+public final class PrivFrame extends Id3Frame {
+
+ public static final String ID = "PRIV";
+
+ public final String owner;
+ public final byte[] privateData;
+
+ public PrivFrame(String owner, byte[] privateData) {
+ super(ID);
+ this.owner = owner;
+ this.privateData = privateData;
+ }
+
+ /* package */ PrivFrame(Parcel in) {
+ super(ID);
+ owner = castNonNull(in.readString());
+ privateData = castNonNull(in.createByteArray());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ PrivFrame other = (PrivFrame) obj;
+ return Util.areEqual(owner, other.owner) && Arrays.equals(privateData, other.privateData);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (owner != null ? owner.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(privateData);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": owner=" + owner;
+ }
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(owner);
+ dest.writeByteArray(privateData);
+ }
+
+ public static final Parcelable.Creator<PrivFrame> CREATOR = new Parcelable.Creator<PrivFrame>() {
+
+ @Override
+ public PrivFrame createFromParcel(Parcel in) {
+ return new PrivFrame(in);
+ }
+
+ @Override
+ public PrivFrame[] newArray(int size) {
+ return new PrivFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
new file mode 100644
index 0000000000..c0bd36ccf7
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java
@@ -0,0 +1,96 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+
+/**
+ * Text information ID3 frame.
+ */
+public final class TextInformationFrame extends Id3Frame {
+
+ @Nullable public final String description;
+ public final String value;
+
+ public TextInformationFrame(String id, @Nullable String description, String value) {
+ super(id);
+ this.description = description;
+ this.value = value;
+ }
+
+ /* package */ TextInformationFrame(Parcel in) {
+ super(castNonNull(in.readString()));
+ description = in.readString();
+ value = castNonNull(in.readString());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ TextInformationFrame other = (TextInformationFrame) obj;
+ return id.equals(other.id) && Util.areEqual(description, other.description)
+ && Util.areEqual(value, other.value);
+ }
+
+ @Override
+ public int hashCode() {
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": description=" + description + ": value=" + value;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(description);
+ dest.writeString(value);
+ }
+
+ public static final Parcelable.Creator<TextInformationFrame> CREATOR =
+ new Parcelable.Creator<TextInformationFrame>() {
+
+ @Override
+ public TextInformationFrame createFromParcel(Parcel in) {
+ return new TextInformationFrame(in);
+ }
+
+ @Override
+ public TextInformationFrame[] newArray(int size) {
+ return new TextInformationFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java
new file mode 100644
index 0000000000..ced474960e
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java
@@ -0,0 +1,96 @@
+/*
+ * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import static org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+
+/**
+ * Url link ID3 frame.
+ */
+public final class UrlLinkFrame extends Id3Frame {
+
+ @Nullable public final String description;
+ public final String url;
+
+ public UrlLinkFrame(String id, @Nullable String description, String url) {
+ super(id);
+ this.description = description;
+ this.url = url;
+ }
+
+ /* package */ UrlLinkFrame(Parcel in) {
+ super(castNonNull(in.readString()));
+ description = in.readString();
+ url = castNonNull(in.readString());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return id + ": url=" + url;
+ }
+
+ // Parcelable implementation.
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(description);
+ dest.writeString(url);
+ }
+
+ public static final Parcelable.Creator<UrlLinkFrame> CREATOR =
+ new Parcelable.Creator<UrlLinkFrame>() {
+
+ @Override
+ public UrlLinkFrame createFromParcel(Parcel in) {
+ return new UrlLinkFrame(in);
+ }
+
+ @Override
+ public UrlLinkFrame[] newArray(int size) {
+ return new UrlLinkFrame[size];
+ }
+
+ };
+
+}
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/package-info.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/package-info.java
new file mode 100644
index 0000000000..87b20161df
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/metadata/id3/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+@NonNullApi
+package org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.id3;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.NonNullApi;