Add RequestMetadata to MediaItem

These fields can be used to transport additional request properties
when the requester doesn't know the details needed for the actual
playback (i.e. the LocalConfiguration).

PiperOrigin-RevId: 451857093
This commit is contained in:
tonihei 2022-05-30 12:30:02 +00:00 committed by Marc Baechinger
parent b25d00a795
commit 6d776a5ae4
2 changed files with 203 additions and 5 deletions

View File

@ -85,6 +85,7 @@ public final class MediaItem implements Bundleable {
// TODO: Change this to LiveConfiguration once all the deprecated individual setters
// are removed.
private LiveConfiguration.Builder liveConfiguration;
private RequestMetadata requestMetadata;
/** Creates a builder. */
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
@ -94,6 +95,7 @@ public final class MediaItem implements Bundleable {
streamKeys = Collections.emptyList();
subtitleConfigurations = ImmutableList.of();
liveConfiguration = new LiveConfiguration.Builder();
requestMetadata = RequestMetadata.EMPTY;
}
private Builder(MediaItem mediaItem) {
@ -102,6 +104,7 @@ public final class MediaItem implements Bundleable {
mediaId = mediaItem.mediaId;
mediaMetadata = mediaItem.mediaMetadata;
liveConfiguration = mediaItem.liveConfiguration.buildUpon();
requestMetadata = mediaItem.requestMetadata;
@Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration;
if (localConfiguration != null) {
customCacheKey = localConfiguration.customCacheKey;
@ -526,6 +529,12 @@ public final class MediaItem implements Bundleable {
return this;
}
/** Sets the request metadata. */
public Builder setRequestMetadata(RequestMetadata requestMetadata) {
this.requestMetadata = requestMetadata;
return this;
}
/** Returns a new {@link MediaItem} instance with the current builder values. */
@SuppressWarnings("deprecation") // Using PlaybackProperties while it exists.
public MediaItem build() {
@ -550,7 +559,8 @@ public final class MediaItem implements Bundleable {
clippingConfiguration.buildClippingProperties(),
localConfiguration,
liveConfiguration.build(),
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY);
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY,
requestMetadata);
}
}
@ -1730,6 +1740,146 @@ public final class MediaItem implements Bundleable {
}
}
/**
* Metadata that helps the player to understand a playback request represented by a {@link
* MediaItem}.
*
* <p>This metadata is most useful for cases where playback requests are forwarded to other player
* instances (e.g. from a {@link android.media.session.MediaController}) and the player creating
* the request doesn't know the required {@link LocalConfiguration} for playback.
*/
public static final class RequestMetadata implements Bundleable {
/** Empty request metadata. */
public static final RequestMetadata EMPTY = new Builder().build();
/** Builder for {@link RequestMetadata} instances. */
public static final class Builder {
@Nullable private Uri mediaUri;
@Nullable private String searchQuery;
@Nullable private Bundle extras;
/** Constructs an instance. */
public Builder() {}
private Builder(RequestMetadata requestMetadata) {
this.mediaUri = requestMetadata.mediaUri;
this.searchQuery = requestMetadata.searchQuery;
this.extras = requestMetadata.extras;
}
/** Sets the URI of the requested media, or null if not known or applicable. */
public Builder setMediaUri(@Nullable Uri mediaUri) {
this.mediaUri = mediaUri;
return this;
}
/** Sets the search query for the requested media, or null if not applicable. */
public Builder setSearchQuery(@Nullable String searchQuery) {
this.searchQuery = searchQuery;
return this;
}
/** Sets optional extras {@link Bundle}. */
public Builder setExtras(@Nullable Bundle extras) {
this.extras = extras;
return this;
}
/** Builds the request metadata. */
public RequestMetadata build() {
return new RequestMetadata(this);
}
}
/** The URI of the requested media, or null if not known or applicable. */
@Nullable public final Uri mediaUri;
/** The search query for the requested media, or null if not applicable. */
@Nullable public final String searchQuery;
/**
* Optional extras {@link Bundle}.
*
* <p>Given the complexities of checking the equality of two {@link Bundle}s, this is not
* considered in the {@link #equals(Object)} or {@link #hashCode()}.
*/
@Nullable public final Bundle extras;
private RequestMetadata(Builder builder) {
this.mediaUri = builder.mediaUri;
this.searchQuery = builder.searchQuery;
this.extras = builder.extras;
}
/** Returns a {@link Builder} initialized with the values of this instance. */
public Builder buildUpon() {
return new Builder(this);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RequestMetadata)) {
return false;
}
RequestMetadata that = (RequestMetadata) o;
return Util.areEqual(mediaUri, that.mediaUri) && Util.areEqual(searchQuery, that.searchQuery);
}
@Override
public int hashCode() {
int result = mediaUri == null ? 0 : mediaUri.hashCode();
result = 31 * result + (searchQuery == null ? 0 : searchQuery.hashCode());
return result;
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({FIELD_MEDIA_URI, FIELD_SEARCH_QUERY, FIELD_EXTRAS})
private @interface FieldNumber {}
private static final int FIELD_MEDIA_URI = 0;
private static final int FIELD_SEARCH_QUERY = 1;
private static final int FIELD_EXTRAS = 2;
@UnstableApi
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
if (mediaUri != null) {
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
}
if (searchQuery != null) {
bundle.putString(keyForField(FIELD_SEARCH_QUERY), searchQuery);
}
if (extras != null) {
bundle.putBundle(keyForField(FIELD_EXTRAS), extras);
}
return bundle;
}
/** Object that can restore {@link RequestMetadata} from a {@link Bundle}. */
@UnstableApi
public static final Creator<RequestMetadata> CREATOR =
bundle ->
new RequestMetadata.Builder()
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
.setSearchQuery(bundle.getString(keyForField(FIELD_SEARCH_QUERY)))
.setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS)))
.build();
private static String keyForField(@RequestMetadata.FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}
/**
* The default media ID that is used if the media ID is not explicitly set by {@link
* Builder#setMediaId(String)}.
@ -1765,6 +1915,9 @@ public final class MediaItem implements Bundleable {
*/
@UnstableApi @Deprecated public final ClippingProperties clippingProperties;
/** The media {@link RequestMetadata}. */
public final RequestMetadata requestMetadata;
// Using PlaybackProperties and ClippingProperties until they're deleted.
@SuppressWarnings("deprecation")
private MediaItem(
@ -1772,7 +1925,8 @@ public final class MediaItem implements Bundleable {
ClippingProperties clippingConfiguration,
@Nullable PlaybackProperties localConfiguration,
LiveConfiguration liveConfiguration,
MediaMetadata mediaMetadata) {
MediaMetadata mediaMetadata,
RequestMetadata requestMetadata) {
this.mediaId = mediaId;
this.localConfiguration = localConfiguration;
this.playbackProperties = localConfiguration;
@ -1780,6 +1934,7 @@ public final class MediaItem implements Bundleable {
this.mediaMetadata = mediaMetadata;
this.clippingConfiguration = clippingConfiguration;
this.clippingProperties = clippingConfiguration;
this.requestMetadata = requestMetadata;
}
/** Returns a {@link Builder} initialized with the values of this instance. */
@ -1802,7 +1957,8 @@ public final class MediaItem implements Bundleable {
&& clippingConfiguration.equals(other.clippingConfiguration)
&& Util.areEqual(localConfiguration, other.localConfiguration)
&& Util.areEqual(liveConfiguration, other.liveConfiguration)
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
&& Util.areEqual(mediaMetadata, other.mediaMetadata)
&& Util.areEqual(requestMetadata, other.requestMetadata);
}
@Override
@ -1812,6 +1968,7 @@ public final class MediaItem implements Bundleable {
result = 31 * result + liveConfiguration.hashCode();
result = 31 * result + clippingConfiguration.hashCode();
result = 31 * result + mediaMetadata.hashCode();
result = 31 * result + requestMetadata.hashCode();
return result;
}
@ -1824,7 +1981,8 @@ public final class MediaItem implements Bundleable {
FIELD_MEDIA_ID,
FIELD_LIVE_CONFIGURATION,
FIELD_MEDIA_METADATA,
FIELD_CLIPPING_PROPERTIES
FIELD_CLIPPING_PROPERTIES,
FIELD_REQUEST_METADATA
})
private @interface FieldNumber {}
@ -1832,6 +1990,7 @@ public final class MediaItem implements Bundleable {
private static final int FIELD_LIVE_CONFIGURATION = 1;
private static final int FIELD_MEDIA_METADATA = 2;
private static final int FIELD_CLIPPING_PROPERTIES = 3;
private static final int FIELD_REQUEST_METADATA = 4;
/**
* {@inheritDoc}
@ -1847,6 +2006,7 @@ public final class MediaItem implements Bundleable {
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle());
return bundle;
}
@ -1883,12 +2043,20 @@ public final class MediaItem implements Bundleable {
} else {
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
}
@Nullable Bundle requestMetadataBundle = bundle.getBundle(keyForField(FIELD_REQUEST_METADATA));
RequestMetadata requestMetadata;
if (requestMetadataBundle == null) {
requestMetadata = RequestMetadata.EMPTY;
} else {
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
}
return new MediaItem(
mediaId,
clippingConfiguration,
/* localConfiguration= */ null,
liveConfiguration,
mediaMetadata);
mediaMetadata,
requestMetadata);
}
private static String keyForField(@FieldNumber int field) {

View File

@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.net.Uri;
import android.os.Bundle;
import androidx.media3.common.MediaItem.RequestMetadata;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@ -579,6 +581,24 @@ public class MediaItemTest {
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(1234);
}
@Test
public void builder_setRequestMetadata_setsRequestMetadata() {
Bundle extras = new Bundle();
extras.putString("key", "value");
RequestMetadata requestMetadata =
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("Play media!")
.setExtras(extras)
.build();
MediaItem mediaItem =
new MediaItem.Builder().setMediaId("mediaID").setRequestMetadata(requestMetadata).build();
assertThat(mediaItem.requestMetadata).isEqualTo(requestMetadata);
assertThat(mediaItem.requestMetadata.extras.getString("key")).isEqualTo("value");
}
@Test
@SuppressWarnings("deprecation") // Testing deprecated setter methods
public void buildUpon_individualSetters_equalsToOriginal() {
@ -677,6 +697,11 @@ public class MediaItemTest {
.setLabel("label")
.setId("id")
.build()))
.setRequestMetadata(
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("search")
.build())
.setTag(new Object())
.build();
@ -704,6 +729,11 @@ public class MediaItemTest {
.setClipRelativeToDefaultPosition(true)
.setClipRelativeToLiveWindow(true)
.setClipStartsAtKeyFrame(true)
.setRequestMetadata(
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("search")
.build())
.build();
assertThat(mediaItem.localConfiguration).isNull();