Skip to content

Commit

Permalink
Merge pull request #1054 from jekopena:main
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 619573181
(cherry picked from commit 8fe7033)
  • Loading branch information
Copybara-Service authored and SheenaChhabra committed Mar 27, 2024
1 parent 4caed3c commit 7b5522f
Show file tree
Hide file tree
Showing 33 changed files with 352 additions and 16 deletions.
9 changes: 7 additions & 2 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### 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 All @@ -28,7 +30,7 @@
from WAV files ([#1117](https://github.com/androidx/media/pull/1117)).
* MP3: Populate `Format.averageBitrate` from metadata frames such as
`XING` and `VBRI`.
* Audio:
* Audio:
* Allow renderer recovery by disabling offload if audio track fails to
initialize in offload mode.
* Video:
Expand Down Expand Up @@ -60,10 +62,13 @@
* Fix issue where `MediaMetadata` with just non-null `extras` is not
transmitted between media controllers and sessions
([#1176](https://github.com/androidx/media/issues/1176)).
* UI:
* UI:
* Fallback to include audio track language name if `Locale` cannot
identify a display name
([#988](https://github.com/androidx/media/issues/988)).
* DASH Extension:
* Populate all `Label` elements from the manifest into `Format.labels`
([#1054](https://github.com/androidx/media/pull/1054)).
* RTSP Extension:
* Skip empty session information values (i-tags) in SDP parsing
([#1087](https://github.com/androidx/media/issues/1087)).
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

0 comments on commit 7b5522f

Please sign in to comment.