Commit 630abd83 by jbibik Committed by Tofunmi Adigun-Hameed

Allow playback of `MediaItems` with `LocalConfiguration`

When initiated by MediaController, it should be possible for `MediaSession` to pass `MediaItems` to the `Player` if they have `LocalConfiguration`. In such case, it is not required to override `MediaSession.Callback.onAddMediaItems`, because the new current default implementation will handle it.

However, in other cases, MediaItem.toBundle() will continue to strip the LocalConfiguration information.

Issue: androidx/media#282

#minor-release

PiperOrigin-RevId: 537993460
(cherry picked from commit bcddaf27654ed342ce70fc7a270d478953c2fb80)
parent 62263105
...@@ -1054,7 +1054,7 @@ public final class MediaItem implements Bundleable { ...@@ -1054,7 +1054,7 @@ public final class MediaItem implements Bundleable {
} }
/** Properties for local playback. */ /** Properties for local playback. */
public static final class LocalConfiguration { public static final class LocalConfiguration implements Bundleable {
/** The {@link Uri}. */ /** The {@link Uri}. */
public final Uri uri; public final Uri uri;
...@@ -1150,6 +1150,79 @@ public final class MediaItem implements Bundleable { ...@@ -1150,6 +1150,79 @@ public final class MediaItem implements Bundleable {
result = 31 * result + (tag == null ? 0 : tag.hashCode()); result = 31 * result + (tag == null ? 0 : tag.hashCode());
return result; return result;
} }
// Bundleable implementation.
private static final String FIELD_URI = Util.intToStringMaxRadix(0);
private static final String FIELD_MIME_TYPE = Util.intToStringMaxRadix(1);
private static final String FIELD_DRM_CONFIGURATION = Util.intToStringMaxRadix(2);
private static final String FIELD_ADS_CONFIGURATION = Util.intToStringMaxRadix(3);
private static final String FIELD_STREAM_KEYS = Util.intToStringMaxRadix(4);
private static final String FIELD_CUSTOM_CACHE_KEY = Util.intToStringMaxRadix(5);
private static final String FIELD_SUBTITLE_CONFIGURATION = Util.intToStringMaxRadix(6);
/**
* {@inheritDoc}
*
* <p>It omits the {@link #tag} field. The {@link #tag} of an instance restored from such a
* bundle by {@link #CREATOR} will be {@code null}.
*/
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelable(FIELD_URI, uri);
if (mimeType != null) {
bundle.putString(FIELD_MIME_TYPE, mimeType);
}
if (drmConfiguration != null) {
bundle.putBundle(FIELD_DRM_CONFIGURATION, drmConfiguration.toBundle());
}
if (adsConfiguration != null) {
bundle.putBundle(FIELD_ADS_CONFIGURATION, adsConfiguration.toBundle());
}
if (!streamKeys.isEmpty()) {
bundle.putParcelableArrayList(FIELD_STREAM_KEYS, new ArrayList<>(streamKeys));
}
if (customCacheKey != null) {
bundle.putString(FIELD_CUSTOM_CACHE_KEY, customCacheKey);
}
if (!subtitleConfigurations.isEmpty()) {
bundle.putParcelableArrayList(
FIELD_SUBTITLE_CONFIGURATION, BundleableUtil.toBundleArrayList(subtitleConfigurations));
}
return bundle;
}
/** Object that can restore {@link LocalConfiguration} from a {@link Bundle}. */
public static final Creator<LocalConfiguration> CREATOR = LocalConfiguration::fromBundle;
private static LocalConfiguration fromBundle(Bundle bundle) {
@Nullable Bundle drmBundle = bundle.getBundle(FIELD_DRM_CONFIGURATION);
DrmConfiguration drmConfiguration =
drmBundle == null ? null : DrmConfiguration.CREATOR.fromBundle(drmBundle);
@Nullable Bundle adsBundle = bundle.getBundle(FIELD_ADS_CONFIGURATION);
AdsConfiguration adsConfiguration =
adsBundle == null ? null : AdsConfiguration.CREATOR.fromBundle(adsBundle);
@Nullable List<StreamKey> streamKeysList = bundle.getParcelableArrayList(FIELD_STREAM_KEYS);
List<StreamKey> streamKeys =
streamKeysList == null ? ImmutableList.of() : ImmutableList.copyOf(streamKeysList);
@Nullable
List<Bundle> subtitleBundles = bundle.getParcelableArrayList(FIELD_SUBTITLE_CONFIGURATION);
ImmutableList<SubtitleConfiguration> subtitleConfiguration =
subtitleBundles == null
? ImmutableList.of()
: BundleableUtil.fromBundleList(SubtitleConfiguration.CREATOR, subtitleBundles);
return new LocalConfiguration(
checkNotNull(bundle.getParcelable(FIELD_URI)),
bundle.getString(FIELD_MIME_TYPE),
drmConfiguration,
adsConfiguration,
streamKeys,
bundle.getString(FIELD_CUSTOM_CACHE_KEY),
subtitleConfiguration,
/* tag= */ null);
}
} }
/** Live playback configuration. */ /** Live playback configuration. */
...@@ -2118,15 +2191,9 @@ public final class MediaItem implements Bundleable { ...@@ -2118,15 +2191,9 @@ public final class MediaItem implements Bundleable {
private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2); private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2);
private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3); private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3);
private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4); private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4);
private static final String FIELD_LOCAL_CONFIGURATION = Util.intToStringMaxRadix(5);
/** private Bundle toBundle(boolean includeLocalConfiguration) {
* {@inheritDoc}
*
* <p>It omits the {@link #localConfiguration} field. The {@link #localConfiguration} of an
* instance restored by {@link #CREATOR} will always be {@code null}.
*/
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
if (!mediaId.equals(DEFAULT_MEDIA_ID)) { if (!mediaId.equals(DEFAULT_MEDIA_ID)) {
bundle.putString(FIELD_MEDIA_ID, mediaId); bundle.putString(FIELD_MEDIA_ID, mediaId);
...@@ -2143,10 +2210,32 @@ public final class MediaItem implements Bundleable { ...@@ -2143,10 +2210,32 @@ public final class MediaItem implements Bundleable {
if (!requestMetadata.equals(RequestMetadata.EMPTY)) { if (!requestMetadata.equals(RequestMetadata.EMPTY)) {
bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle()); bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle());
} }
if (includeLocalConfiguration && localConfiguration != null) {
bundle.putBundle(FIELD_LOCAL_CONFIGURATION, localConfiguration.toBundle());
}
return bundle; return bundle;
} }
/** /**
* {@inheritDoc}
*
* <p>It omits the {@link #localConfiguration} field. The {@link #localConfiguration} of an
* instance restored from such a bundle by {@link #CREATOR} will be {@code null}.
*/
@Override
public Bundle toBundle() {
return toBundle(/* includeLocalConfiguration= */ false);
}
/**
* Returns a {@link Bundle} representing the information stored in this {@link #MediaItem} object,
* while including the {@link #localConfiguration} field if it is not null (otherwise skips it).
*/
public Bundle toBundleIncludeLocalConfiguration() {
return toBundle(/* includeLocalConfiguration= */ true);
}
/**
* An object that can restore {@link MediaItem} from a {@link Bundle}. * An object that can restore {@link MediaItem} from a {@link Bundle}.
* *
* <p>The {@link #localConfiguration} of a restored instance will always be {@code null}. * <p>The {@link #localConfiguration} of a restored instance will always be {@code null}.
...@@ -2184,10 +2273,17 @@ public final class MediaItem implements Bundleable { ...@@ -2184,10 +2273,17 @@ public final class MediaItem implements Bundleable {
} else { } else {
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle); requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
} }
@Nullable Bundle localConfigurationBundle = bundle.getBundle(FIELD_LOCAL_CONFIGURATION);
LocalConfiguration localConfiguration;
if (localConfigurationBundle == null) {
localConfiguration = null;
} else {
localConfiguration = LocalConfiguration.CREATOR.fromBundle(localConfigurationBundle);
}
return new MediaItem( return new MediaItem(
mediaId, mediaId,
clippingConfiguration, clippingConfiguration,
/* localConfiguration= */ null, localConfiguration,
liveConfiguration, liveConfiguration,
mediaMetadata, mediaMetadata,
requestMetadata); requestMetadata);
......
...@@ -22,6 +22,7 @@ import android.os.Bundle; ...@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.Bundleable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -35,10 +36,21 @@ public final class BundleableUtil { ...@@ -35,10 +36,21 @@ public final class BundleableUtil {
/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */ /** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) { public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
return toBundleList(bundleableList, Bundleable::toBundle);
}
/**
* Converts a list of {@link Bundleable} to a list {@link Bundle}
*
* @param bundleableList list of Bundleable items to be converted
* @param customToBundleFunc function that specifies how to bundle up each {@link Bundleable}
*/
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(
List<T> bundleableList, Function<T, Bundle> customToBundleFunc) {
ImmutableList.Builder<Bundle> builder = ImmutableList.builder(); ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
for (int i = 0; i < bundleableList.size(); i++) { for (int i = 0; i < bundleableList.size(); i++) {
Bundleable bundleable = bundleableList.get(i); T bundleable = bundleableList.get(i);
builder.add(bundleable.toBundle()); builder.add(customToBundleFunc.apply(bundleable));
} }
return builder.build(); return builder.build();
} }
......
...@@ -668,6 +668,68 @@ public class MediaItemTest { ...@@ -668,6 +668,68 @@ public class MediaItemTest {
} }
@Test @Test
public void
createDefaultLocalConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
Bundle localConfigurationBundle = mediaItem.localConfiguration.toBundle();
// Check that default values are skipped when bundling, only Uri field (="0") is present
assertThat(localConfigurationBundle.keySet()).containsExactly("0");
MediaItem.LocalConfiguration restoredLocalConfiguration =
MediaItem.LocalConfiguration.CREATOR.fromBundle(localConfigurationBundle);
assertThat(restoredLocalConfiguration).isEqualTo(mediaItem.localConfiguration);
assertThat(restoredLocalConfiguration.streamKeys).isEmpty();
assertThat(restoredLocalConfiguration.subtitleConfigurations).isEmpty();
}
@Test
public void createLocalConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.put("Referer", "http://www.google.com");
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(URI_STRING)
.setMimeType(MimeTypes.APPLICATION_MP4)
.setCustomCacheKey("key")
.setSubtitleConfigurations(
ImmutableList.of(
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(URI_STRING + "/en"))
.setMimeType(MimeTypes.APPLICATION_TTML)
.setLanguage("en")
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
.setRoleFlags(C.ROLE_FLAG_ALTERNATE)
.setLabel("label")
.setId("id")
.build()))
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(Uri.parse(URI_STRING))
.setLicenseRequestHeaders(requestHeaders)
.setMultiSession(true)
.setForceDefaultLicenseUri(true)
.setPlayClearContentWithoutKey(true)
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
.setKeySetId(new byte[] {1, 2, 3})
.build())
.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(Uri.parse(URI_STRING)).build())
.build();
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
MediaItem.LocalConfiguration localConfigurationFromBundle =
MediaItem.LocalConfiguration.CREATOR.fromBundle(localConfiguration.toBundle());
MediaItem.LocalConfiguration localConfigurationFromMediaItemBundle =
MediaItem.CREATOR.fromBundle(mediaItem.toBundleIncludeLocalConfiguration())
.localConfiguration;
assertThat(localConfigurationFromBundle).isEqualTo(localConfiguration);
assertThat(localConfigurationFromMediaItemBundle).isEqualTo(localConfiguration);
}
@Test
public void builderSetLiveConfiguration() { public void builderSetLiveConfiguration() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder() new MediaItem.Builder()
...@@ -894,7 +956,8 @@ public class MediaItemTest { ...@@ -894,7 +956,8 @@ public class MediaItemTest {
} }
@Test @Test
public void roundTripViaBundle_withLocalConfiguration_dropsLocalConfiguration() { public void
roundTripViaDefaultBundle_mediaItemContainsLocalConfiguration_dropsLocalConfiguration() {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build(); MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
assertThat(mediaItem.localConfiguration).isNotNull(); assertThat(mediaItem.localConfiguration).isNotNull();
...@@ -902,6 +965,17 @@ public class MediaItemTest { ...@@ -902,6 +965,17 @@ public class MediaItemTest {
} }
@Test @Test
public void
roundTripViaBundleIncludeLocalConfiguration_mediaItemContainsLocalConfiguration_restoresLocalConfiguration() {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
MediaItem restoredMediaItem =
MediaItem.CREATOR.fromBundle(mediaItem.toBundleIncludeLocalConfiguration());
assertThat(mediaItem.localConfiguration).isNotNull();
assertThat(restoredMediaItem.localConfiguration).isEqualTo(mediaItem.localConfiguration);
}
@Test
public void createDefaultMediaItemInstance_checksDefaultValues() { public void createDefaultMediaItemInstance_checksDefaultValues() {
MediaItem mediaItem = new MediaItem.Builder().build(); MediaItem mediaItem = new MediaItem.Builder().build();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment