Skip to content

Commit

Permalink
Allow playback of MediaItems with LocalConfiguration
Browse files Browse the repository at this point in the history
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 bcddaf2)
  • Loading branch information
oceanjules authored and tof-tof committed Jun 5, 2023
1 parent 6226310 commit 630abd8
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ public Bundle toBundle() {
}

/** Properties for local playback. */
public static final class LocalConfiguration {
public static final class LocalConfiguration implements Bundleable {

/** The {@link Uri}. */
public final Uri uri;
Expand Down Expand Up @@ -1150,6 +1150,79 @@ public int hashCode() {
result = 31 * result + (tag == null ? 0 : tag.hashCode());
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. */
Expand Down Expand Up @@ -2118,15 +2191,9 @@ public int hashCode() {
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_REQUEST_METADATA = Util.intToStringMaxRadix(4);
private static final String FIELD_LOCAL_CONFIGURATION = Util.intToStringMaxRadix(5);

/**
* {@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() {
private Bundle toBundle(boolean includeLocalConfiguration) {
Bundle bundle = new Bundle();
if (!mediaId.equals(DEFAULT_MEDIA_ID)) {
bundle.putString(FIELD_MEDIA_ID, mediaId);
Expand All @@ -2143,9 +2210,31 @@ public Bundle toBundle() {
if (!requestMetadata.equals(RequestMetadata.EMPTY)) {
bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle());
}
if (includeLocalConfiguration && localConfiguration != null) {
bundle.putBundle(FIELD_LOCAL_CONFIGURATION, localConfiguration.toBundle());
}
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}.
*
Expand Down Expand Up @@ -2184,10 +2273,17 @@ private static MediaItem fromBundle(Bundle bundle) {
} else {
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(
mediaId,
clippingConfiguration,
/* localConfiguration= */ null,
localConfiguration,
liveConfiguration,
mediaMetadata,
requestMetadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
Expand All @@ -35,10 +36,21 @@ public final class BundleableUtil {

/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
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();
for (int i = 0; i < bundleableList.size(); i++) {
Bundleable bundleable = bundleableList.get(i);
builder.add(bundleable.toBundle());
T bundleable = bundleableList.get(i);
builder.add(customToBundleFunc.apply(bundleable));
}
return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,68 @@ public void createLiveConfigurationInstance_roundTripViaBundle_yieldsEqualInstan
assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration);
}

@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() {
MediaItem mediaItem =
Expand Down Expand Up @@ -894,13 +956,25 @@ public void roundTripViaBundle_withoutLocalConfiguration_yieldsEqualInstance() {
}

@Test
public void roundTripViaBundle_withLocalConfiguration_dropsLocalConfiguration() {
public void
roundTripViaDefaultBundle_mediaItemContainsLocalConfiguration_dropsLocalConfiguration() {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();

assertThat(mediaItem.localConfiguration).isNotNull();
assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull();
}

@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() {
MediaItem mediaItem = new MediaItem.Builder().build();
Expand Down

0 comments on commit 630abd8

Please sign in to comment.