Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacing label with a list of Label elements in Format. #1054

Merged
merged 8 commits into from
Mar 27, 2024
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Unreleased changes

* Common Library:
* Add `Format.labels` to allow localized or other alternative labels.
* ExoPlayer:
* Fix issue where `PreloadMediaPeriod` cannot retain the streams when it
is preloaded again.
Expand Down Expand Up @@ -100,6 +101,8 @@
* RTMP Extension:
* HLS Extension:
* DASH Extension:
* Populate all `Label` elements from the manifest into `Format.labels`
([#1054](https://github.com/androidx/media/pull/1054)).
* Smooth Streaming Extension:
* RTSP Extension:
* Skip empty session information values (i-tags) in SDP parsing
Expand Down
87 changes: 81 additions & 6 deletions libraries/common/src/main/java/androidx/media3/common/Format.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
*/
package androidx.media3.common;

import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.util.BundleCollectionUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
Expand All @@ -50,6 +53,7 @@
* <ul>
* <li>{@link #id}
* <li>{@link #label}
* <li>{@link #labels}
* <li>{@link #language}
* <li>{@link #selectionFlags}
* <li>{@link #roleFlags}
Expand Down Expand Up @@ -137,6 +141,7 @@ public static final class Builder {

@Nullable private String id;
@Nullable private String label;
private List<Label> labels;
@Nullable private String language;
private @C.SelectionFlags int selectionFlags;
private @C.RoleFlags int roleFlags;
Expand Down Expand Up @@ -192,6 +197,7 @@ public static final class Builder {

/** Creates a new instance with default values. */
public Builder() {
labels = ImmutableList.of();
averageBitrate = NO_VALUE;
peakBitrate = NO_VALUE;
// Sample specific.
Expand Down Expand Up @@ -225,6 +231,7 @@ public Builder() {
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;
Expand Down Expand Up @@ -293,6 +300,9 @@ public Builder setId(int id) {
/**
* Sets {@link Format#label}. The default value is {@code null}.
*
* <p>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.
*/
Expand All @@ -302,6 +312,21 @@ public Builder setLabel(@Nullable String label) {
return this;
}

/**
* Sets {@link Format#labels}. The default value is an empty list.
*
* <p>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<Label> labels) {
this.labels = ImmutableList.copyOf(labels);
return this;
}

/**
* Sets {@link Format#language}. The default value is {@code null}.
*
Expand Down Expand Up @@ -740,9 +765,22 @@ public Format build() {
/** 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.
*
* <p>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.
*
* <p>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<Label> labels;

/** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */
@Nullable public final String language;

Expand Down Expand Up @@ -931,8 +969,20 @@ public Format build() {

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;
Expand Down Expand Up @@ -1003,6 +1053,7 @@ public Format withManifestFormatInfo(Format manifestFormat) {

// Prefer manifest values, but fill in from sample format if missing.
@Nullable String label = manifestFormat.label != null ? manifestFormat.label : this.label;
List<Label> 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) {
Expand Down Expand Up @@ -1044,6 +1095,7 @@ public Format withManifestFormatInfo(Format manifestFormat) {
return buildUpon()
.setId(id)
.setLabel(label)
.setLabels(labels)
.setLanguage(language)
.setSelectionFlags(selectionFlags)
.setRoleFlags(roleFlags)
Expand Down Expand Up @@ -1111,7 +1163,8 @@ public int hashCode() {
// 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;
Expand Down Expand Up @@ -1190,6 +1243,7 @@ public boolean equals(@Nullable Object obj) {
&& 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)
Expand Down Expand Up @@ -1281,8 +1335,10 @@ public static String toLogString(@Nullable Format format) {
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=[");
Expand Down Expand Up @@ -1331,6 +1387,7 @@ public static String toLogString(@Nullable Format format) {
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
Expand All @@ -1347,6 +1404,8 @@ public Bundle toBundle(boolean excludeMetadata) {
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);
Expand Down Expand Up @@ -1413,7 +1472,14 @@ public static Format fromBundle(Bundle bundle) {
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<Bundle> labelsBundles = bundle.getParcelableArrayList(FIELD_LABELS);
List<Label> 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))
Expand Down Expand Up @@ -1492,4 +1558,13 @@ private static String keyForInitializationData(int initialisationDataIndex) {
private static <T> T defaultIfNull(@Nullable T value, @Nullable T defaultValue) {
return value != null ? value : defaultValue;
}

private static String getDefaultLabel(List<Label> labels, @Nullable String language) {
for (Label l : labels) {
if (TextUtils.equals(l.language, language)) {
return l.value;
}
}
return labels.get(0).value;
}
}
86 changes: 86 additions & 0 deletions libraries/common/src/main/java/androidx/media3/common/Label.java
Original file line number Diff line number Diff line change
@@ -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)));
}
}
Loading