labels;
@Nullable private String language;
private @C.SelectionFlags int selectionFlags;
private @C.RoleFlags int roleFlags;
@@ -192,6 +197,7 @@ public final class Format implements Bundleable {
/** Creates a new instance with default values. */
public Builder() {
+ labels = ImmutableList.of();
averageBitrate = NO_VALUE;
peakBitrate = NO_VALUE;
// Sample specific.
@@ -225,6 +231,7 @@ public final class Format implements Bundleable {
private Builder(Format format) {
this.id = format.id;
this.label = format.label;
+ this.labels = format.labels;
this.language = format.language;
this.selectionFlags = format.selectionFlags;
this.roleFlags = format.roleFlags;
@@ -293,6 +300,9 @@ public final class Format implements Bundleable {
/**
* Sets {@link Format#label}. The default value is {@code null}.
*
+ * If both this default label and a list of {@link #setLabels labels} are set, this default
+ * label must be part of label list.
+ *
* @param label The {@link Format#label}.
* @return The builder.
*/
@@ -302,6 +312,21 @@ public final class Format implements Bundleable {
return this;
}
+ /**
+ * Sets {@link Format#labels}. The default value is an empty list.
+ *
+ *
If both the default {@linkplain #setLabel label} and this list are set, the default label
+ * must be part of this list of labels.
+ *
+ * @param labels The {@link Format#labels}.
+ * @return The builder.
+ */
+ @CanIgnoreReturnValue
+ public Builder setLabels(List labels) {
+ this.labels = ImmutableList.copyOf(labels);
+ return this;
+ }
+
/**
* Sets {@link Format#language}. The default value is {@code null}.
*
@@ -740,9 +765,22 @@ public final class Format implements Bundleable {
/** An identifier for the format, or null if unknown or not applicable. */
@Nullable public final String id;
- /** The human readable label, or null if unknown or not applicable. */
+ /**
+ * The default human readable label, or null if unknown or not applicable.
+ *
+ * If non-null, the same label will be part of {@link #labels} too. If null, {@link #labels}
+ * will be empty.
+ */
@Nullable public final String label;
+ /**
+ * The human readable list of labels, or an empty list if unknown or not applicable.
+ *
+ *
If non-empty, the default {@link #label} will be part of this list. If empty, the default
+ * {@link #label} will be null.
+ */
+ @UnstableApi public final List labels;
+
/** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */
@Nullable public final String language;
@@ -931,8 +969,20 @@ public final class Format implements Bundleable {
private Format(Builder builder) {
id = builder.id;
- label = builder.label;
language = Util.normalizeLanguageCode(builder.language);
+ if (builder.labels.isEmpty() && builder.label != null) {
+ labels = ImmutableList.of(new Label(language, builder.label));
+ label = builder.label;
+ } else if (!builder.labels.isEmpty() && builder.label == null) {
+ labels = builder.labels;
+ label = getDefaultLabel(builder.labels, language);
+ } else {
+ checkState(
+ (builder.labels.isEmpty() && builder.label == null)
+ || (builder.labels.stream().anyMatch(l -> l.value.equals(builder.label))));
+ labels = builder.labels;
+ label = builder.label;
+ }
selectionFlags = builder.selectionFlags;
roleFlags = builder.roleFlags;
averageBitrate = builder.averageBitrate;
@@ -1003,6 +1053,7 @@ public final class Format implements Bundleable {
// Prefer manifest values, but fill in from sample format if missing.
@Nullable String label = manifestFormat.label != null ? manifestFormat.label : this.label;
+ List labels = !manifestFormat.labels.isEmpty() ? manifestFormat.labels : this.labels;
@Nullable String language = this.language;
if ((trackType == C.TRACK_TYPE_TEXT || trackType == C.TRACK_TYPE_AUDIO)
&& manifestFormat.language != null) {
@@ -1044,6 +1095,7 @@ public final class Format implements Bundleable {
return buildUpon()
.setId(id)
.setLabel(label)
+ .setLabels(labels)
.setLanguage(language)
.setSelectionFlags(selectionFlags)
.setRoleFlags(roleFlags)
@@ -1111,7 +1163,8 @@ public final class Format implements Bundleable {
// Some fields for which hashing is expensive are deliberately omitted.
int result = 17;
result = 31 * result + (id == null ? 0 : id.hashCode());
- result = 31 * result + (label != null ? label.hashCode() : 0);
+ result = 31 * result + (label == null ? 0 : label.hashCode());
+ result = 31 * result + labels.hashCode();
result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + selectionFlags;
result = 31 * result + roleFlags;
@@ -1190,6 +1243,7 @@ public final class Format implements Bundleable {
&& Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0
&& Util.areEqual(id, other.id)
&& Util.areEqual(label, other.label)
+ && labels.equals(other.labels)
&& Util.areEqual(codecs, other.codecs)
&& Util.areEqual(containerMimeType, other.containerMimeType)
&& Util.areEqual(sampleMimeType, other.sampleMimeType)
@@ -1281,8 +1335,10 @@ public final class Format implements Bundleable {
if (format.language != null) {
builder.append(", language=").append(format.language);
}
- if (format.label != null) {
- builder.append(", label=").append(format.label);
+ if (!format.labels.isEmpty()) {
+ builder.append(", labels=[");
+ Joiner.on(',').appendTo(builder, format.labels);
+ builder.append("]");
}
if (format.selectionFlags != 0) {
builder.append(", selectionFlags=[");
@@ -1331,6 +1387,7 @@ public final class Format implements Bundleable {
private static final String FIELD_CRYPTO_TYPE = Util.intToStringMaxRadix(29);
private static final String FIELD_TILE_COUNT_HORIZONTAL = Util.intToStringMaxRadix(30);
private static final String FIELD_TILE_COUNT_VERTICAL = Util.intToStringMaxRadix(31);
+ private static final String FIELD_LABELS = Util.intToStringMaxRadix(32);
@UnstableApi
@Override
@@ -1347,6 +1404,8 @@ public final class Format implements Bundleable {
Bundle bundle = new Bundle();
bundle.putString(FIELD_ID, id);
bundle.putString(FIELD_LABEL, label);
+ bundle.putParcelableArrayList(
+ FIELD_LABELS, BundleCollectionUtil.toBundleArrayList(labels, Label::toBundle));
bundle.putString(FIELD_LANGUAGE, language);
bundle.putInt(FIELD_SELECTION_FLAGS, selectionFlags);
bundle.putInt(FIELD_ROLE_FLAGS, roleFlags);
@@ -1413,7 +1472,14 @@ public final class Format implements Bundleable {
BundleCollectionUtil.ensureClassLoader(bundle);
builder
.setId(defaultIfNull(bundle.getString(FIELD_ID), DEFAULT.id))
- .setLabel(defaultIfNull(bundle.getString(FIELD_LABEL), DEFAULT.label))
+ .setLabel(defaultIfNull(bundle.getString(FIELD_LABEL), DEFAULT.label));
+ @Nullable List labelsBundles = bundle.getParcelableArrayList(FIELD_LABELS);
+ List labels =
+ labelsBundles == null
+ ? ImmutableList.of()
+ : BundleCollectionUtil.fromBundleList(Label::fromBundle, labelsBundles);
+ builder
+ .setLabels(labels)
.setLanguage(defaultIfNull(bundle.getString(FIELD_LANGUAGE), DEFAULT.language))
.setSelectionFlags(bundle.getInt(FIELD_SELECTION_FLAGS, DEFAULT.selectionFlags))
.setRoleFlags(bundle.getInt(FIELD_ROLE_FLAGS, DEFAULT.roleFlags))
@@ -1492,4 +1558,13 @@ public final class Format implements Bundleable {
private static T defaultIfNull(@Nullable T value, @Nullable T defaultValue) {
return value != null ? value : defaultValue;
}
+
+ private static String getDefaultLabel(List labels, @Nullable String language) {
+ for (Label l : labels) {
+ if (TextUtils.equals(l.language, language)) {
+ return l.value;
+ }
+ }
+ return labels.get(0).value;
+ }
}
diff --git a/libraries/common/src/main/java/androidx/media3/common/Label.java b/libraries/common/src/main/java/androidx/media3/common/Label.java
new file mode 100644
index 0000000000..455ed70dc0
--- /dev/null
+++ b/libraries/common/src/main/java/androidx/media3/common/Label.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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 androidx.media3.common;
+
+import static androidx.media3.common.util.Assertions.checkNotNull;
+
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.common.util.Util;
+
+/** A label for a {@link Format}. */
+@UnstableApi
+public class Label {
+ /**
+ * The language of this label, as an IETF BCP 47 conformant tag, or null if unknown or not
+ * applicable.
+ */
+ @Nullable public final String language;
+
+ /** The value for this label. */
+ public final String value;
+
+ /**
+ * Creates a label.
+ *
+ * @param language The language of this label, as an IETF BCP 47 conformant tag, or null if
+ * unknown or not applicable.
+ * @param value The label value.
+ */
+ public Label(@Nullable String language, String value) {
+ this.language = Util.normalizeLanguageCode(language);
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Label label = (Label) o;
+ return Util.areEqual(language, label.language) && Util.areEqual(value, label.value);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = value.hashCode();
+ result = 31 * result + (language != null ? language.hashCode() : 0);
+ return result;
+ }
+
+ private static final String FIELD_LANGUAGE_INDEX = Util.intToStringMaxRadix(0);
+ private static final String FIELD_VALUE_INDEX = Util.intToStringMaxRadix(1);
+
+ /** Serializes this instance to a {@link Bundle}. */
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ if (language != null) {
+ bundle.putString(FIELD_LANGUAGE_INDEX, language);
+ }
+ bundle.putString(FIELD_VALUE_INDEX, value);
+ return bundle;
+ }
+
+ /** Deserializes an instance from a {@link Bundle} produced by {@link #toBundle()}. */
+ public static Label fromBundle(Bundle bundle) {
+ return new Label(
+ bundle.getString(FIELD_LANGUAGE_INDEX), checkNotNull(bundle.getString(FIELD_VALUE_INDEX)));
+ }
+}
diff --git a/libraries/common/src/test/java/androidx/media3/common/FormatTest.java b/libraries/common/src/test/java/androidx/media3/common/FormatTest.java
index 42cfb2f53c..1710b232ed 100644
--- a/libraries/common/src/test/java/androidx/media3/common/FormatTest.java
+++ b/libraries/common/src/test/java/androidx/media3/common/FormatTest.java
@@ -20,10 +20,12 @@ import static androidx.media3.common.MimeTypes.VIDEO_MP4;
import static androidx.media3.common.MimeTypes.VIDEO_WEBM;
import static androidx.media3.test.utils.TestUtil.buildTestData;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import android.os.Bundle;
import androidx.media3.test.utils.FakeMetadataEntry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
@@ -57,6 +59,62 @@ public final class FormatTest {
assertThat(formatWithMetadataExcluded).isEqualTo(format.buildUpon().setMetadata(null).build());
}
+ @Test
+ public void formatBuild_withLabelAndWithoutLabels_labelIsInLabels() {
+ Format format = new Format.Builder().setLabel("label").setLabels(ImmutableList.of()).build();
+
+ assertThat(format.label).isEqualTo("label");
+ assertThat(format.labels).hasSize(1);
+ assertThat(format.labels.get(0).value).isEqualTo("label");
+ }
+
+ @Test
+ public void formatBuild_withLabelsAndLanguageMatchingAndWithoutLabel_theLanguageMatchIsInLabel() {
+ Format format =
+ new Format.Builder()
+ .setLabel(null)
+ .setLabels(
+ ImmutableList.of(
+ new Label("en", "nonDefaultLabel"), new Label("zh", "matchingLabel")))
+ .setLanguage("zh")
+ .build();
+
+ assertThat(format.label).isEqualTo("matchingLabel");
+ }
+
+ @Test
+ public void formatBuild_withLabelsAndNoLanguageMatchingAndWithoutLabel_theFirstIsInLabel() {
+ Format format =
+ new Format.Builder()
+ .setLabel(null)
+ .setLabels(
+ ImmutableList.of(new Label("fr", "firstLabel"), new Label("de", "secondLabel")))
+ .setLanguage("en")
+ .build();
+
+ assertThat(format.label).isEqualTo("firstLabel");
+ }
+
+ @Test
+ public void formatBuild_withoutLabelsOrLabel_bothEmpty() {
+ Format format = createTestFormat();
+ format = format.buildUpon().setLabel(null).setLabels(ImmutableList.of()).build();
+
+ assertThat(format.label).isNull();
+ assertThat(format.labels).isEmpty();
+ }
+
+ @Test
+ public void formatBuild_withLabelAndLabelsSetButNoMatch_throwsException() {
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ new Format.Builder()
+ .setLabel("otherLabel")
+ .setLabels(ImmutableList.of(new Label("en", "label")))
+ .build());
+ }
+
private static Format createTestFormat() {
byte[] initData1 = new byte[] {1, 2, 3};
byte[] initData2 = new byte[] {4, 5, 6};
@@ -86,6 +144,7 @@ public final class FormatTest {
return new Format.Builder()
.setId("id")
.setLabel("label")
+ .setLabels(ImmutableList.of(new Label("en", "label")))
.setLanguage("language")
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.setRoleFlags(C.ROLE_FLAG_MAIN)
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java
index bc895f6846..5f10a0bfbd 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java
@@ -451,6 +451,7 @@ public abstract class DecoderAudioRenderer<
.setMetadata(inputFormat.metadata)
.setId(inputFormat.id)
.setLabel(inputFormat.label)
+ .setLabels(inputFormat.labels)
.setLanguage(inputFormat.language)
.setSelectionFlags(inputFormat.selectionFlags)
.setRoleFlags(inputFormat.roleFlags)
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
index ddf7164cd1..c4468563d8 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
@@ -552,6 +552,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
.setMetadata(format.metadata)
.setId(format.id)
.setLabel(format.label)
+ .setLabels(format.labels)
.setLanguage(format.language)
.setSelectionFlags(format.selectionFlags)
.setRoleFlags(format.roleFlags)
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/mediaparser/OutputConsumerAdapterV30.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/mediaparser/OutputConsumerAdapterV30.java
index 8d2116897c..ab15bba669 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/mediaparser/OutputConsumerAdapterV30.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/mediaparser/OutputConsumerAdapterV30.java
@@ -518,6 +518,7 @@ public final class OutputConsumerAdapterV30 implements MediaParser.OutputConsume
.setRoleFlags(muxedCaptionFormat.roleFlags)
.setSelectionFlags(muxedCaptionFormat.selectionFlags)
.setLabel(muxedCaptionFormat.label)
+ .setLabels(muxedCaptionFormat.labels)
.setMetadata(muxedCaptionFormat.metadata);
break;
}
diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java
index 6ce3993665..4fa9eea43c 100644
--- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java
+++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java
@@ -29,6 +29,7 @@ import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.DrmInitData.SchemeData;
import androidx.media3.common.Format;
+import androidx.media3.common.Label;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.Assertions;
@@ -405,6 +406,7 @@ public class DashManifestParser extends DefaultHandler
int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE);
String language = xpp.getAttributeValue(null, "lang");
String label = xpp.getAttributeValue(null, "label");
+ List labels = new ArrayList<>();
String drmSchemeType = null;
ArrayList drmSchemeDatas = new ArrayList<>();
ArrayList inbandEventStreams = new ArrayList<>();
@@ -504,7 +506,7 @@ public class DashManifestParser extends DefaultHandler
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
} else if (XmlPullParserUtil.isStartTag(xpp, "Label")) {
- label = parseLabel(xpp);
+ labels.add(parseLabel(xpp));
} else if (XmlPullParserUtil.isStartTag(xpp)) {
parseAdaptationSetChild(xpp);
}
@@ -517,6 +519,7 @@ public class DashManifestParser extends DefaultHandler
buildRepresentation(
representationInfos.get(i),
label,
+ labels,
drmSchemeType,
drmSchemeDatas,
inbandEventStreams));
@@ -856,12 +859,15 @@ public class DashManifestParser extends DefaultHandler
protected Representation buildRepresentation(
RepresentationInfo representationInfo,
@Nullable String label,
+ List labels,
@Nullable String extraDrmSchemeType,
ArrayList extraDrmSchemeDatas,
ArrayList extraInbandEventStreams) {
Format.Builder formatBuilder = representationInfo.format.buildUpon();
- if (label != null) {
+ if (label != null && labels.isEmpty()) {
formatBuilder.setLabel(label);
+ } else {
+ formatBuilder.setLabels(labels);
}
@Nullable String drmSchemeType = representationInfo.drmSchemeType;
if (drmSchemeType == null) {
@@ -1405,8 +1411,10 @@ public class DashManifestParser extends DefaultHandler
* @throws IOException If an error occurs reading the element.
* @return The parsed label.
*/
- protected String parseLabel(XmlPullParser xpp) throws XmlPullParserException, IOException {
- return parseText(xpp, "Label");
+ protected Label parseLabel(XmlPullParser xpp) throws XmlPullParserException, IOException {
+ String lang = xpp.getAttributeValue(null, "lang");
+ String value = parseText(xpp, "Label");
+ return new Label(lang, value);
}
/**
diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java
index 29510717d7..2e6fa372b2 100644
--- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java
+++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
+import androidx.media3.common.Label;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.dash.manifest.Representation.MultiSegmentRepresentation;
@@ -280,6 +281,12 @@ public class DashManifestParserTest {
assertThat(adaptationSets.get(0).representations.get(0).format.label).isEqualTo("audio label");
assertThat(adaptationSets.get(1).representations.get(0).format.label).isEqualTo("video label");
+ assertThat(adaptationSets.get(0).representations.get(0).format.labels).hasSize(1);
+ assertThat(adaptationSets.get(0).representations.get(0).format.labels.get(0).value)
+ .isEqualTo("audio label");
+ assertThat(adaptationSets.get(1).representations.get(0).format.labels).hasSize(1);
+ assertThat(adaptationSets.get(1).representations.get(0).format.labels.get(0).value)
+ .isEqualTo("video label");
}
@Test
@@ -431,13 +438,26 @@ public class DashManifestParserTest {
@Test
public void parseLabel() throws Exception {
+ DashManifestParser parser = new DashManifestParser();
+ XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
+ xpp.setInput(new StringReader("test label " + NEXT_TAG));
+ xpp.next();
+
+ Label label = parser.parseLabel(xpp);
+ assertThat(label.language).isEqualTo("en");
+ assertThat(label.value).isEqualTo("test label");
+ assertNextTag(xpp);
+ }
+
+ @Test
+ public void parseLabel_noLang() throws Exception {
DashManifestParser parser = new DashManifestParser();
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
xpp.setInput(new StringReader("test label " + NEXT_TAG));
xpp.next();
- String label = parser.parseLabel(xpp);
- assertThat(label).isEqualTo("test label");
+ Label label = parser.parseLabel(xpp);
+ assertThat(label.language).isNull();
assertNextTag(xpp);
}
@@ -448,8 +468,9 @@ public class DashManifestParserTest {
xpp.setInput(new StringReader(" " + NEXT_TAG));
xpp.next();
- String label = parser.parseLabel(xpp);
- assertThat(label).isEqualTo("");
+ Label label = parser.parseLabel(xpp);
+ assertThat(label.value).isNotNull();
+ assertThat(label.value).isEmpty();
assertNextTag(xpp);
}
diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java
index d43cea69d5..cfb7e82525 100644
--- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java
+++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java
@@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
+import androidx.media3.common.Label;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.StreamKey;
@@ -852,6 +853,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return new Format.Builder()
.setId(variantFormat.id)
.setLabel(variantFormat.label)
+ .setLabels(variantFormat.labels)
.setContainerMimeType(variantFormat.containerMimeType)
.setSampleMimeType(sampleMimeType)
.setCodecs(codecs)
@@ -875,6 +877,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int roleFlags = 0;
@Nullable String language = null;
@Nullable String label = null;
+ List labels = ImmutableList.of();
if (mediaTagFormat != null) {
codecs = mediaTagFormat.codecs;
metadata = mediaTagFormat.metadata;
@@ -883,6 +886,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
roleFlags = mediaTagFormat.roleFlags;
language = mediaTagFormat.language;
label = mediaTagFormat.label;
+ labels = mediaTagFormat.labels;
} else {
codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_AUDIO);
metadata = variantFormat.metadata;
@@ -892,6 +896,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
roleFlags = variantFormat.roleFlags;
language = variantFormat.language;
label = variantFormat.label;
+ labels = variantFormat.labels;
}
}
@Nullable String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
@@ -900,6 +905,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return new Format.Builder()
.setId(variantFormat.id)
.setLabel(label)
+ .setLabels(labels)
.setContainerMimeType(variantFormat.containerMimeType)
.setSampleMimeType(sampleMimeType)
.setCodecs(codecs)
diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsSampleStreamWrapper.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsSampleStreamWrapper.java
index ede8575201..aebff9592d 100644
--- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsSampleStreamWrapper.java
+++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsSampleStreamWrapper.java
@@ -1584,6 +1584,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
.buildUpon()
.setId(playlistFormat.id)
.setLabel(playlistFormat.label)
+ .setLabels(playlistFormat.labels)
.setLanguage(playlistFormat.language)
.setSelectionFlags(playlistFormat.selectionFlags)
.setRoleFlags(playlistFormat.roleFlags)
diff --git a/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/manifest/SsManifestParserTest.java b/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/manifest/SsManifestParserTest.java
index 61e921293b..9f45a58d56 100644
--- a/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/manifest/SsManifestParserTest.java
+++ b/libraries/exoplayer_smoothstreaming/src/test/java/androidx/media3/exoplayer/smoothstreaming/manifest/SsManifestParserTest.java
@@ -52,5 +52,6 @@ public final class SsManifestParserTest {
TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_ISMC_1));
assertThat(ssManifest.streamElements[0].formats[0].label).isEqualTo("video");
+ assertThat(ssManifest.streamElements[0].formats[0].labels.get(0).value).isEqualTo("video");
}
}
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.0.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.0.dump
index 3c09609d18..c4adbf92c2 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.0.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.0.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.1.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.1.dump
index 3c09609d18..c4adbf92c2 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.1.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.1.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.2.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.2.dump
index 3c09609d18..c4adbf92c2 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.2.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.2.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.3.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.3.dump
index 3c09609d18..c4adbf92c2 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.3.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.3.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.unknown_length.dump
index 3c09609d18..c4adbf92c2 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.unknown_length.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_null_terminated_srt.mkv.unknown_length.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.0.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.0.dump
index e2c047c404..5bbcc39d9d 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.0.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.0.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.1.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.1.dump
index e2c047c404..5bbcc39d9d 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.1.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.1.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.2.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.2.dump
index e2c047c404..5bbcc39d9d 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.2.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.2.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.3.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.3.dump
index e2c047c404..5bbcc39d9d 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.3.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.3.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.unknown_length.dump
index e2c047c404..5bbcc39d9d 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.unknown_length.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv/sample_with_srt.mkv.unknown_length.dump
@@ -275,6 +275,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.0.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.0.dump
index 5470deb7aa..c70250ab63 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.0.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.0.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.1.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.1.dump
index 5470deb7aa..c70250ab63 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.1.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.1.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.2.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.2.dump
index 5470deb7aa..c70250ab63 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.2.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.2.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.3.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.3.dump
index 5470deb7aa..c70250ab63 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.3.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.3.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.unknown_length.dump
index 5470deb7aa..c70250ab63 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.unknown_length.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_null_terminated_srt.mkv.unknown_length.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.0.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.0.dump
index 69069591f0..c300df088a 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.0.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.0.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.1.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.1.dump
index 69069591f0..c300df088a 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.1.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.1.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.2.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.2.dump
index 69069591f0..c300df088a 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.2.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.2.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.3.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.3.dump
index 69069591f0..c300df088a 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.3.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.3.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.unknown_length.dump
index 69069591f0..c300df088a 100644
--- a/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.unknown_length.dump
+++ b/libraries/test_data/src/test/assets/extractordumps/mkv_subtitle_transcoding/sample_with_srt.mkv.unknown_length.dump
@@ -276,6 +276,9 @@ track 3:
selectionFlags = [default]
language = en
label = Subs Label
+ labels:
+ lang = en
+ value = Subs Label
sample 0:
time = 0
flags = 1
diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/DumpableFormat.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/DumpableFormat.java
index 13f4453d4d..59cd6af726 100644
--- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/DumpableFormat.java
+++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/DumpableFormat.java
@@ -101,6 +101,17 @@ public final class DumpableFormat implements Dumper.Dumpable {
format -> Util.getRoleFlagStrings(format.roleFlags));
addIfNonDefault(dumper, "language", format, DEFAULT_FORMAT, format -> format.language);
addIfNonDefault(dumper, "label", format, DEFAULT_FORMAT, format -> format.label);
+ if (!format.labels.isEmpty()) {
+ dumper.startBlock("labels");
+ for (int i = 0; i < format.labels.size(); i++) {
+ String lang = format.labels.get(i).language;
+ if (lang != null) {
+ dumper.add("lang", lang);
+ }
+ dumper.add("value", format.labels.get(i).value);
+ }
+ dumper.endBlock();
+ }
if (format.drmInitData != null) {
dumper.add("drmInitData", format.drmInitData.hashCode());
}