diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index 607f797103..2df780ce93 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -21,11 +21,14 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import java.util.Arrays; /** Defines an immutable group of tracks identified by their format identity. */ public final class TrackGroup implements Parcelable { + private static final String TAG = "TrackGroup"; + /** The number of tracks in the group. */ public final int length; @@ -41,6 +44,7 @@ public final class TrackGroup implements Parcelable { Assertions.checkState(formats.length > 0); this.formats = formats; this.length = formats.length; + verifyCorrectness(); } /* package */ TrackGroup(Parcel in) { @@ -129,4 +133,62 @@ public final class TrackGroup implements Parcelable { return new TrackGroup[size]; } }; + + private void verifyCorrectness() { + // TrackGroups should only contain tracks with exactly the same content (but in different + // qualities). We only log an error instead of throwing to not break backwards-compatibility for + // cases where malformed TrackGroups happen to work by chance (e.g. because adaptive selections + // are always disabled). + String language = normalizeLanguage(formats[0].language); + @C.RoleFlags int roleFlags = normalizeRoleFlags(formats[0].roleFlags); + for (int i = 1; i < formats.length; i++) { + if (!language.equals(normalizeLanguage(formats[i].language))) { + logErrorMessage( + /* mismatchField= */ "languages", + /* valueIndex0= */ formats[0].language, + /* otherValue=* */ formats[i].language, + /* otherIndex= */ i); + return; + } + if (roleFlags != normalizeRoleFlags(formats[i].roleFlags)) { + logErrorMessage( + /* mismatchField= */ "role flags", + /* valueIndex0= */ Integer.toBinaryString(formats[0].roleFlags), + /* otherValue=* */ Integer.toBinaryString(formats[i].roleFlags), + /* otherIndex= */ i); + return; + } + } + } + + private static String normalizeLanguage(@Nullable String language) { + // Treat all variants of undetermined or unknown languages as compatible. + return language == null || language.equals(C.LANGUAGE_UNDETERMINED) ? "" : language; + } + + @C.RoleFlags + private static int normalizeRoleFlags(@C.RoleFlags int roleFlags) { + // Treat trick-play and non-trick-play formats as compatible. + return roleFlags | C.ROLE_FLAG_TRICK_PLAY; + } + + private static void logErrorMessage( + String mismatchField, + @Nullable String valueIndex0, + @Nullable String otherValue, + int otherIndex) { + Log.e( + TAG, + "", + new IllegalStateException( + "Different " + + mismatchField + + " combined in one TrackGroup: '" + + valueIndex0 + + "' (track 0) and '" + + otherValue + + "' (track " + + otherIndex + + ")")); + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 0dbce9a70f..d04b4f8341 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -670,7 +670,7 @@ public final class Util { // Locale data (especially for API < 21) may produce tags with '_' instead of the // standard-conformant '-'. String normalizedTag = language.replace('_', '-'); - if (normalizedTag.isEmpty() || "und".equals(normalizedTag)) { + if (normalizedTag.isEmpty() || normalizedTag.equals(C.LANGUAGE_UNDETERMINED)) { // Tag isn't valid, keep using the original. normalizedTag = language; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index 53ed281152..a5470dace6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -279,8 +279,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; "Psybient" }; - private static final String LANGUAGE_UNDEFINED = "und"; - private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9; private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD. @@ -467,7 +465,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (atomType == Atom.TYPE_data) { data.skipBytes(8); // version (1), flags (3), empty (4) String value = data.readNullTerminatedString(atomSize - 16); - return new CommentFrame(LANGUAGE_UNDEFINED, value, value); + return new CommentFrame(C.LANGUAGE_UNDETERMINED, value, value); } Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type)); return null; @@ -487,7 +485,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (value >= 0) { return isTextInformationFrame ? new TextInformationFrame(id, /* description= */ null, Integer.toString(value)) - : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); + : new CommentFrame(C.LANGUAGE_UNDETERMINED, id, Integer.toString(value)); } Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); return null;