From cd297b048a044c471d80b5c4a09dfd1b8756bbe4 Mon Sep 17 00:00:00 2001 From: krocard Date: Tue, 17 Aug 2021 20:40:50 +0100 Subject: [PATCH] Make Track selection objects Bundleable Most of those objects needs to be sent to MediaControler. `TrackSelectior.Parameters` could have stayed Parcelable, but it needs to be `Bundleable` as it inherit from `TrackSelectionParameters` that is and needs to be serializable anyway for the demo app. As a result it has also been migrated to bundleable. PiperOrigin-RevId: 391353293 --- .../exoplayer2/demo/PlayerActivity.java | 8 +- .../com/google/android/exoplayer2/Format.java | 305 +++++++++----- .../android/exoplayer2/source/TrackGroup.java | 84 ++-- .../exoplayer2/source/TrackGroupArray.java | 76 ++-- .../TrackSelectionParameters.java | 301 +++++++++----- .../exoplayer2/util/BundleableUtils.java | 60 ++- .../android/exoplayer2/video/ColorInfo.java | 77 ++-- .../google/android/exoplayer2/FormatTest.java | 21 +- .../exoplayer2/ExoPlaybackException.java | 8 +- .../trackselection/DefaultTrackSelector.java | 388 ++++++++++++------ .../source/TrackGroupArrayTest.java | 15 +- .../exoplayer2/source/TrackGroupTest.java | 14 +- .../DefaultTrackSelectorTest.java | 43 +- 13 files changed, 899 insertions(+), 501 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index c918603931..a3e89cd753 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -98,7 +98,7 @@ public class PlayerActivity extends AppCompatActivity // Activity lifecycle. @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); dataSourceFactory = DemoUtil.getDataSourceFactory(/* context= */ this); @@ -114,7 +114,9 @@ public class PlayerActivity extends AppCompatActivity playerView.requestFocus(); if (savedInstanceState != null) { - trackSelectorParameters = savedInstanceState.getParcelable(KEY_TRACK_SELECTOR_PARAMETERS); + trackSelectorParameters = + DefaultTrackSelector.Parameters.CREATOR.fromBundle( + savedInstanceState.getBundle(KEY_TRACK_SELECTOR_PARAMETERS)); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); startWindow = savedInstanceState.getInt(KEY_WINDOW); startPosition = savedInstanceState.getLong(KEY_POSITION); @@ -207,7 +209,7 @@ public class PlayerActivity extends AppCompatActivity super.onSaveInstanceState(outState); updateTrackSelectorParameters(); updateStartPosition(); - outState.putParcelable(KEY_TRACK_SELECTOR_PARAMETERS, trackSelectorParameters); + outState.putBundle(KEY_TRACK_SELECTOR_PARAMETERS, trackSelectorParameters.toBundle()); outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); outState.putInt(KEY_WINDOW, startWindow); outState.putLong(KEY_POSITION, startPosition); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java index eba199299e..6fcc34b11f 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java @@ -15,19 +15,21 @@ */ package com.google.android.exoplayer2; -import static com.google.android.exoplayer2.util.Assertions.checkNotNull; - -import android.os.Parcel; -import android.os.Parcelable; +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.UnsupportedMediaCrypto; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.util.BundleableUtils; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.base.Joiner; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -112,7 +114,7 @@ import java.util.UUID; *
  • {@link #accessibilityChannel} * */ -public final class Format implements Parcelable { +public final class Format implements Bundleable { /** * Builds {@link Format} instances. @@ -610,6 +612,8 @@ public final class Format implements Parcelable { */ public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; + private static final Format DEFAULT = new Builder().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. */ @@ -968,54 +972,6 @@ public final class Format implements Parcelable { } } - // Some fields are deprecated but they're still assigned below. - @SuppressWarnings({"ResourceType"}) - /* package */ Format(Parcel in) { - id = in.readString(); - label = in.readString(); - language = in.readString(); - selectionFlags = in.readInt(); - roleFlags = in.readInt(); - averageBitrate = in.readInt(); - peakBitrate = in.readInt(); - bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate; - codecs = in.readString(); - metadata = in.readParcelable(Metadata.class.getClassLoader()); - // Container specific. - containerMimeType = in.readString(); - // Sample specific. - sampleMimeType = in.readString(); - maxInputSize = in.readInt(); - int initializationDataSize = in.readInt(); - initializationData = new ArrayList<>(initializationDataSize); - for (int i = 0; i < initializationDataSize; i++) { - initializationData.add(checkNotNull(in.createByteArray())); - } - drmInitData = in.readParcelable(DrmInitData.class.getClassLoader()); - subsampleOffsetUs = in.readLong(); - // Video specific. - width = in.readInt(); - height = in.readInt(); - frameRate = in.readFloat(); - rotationDegrees = in.readInt(); - pixelWidthHeightRatio = in.readFloat(); - boolean hasProjectionData = Util.readBoolean(in); - projectionData = hasProjectionData ? in.createByteArray() : null; - stereoMode = in.readInt(); - colorInfo = in.readParcelable(ColorInfo.class.getClassLoader()); - // Audio specific. - channelCount = in.readInt(); - sampleRate = in.readInt(); - pcmEncoding = in.readInt(); - encoderDelay = in.readInt(); - encoderPadding = in.readInt(); - // Text specific. - accessibilityChannel = in.readInt(); - // Provided by source. - // Encrypted content must always have a non-null exoMediaCryptoType. - exoMediaCryptoType = drmInitData != null ? UnsupportedMediaCrypto.class : null; - } - /** Returns a {@link Format.Builder} initialized with the values of this instance. */ public Builder buildUpon() { return new Builder(this); @@ -1371,69 +1327,208 @@ public final class Format implements Parcelable { return builder.toString(); } - // Parcelable implementation. + // Bundleable implementation. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_ID, + FIELD_LABEL, + FIELD_LANGUAGE, + FIELD_SELECTION_FLAGS, + FIELD_ROLE_FLAGS, + FIELD_AVERAGE_BITRATE, + FIELD_PEAK_BITRATE, + FIELD_CODECS, + FIELD_METADATA, + FIELD_CONTAINER_MIME_TYPE, + FIELD_SAMPLE_MIME_TYPE, + FIELD_MAX_INPUT_SIZE, + FIELD_INITIALIZATION_DATA, + FIELD_DRM_INIT_DATA, + FIELD_SUBSAMPLE_OFFSET_US, + FIELD_WIDTH, + FIELD_HEIGHT, + FIELD_FRAME_RATE, + FIELD_ROTATION_DEGREES, + FIELD_PIXEL_WIDTH_HEIGHT_RATIO, + FIELD_PROJECTION_DATA, + FIELD_STEREO_MODE, + FIELD_COLOR_INFO, + FIELD_CHANNEL_COUNT, + FIELD_SAMPLE_RATE, + FIELD_PCM_ENCODING, + FIELD_ENCODER_DELAY, + FIELD_ENCODER_PADDING, + FIELD_ACCESSIBILITY_CHANNEL, + }) + private @interface FieldNumber {} - @Override - public int describeContents() { - return 0; - } + private static final int FIELD_ID = 0; + private static final int FIELD_LABEL = 1; + private static final int FIELD_LANGUAGE = 2; + private static final int FIELD_SELECTION_FLAGS = 3; + private static final int FIELD_ROLE_FLAGS = 4; + private static final int FIELD_AVERAGE_BITRATE = 5; + private static final int FIELD_PEAK_BITRATE = 6; + private static final int FIELD_CODECS = 7; + private static final int FIELD_METADATA = 8; + private static final int FIELD_CONTAINER_MIME_TYPE = 9; + private static final int FIELD_SAMPLE_MIME_TYPE = 10; + private static final int FIELD_MAX_INPUT_SIZE = 11; + private static final int FIELD_INITIALIZATION_DATA = 12; + private static final int FIELD_DRM_INIT_DATA = 13; + private static final int FIELD_SUBSAMPLE_OFFSET_US = 14; + private static final int FIELD_WIDTH = 15; + private static final int FIELD_HEIGHT = 16; + private static final int FIELD_FRAME_RATE = 17; + private static final int FIELD_ROTATION_DEGREES = 18; + private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 19; + private static final int FIELD_PROJECTION_DATA = 20; + private static final int FIELD_STEREO_MODE = 21; + private static final int FIELD_COLOR_INFO = 22; + private static final int FIELD_CHANNEL_COUNT = 23; + private static final int FIELD_SAMPLE_RATE = 24; + private static final int FIELD_PCM_ENCODING = 25; + private static final int FIELD_ENCODER_DELAY = 26; + private static final int FIELD_ENCODER_PADDING = 27; + private static final int FIELD_ACCESSIBILITY_CHANNEL = 28; + /** + * {@inheritDoc} + * + *

    Omits the {@link #exoMediaCryptoType} field. The {@link #exoMediaCryptoType} of an instance + * restored by {@link #CREATOR} will always be {@link UnsupportedMediaCrypto}. + */ @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(id); - dest.writeString(label); - dest.writeString(language); - dest.writeInt(selectionFlags); - dest.writeInt(roleFlags); - dest.writeInt(averageBitrate); - dest.writeInt(peakBitrate); - dest.writeString(codecs); - dest.writeParcelable(metadata, 0); + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putString(keyForField(FIELD_ID), id); + bundle.putString(keyForField(FIELD_LABEL), label); + bundle.putString(keyForField(FIELD_LANGUAGE), language); + bundle.putInt(keyForField(FIELD_SELECTION_FLAGS), selectionFlags); + bundle.putInt(keyForField(FIELD_ROLE_FLAGS), roleFlags); + bundle.putInt(keyForField(FIELD_AVERAGE_BITRATE), averageBitrate); + bundle.putInt(keyForField(FIELD_PEAK_BITRATE), peakBitrate); + bundle.putString(keyForField(FIELD_CODECS), codecs); + // Metadata is currently not Bundleable because Metadata.Entry is an Interface, + // which would be difficult to unbundle in a backward compatible way. + // The entries are additionally of limited usefulness to remote processes. + bundle.putParcelable(keyForField(FIELD_METADATA), metadata); // Container specific. - dest.writeString(containerMimeType); + bundle.putString(keyForField(FIELD_CONTAINER_MIME_TYPE), containerMimeType); // Sample specific. - dest.writeString(sampleMimeType); - dest.writeInt(maxInputSize); - int initializationDataSize = initializationData.size(); - dest.writeInt(initializationDataSize); - for (int i = 0; i < initializationDataSize; i++) { - dest.writeByteArray(initializationData.get(i)); + bundle.putString(keyForField(FIELD_SAMPLE_MIME_TYPE), sampleMimeType); + bundle.putInt(keyForField(FIELD_MAX_INPUT_SIZE), maxInputSize); + for (int i = 0; i < initializationData.size(); i++) { + bundle.putByteArray(keyForInitializationData(i), initializationData.get(i)); } - dest.writeParcelable(drmInitData, 0); - dest.writeLong(subsampleOffsetUs); + // DrmInitData doesn't need to be Bundleable as it's only used in the playing process to + // initialize the decoder. + bundle.putParcelable(keyForField(FIELD_DRM_INIT_DATA), drmInitData); + bundle.putLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), subsampleOffsetUs); // Video specific. - dest.writeInt(width); - dest.writeInt(height); - dest.writeFloat(frameRate); - dest.writeInt(rotationDegrees); - dest.writeFloat(pixelWidthHeightRatio); - Util.writeBoolean(dest, projectionData != null); - if (projectionData != null) { - dest.writeByteArray(projectionData); - } - dest.writeInt(stereoMode); - dest.writeParcelable(colorInfo, flags); + bundle.putInt(keyForField(FIELD_WIDTH), width); + bundle.putInt(keyForField(FIELD_HEIGHT), height); + bundle.putFloat(keyForField(FIELD_FRAME_RATE), frameRate); + bundle.putInt(keyForField(FIELD_ROTATION_DEGREES), rotationDegrees); + bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); + bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData); + bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode); + bundle.putBundle(keyForField(FIELD_COLOR_INFO), BundleableUtils.toNullableBundle(colorInfo)); // Audio specific. - dest.writeInt(channelCount); - dest.writeInt(sampleRate); - dest.writeInt(pcmEncoding); - dest.writeInt(encoderDelay); - dest.writeInt(encoderPadding); + bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount); + bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate); + bundle.putInt(keyForField(FIELD_PCM_ENCODING), pcmEncoding); + bundle.putInt(keyForField(FIELD_ENCODER_DELAY), encoderDelay); + bundle.putInt(keyForField(FIELD_ENCODER_PADDING), encoderPadding); // Text specific. - dest.writeInt(accessibilityChannel); + bundle.putInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), accessibilityChannel); + return bundle; } - public static final Creator CREATOR = - new Creator() { + /** Object that can restore {@code Format} from a {@link Bundle}. */ + public static final Creator CREATOR = Format::fromBundle; - @Override - public Format createFromParcel(Parcel in) { - return new Format(in); - } + private static Format fromBundle(Bundle bundle) { + Builder builder = new Builder(); + BundleableUtils.ensureClassLoader(bundle); + builder + .setId(defaultIfNull(bundle.getString(keyForField(FIELD_ID)), DEFAULT.id)) + .setLabel(defaultIfNull(bundle.getString(keyForField(FIELD_LABEL)), DEFAULT.label)) + .setLanguage(defaultIfNull(bundle.getString(keyForField(FIELD_LANGUAGE)), DEFAULT.language)) + .setSelectionFlags( + bundle.getInt(keyForField(FIELD_SELECTION_FLAGS), DEFAULT.selectionFlags)) + .setRoleFlags(bundle.getInt(keyForField(FIELD_ROLE_FLAGS), DEFAULT.roleFlags)) + .setAverageBitrate( + bundle.getInt(keyForField(FIELD_AVERAGE_BITRATE), DEFAULT.averageBitrate)) + .setPeakBitrate(bundle.getInt(keyForField(FIELD_PEAK_BITRATE), DEFAULT.peakBitrate)) + .setCodecs(defaultIfNull(bundle.getString(keyForField(FIELD_CODECS)), DEFAULT.codecs)) + .setMetadata( + defaultIfNull(bundle.getParcelable(keyForField(FIELD_METADATA)), DEFAULT.metadata)) + // Container specific. + .setContainerMimeType( + defaultIfNull( + bundle.getString(keyForField(FIELD_CONTAINER_MIME_TYPE)), + DEFAULT.containerMimeType)) + // Sample specific. + .setSampleMimeType( + defaultIfNull( + bundle.getString(keyForField(FIELD_SAMPLE_MIME_TYPE)), DEFAULT.sampleMimeType)) + .setMaxInputSize(bundle.getInt(keyForField(FIELD_MAX_INPUT_SIZE), DEFAULT.maxInputSize)); - @Override - public Format[] newArray(int size) { - return new Format[size]; - } - }; + List initializationData = new ArrayList<>(); + for (int i = 0; ; i++) { + @Nullable byte[] data = bundle.getByteArray(keyForInitializationData(i)); + if (data == null) { + break; + } + initializationData.add(data); + } + builder + .setInitializationData(initializationData) + .setDrmInitData(bundle.getParcelable(keyForField(FIELD_DRM_INIT_DATA))) + .setSubsampleOffsetUs( + bundle.getLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), DEFAULT.subsampleOffsetUs)) + // Video specific. + .setWidth(bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT.width)) + .setHeight(bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT.height)) + .setFrameRate(bundle.getFloat(keyForField(FIELD_FRAME_RATE), DEFAULT.frameRate)) + .setRotationDegrees( + bundle.getInt(keyForField(FIELD_ROTATION_DEGREES), DEFAULT.rotationDegrees)) + .setPixelWidthHeightRatio( + bundle.getFloat( + keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio)) + .setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA))) + .setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode)) + .setColorInfo( + BundleableUtils.fromNullableBundle( + ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO)))) + // Audio specific. + .setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount)) + .setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate)) + .setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding)) + .setEncoderDelay(bundle.getInt(keyForField(FIELD_ENCODER_DELAY), DEFAULT.encoderDelay)) + .setEncoderPadding( + bundle.getInt(keyForField(FIELD_ENCODER_PADDING), DEFAULT.encoderPadding)) + // Text specific. + .setAccessibilityChannel( + bundle.getInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), DEFAULT.accessibilityChannel)); + + return builder.build(); + } + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + + private static String keyForInitializationData(int initialisationDataIndex) { + return keyForField(FIELD_INITIALIZATION_DATA) + + "_" + + Integer.toString(initialisationDataIndex, Character.MAX_RADIX); + } + + @Nullable + private static T defaultIfNull(@Nullable T value, @Nullable T defaultValue) { + return value != null ? value : defaultValue; + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index 0888f3fa96..4f9f93fc33 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -15,17 +15,26 @@ */ package com.google.android.exoplayer2.source; -import android.os.Parcel; -import android.os.Parcelable; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; + +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.BundleableUtils; import com.google.android.exoplayer2.util.Log; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.List; /** Defines an immutable group of tracks identified by their format identity. */ -public final class TrackGroup implements Parcelable { +public final class TrackGroup implements Bundleable { private static final String TAG = "TrackGroup"; @@ -37,22 +46,18 @@ public final class TrackGroup implements Parcelable { // Lazily initialized hashcode. private int hashCode; - /** @param formats The track formats. At least one {@link Format} must be provided. */ + /** + * Constructs an instance {@code TrackGroup} containing the provided {@code formats}. + * + * @param formats Non empty array of format. + */ public TrackGroup(Format... formats) { - Assertions.checkState(formats.length > 0); + checkArgument(formats.length > 0); this.formats = formats; this.length = formats.length; verifyCorrectness(); } - /* package */ TrackGroup(Parcel in) { - length = in.readInt(); - formats = new Format[length]; - for (int i = 0; i < length; i++) { - formats[i] = in.readParcelable(Format.class.getClassLoader()); - } - } - /** * Returns the format of the track at a given index. * @@ -103,35 +108,40 @@ public final class TrackGroup implements Parcelable { return length == other.length && Arrays.equals(formats, other.formats); } - // Parcelable implementation. + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_FORMATS, + }) + private @interface FieldNumber {} + + private static final int FIELD_FORMATS = 0; @Override - public int describeContents() { - return 0; + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + keyForField(FIELD_FORMATS), BundleableUtils.toBundleArrayList(Lists.newArrayList(formats))); + return bundle; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(length); - for (int i = 0; i < length; i++) { - dest.writeParcelable(formats[i], 0); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - - @Override - public TrackGroup createFromParcel(Parcel in) { - return new TrackGroup(in); - } - - @Override - public TrackGroup[] newArray(int size) { - return new TrackGroup[size]; - } + /** Object that can restore {@code TrackGroup} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + List formats = + BundleableUtils.fromBundleNullableList( + Format.CREATOR, + bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)), + ImmutableList.of()); + return new TrackGroup(formats.toArray(new Format[0])); }; + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + private void verifyCorrectness() { // TrackGroups should only contain tracks with exactly the same content (but in different // qualities). We only log an error instead of throwing to not break backwards-compatibility for diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java index 8db7b9c385..0af14a5fdc 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java @@ -15,14 +15,22 @@ */ package com.google.android.exoplayer2.source; -import android.os.Parcel; -import android.os.Parcelable; +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.BundleableUtils; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.List; /** An immutable array of {@link TrackGroup}s. */ -public final class TrackGroupArray implements Parcelable { +public final class TrackGroupArray implements Bundleable { /** The empty array. */ public static final TrackGroupArray EMPTY = new TrackGroupArray(); @@ -35,20 +43,12 @@ public final class TrackGroupArray implements Parcelable { // Lazily initialized hashcode. private int hashCode; - /** @param trackGroups The groups. May be empty. */ + /** Construct a {@code TrackGroupArray} from an array of (possibly empty) trackGroups. */ public TrackGroupArray(TrackGroup... trackGroups) { this.trackGroups = trackGroups; this.length = trackGroups.length; } - /* package */ TrackGroupArray(Parcel in) { - length = in.readInt(); - trackGroups = new TrackGroup[length]; - for (int i = 0; i < length; i++) { - trackGroups[i] = in.readParcelable(TrackGroup.class.getClassLoader()); - } - } - /** * Returns the group at a given index. * @@ -102,32 +102,38 @@ public final class TrackGroupArray implements Parcelable { return length == other.length && Arrays.equals(trackGroups, other.trackGroups); } - // Parcelable implementation. + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TRACK_GROUPS, + }) + private @interface FieldNumber {} + + private static final int FIELD_TRACK_GROUPS = 0; @Override - public int describeContents() { - return 0; + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + keyForField(FIELD_TRACK_GROUPS), + BundleableUtils.toBundleArrayList(Lists.newArrayList(trackGroups))); + return bundle; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(length); - for (int i = 0; i < length; i++) { - dest.writeParcelable(trackGroups[i], 0); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - - @Override - public TrackGroupArray createFromParcel(Parcel in) { - return new TrackGroupArray(in); - } - - @Override - public TrackGroupArray[] newArray(int size) { - return new TrackGroupArray[size]; - } + /** Object that can restores a TrackGroupArray from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + List trackGroups = + BundleableUtils.fromBundleNullableList( + TrackGroup.CREATOR, + bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS)), + /* defaultValue= */ ImmutableList.of()); + return new TrackGroupArray(trackGroups.toArray(new TrackGroup[0])); }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index bb8fce5fa4..e62e93893b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -16,23 +16,28 @@ package com.google.android.exoplayer2.trackselection; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.common.base.MoreObjects.firstNonNull; import android.content.Context; import android.graphics.Point; +import android.os.Bundle; import android.os.Looper; -import android.os.Parcel; -import android.os.Parcelable; import android.view.accessibility.CaptioningManager; +import androidx.annotation.CallSuper; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import java.util.ArrayList; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Locale; /** Constraint parameters for track selection. */ -public class TrackSelectionParameters implements Parcelable { +public class TrackSelectionParameters implements Bundleable { /** * A builder for {@link TrackSelectionParameters}. See the {@link TrackSelectionParameters} @@ -108,10 +113,7 @@ public class TrackSelectionParameters implements Parcelable { setViewportSizeToPhysicalDisplaySize(context, /* viewportOrientationMayChange= */ true); } - /** - * @param initialValues The {@link TrackSelectionParameters} from which the initial values of - * the builder are obtained. - */ + /** Creates a builder with the initial values specified in {@code initialValues}. */ protected Builder(TrackSelectionParameters initialValues) { // Video maxVideoWidth = initialValues.maxVideoWidth; @@ -141,6 +143,89 @@ public class TrackSelectionParameters implements Parcelable { forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; } + /** Creates a builder with the initial values specified in {@code bundle}. */ + protected Builder(Bundle bundle) { + // Video + maxVideoWidth = + bundle.getInt(keyForField(FIELD_MAX_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.maxVideoWidth); + maxVideoHeight = + bundle.getInt( + keyForField(FIELD_MAX_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.maxVideoHeight); + maxVideoFrameRate = + bundle.getInt( + keyForField(FIELD_MAX_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate); + maxVideoBitrate = + bundle.getInt( + keyForField(FIELD_MAX_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate); + minVideoWidth = + bundle.getInt(keyForField(FIELD_MIN_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.minVideoWidth); + minVideoHeight = + bundle.getInt( + keyForField(FIELD_MIN_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.minVideoHeight); + minVideoFrameRate = + bundle.getInt( + keyForField(FIELD_MIN_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate); + minVideoBitrate = + bundle.getInt( + keyForField(FIELD_MIN_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.minVideoBitrate); + viewportWidth = + bundle.getInt(keyForField(FIELD_VIEWPORT_WIDTH), DEFAULT_WITHOUT_CONTEXT.viewportWidth); + viewportHeight = + bundle.getInt(keyForField(FIELD_VIEWPORT_HEIGHT), DEFAULT_WITHOUT_CONTEXT.viewportHeight); + viewportOrientationMayChange = + bundle.getBoolean( + keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE), + DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange); + preferredVideoMimeTypes = + ImmutableList.copyOf( + firstNonNull( + bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)), + new String[0])); + // Audio + String[] preferredAudioLanguages1 = + firstNonNull( + bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES)), new String[0]); + preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1); + preferredAudioRoleFlags = + bundle.getInt( + keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS), + DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags); + maxAudioChannelCount = + bundle.getInt( + keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT), + DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount); + maxAudioBitrate = + bundle.getInt( + keyForField(FIELD_MAX_AUDIO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate); + preferredAudioMimeTypes = + ImmutableList.copyOf( + firstNonNull( + bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES)), + new String[0])); + // Text + preferredTextLanguages = + normalizeLanguageCodes( + firstNonNull( + bundle.getStringArray(keyForField(FIELD_PREFERRED_TEXT_LANGUAGES)), + new String[0])); + preferredTextRoleFlags = + bundle.getInt( + keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), + DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags); + selectUndeterminedTextLanguage = + bundle.getBoolean( + keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), + DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage); + // General + forceLowestBitrate = + bundle.getBoolean( + keyForField(FIELD_FORCE_LOWEST_BITRATE), DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate); + forceHighestSupportedBitrate = + bundle.getBoolean( + keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), + DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); + } + // Video /** @@ -322,11 +407,7 @@ public class TrackSelectionParameters implements Parcelable { * @return This builder. */ public Builder setPreferredAudioLanguages(String... preferredAudioLanguages) { - ImmutableList.Builder listBuilder = ImmutableList.builder(); - for (String language : checkNotNull(preferredAudioLanguages)) { - listBuilder.add(Util.normalizeLanguageCode(checkNotNull(language))); - } - this.preferredAudioLanguages = listBuilder.build(); + this.preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages); return this; } @@ -427,11 +508,7 @@ public class TrackSelectionParameters implements Parcelable { * @return This builder. */ public Builder setPreferredTextLanguages(String... preferredTextLanguages) { - ImmutableList.Builder listBuilder = ImmutableList.builder(); - for (String language : checkNotNull(preferredTextLanguages)) { - listBuilder.add(Util.normalizeLanguageCode(checkNotNull(language))); - } - this.preferredTextLanguages = listBuilder.build(); + this.preferredTextLanguages = normalizeLanguageCodes(preferredTextLanguages); return this; } @@ -512,6 +589,14 @@ public class TrackSelectionParameters implements Parcelable { preferredTextLanguages = ImmutableList.of(Util.getLocaleLanguageTag(preferredLocale)); } } + + private static ImmutableList normalizeLanguageCodes(String[] preferredTextLanguages) { + ImmutableList.Builder listBuilder = ImmutableList.builder(); + for (String language : checkNotNull(preferredTextLanguages)) { + listBuilder.add(Util.normalizeLanguageCode(checkNotNull(language))); + } + return listBuilder.build(); + } } /** @@ -537,20 +622,6 @@ public class TrackSelectionParameters implements Parcelable { */ @Deprecated public static final TrackSelectionParameters DEFAULT = DEFAULT_WITHOUT_CONTEXT; - public static final Creator CREATOR = - new Creator() { - - @Override - public TrackSelectionParameters createFromParcel(Parcel in) { - return new TrackSelectionParameters(in); - } - - @Override - public TrackSelectionParameters[] newArray(int size) { - return new TrackSelectionParameters[size]; - } - }; - /** Returns an instance configured with default values. */ public static TrackSelectionParameters getDefaults(Context context) { return new Builder(context).build(); @@ -707,42 +778,6 @@ public class TrackSelectionParameters implements Parcelable { this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate; } - /* package */ TrackSelectionParameters(Parcel in) { - ArrayList preferredAudioLanguages = new ArrayList<>(); - in.readList(preferredAudioLanguages, /* loader= */ null); - this.preferredAudioLanguages = ImmutableList.copyOf(preferredAudioLanguages); - this.preferredAudioRoleFlags = in.readInt(); - ArrayList preferredTextLanguages1 = new ArrayList<>(); - in.readList(preferredTextLanguages1, /* loader= */ null); - this.preferredTextLanguages = ImmutableList.copyOf(preferredTextLanguages1); - this.preferredTextRoleFlags = in.readInt(); - this.selectUndeterminedTextLanguage = Util.readBoolean(in); - // Video - this.maxVideoWidth = in.readInt(); - this.maxVideoHeight = in.readInt(); - this.maxVideoFrameRate = in.readInt(); - this.maxVideoBitrate = in.readInt(); - this.minVideoWidth = in.readInt(); - this.minVideoHeight = in.readInt(); - this.minVideoFrameRate = in.readInt(); - this.minVideoBitrate = in.readInt(); - this.viewportWidth = in.readInt(); - this.viewportHeight = in.readInt(); - this.viewportOrientationMayChange = Util.readBoolean(in); - ArrayList preferredVideoMimeTypes = new ArrayList<>(); - in.readList(preferredVideoMimeTypes, /* loader= */ null); - this.preferredVideoMimeTypes = ImmutableList.copyOf(preferredVideoMimeTypes); - // Audio - this.maxAudioChannelCount = in.readInt(); - this.maxAudioBitrate = in.readInt(); - ArrayList preferredAudioMimeTypes = new ArrayList<>(); - in.readList(preferredAudioMimeTypes, /* loader= */ null); - this.preferredAudioMimeTypes = ImmutableList.copyOf(preferredAudioMimeTypes); - // General - this.forceLowestBitrate = Util.readBoolean(in); - this.forceHighestSupportedBitrate = Util.readBoolean(in); - } - /** Creates a new {@link Builder}, copying the initial values from this instance. */ public Builder buildUpon() { return new Builder(this); @@ -817,39 +852,109 @@ public class TrackSelectionParameters implements Parcelable { return result; } - // Parcelable implementation. + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_PREFERRED_AUDIO_LANGUAGES, + FIELD_PREFERRED_AUDIO_ROLE_FLAGS, + FIELD_PREFERRED_TEXT_LANGUAGES, + FIELD_PREFERRED_TEXT_ROLE_FLAGS, + FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, + FIELD_MAX_VIDEO_WIDTH, + FIELD_MAX_VIDEO_HEIGHT, + FIELD_MAX_VIDEO_FRAMERATE, + FIELD_MAX_VIDEO_BITRATE, + FIELD_MIN_VIDEO_WIDTH, + FIELD_MIN_VIDEO_HEIGHT, + FIELD_MIN_VIDEO_FRAMERATE, + FIELD_MIN_VIDEO_BITRATE, + FIELD_VIEWPORT_WIDTH, + FIELD_VIEWPORT_HEIGHT, + FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, + FIELD_PREFERRED_VIDEO_MIMETYPES, + FIELD_MAX_AUDIO_CHANNEL_COUNT, + FIELD_MAX_AUDIO_BITRATE, + FIELD_PREFERRED_AUDIO_MIME_TYPES, + FIELD_FORCE_LOWEST_BITRATE, + FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, + }) + private @interface FieldNumber {} + + private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1; + private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2; + private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3; + private static final int FIELD_PREFERRED_TEXT_ROLE_FLAGS = 4; + private static final int FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = 5; + private static final int FIELD_MAX_VIDEO_WIDTH = 6; + private static final int FIELD_MAX_VIDEO_HEIGHT = 7; + private static final int FIELD_MAX_VIDEO_FRAMERATE = 8; + private static final int FIELD_MAX_VIDEO_BITRATE = 9; + private static final int FIELD_MIN_VIDEO_WIDTH = 10; + private static final int FIELD_MIN_VIDEO_HEIGHT = 11; + private static final int FIELD_MIN_VIDEO_FRAMERATE = 12; + private static final int FIELD_MIN_VIDEO_BITRATE = 13; + private static final int FIELD_VIEWPORT_WIDTH = 14; + private static final int FIELD_VIEWPORT_HEIGHT = 15; + private static final int FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = 16; + private static final int FIELD_PREFERRED_VIDEO_MIMETYPES = 17; + private static final int FIELD_MAX_AUDIO_CHANNEL_COUNT = 18; + private static final int FIELD_MAX_AUDIO_BITRATE = 19; + private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20; + private static final int FIELD_FORCE_LOWEST_BITRATE = 21; + private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22; @Override - public int describeContents() { - return 0; + @CallSuper + public Bundle toBundle() { + Bundle bundle = new Bundle(); + + // Video + bundle.putInt(keyForField(FIELD_MAX_VIDEO_WIDTH), maxVideoWidth); + bundle.putInt(keyForField(FIELD_MAX_VIDEO_HEIGHT), maxVideoHeight); + bundle.putInt(keyForField(FIELD_MAX_VIDEO_FRAMERATE), maxVideoFrameRate); + bundle.putInt(keyForField(FIELD_MAX_VIDEO_BITRATE), maxVideoBitrate); + bundle.putInt(keyForField(FIELD_MIN_VIDEO_WIDTH), minVideoWidth); + bundle.putInt(keyForField(FIELD_MIN_VIDEO_HEIGHT), minVideoHeight); + bundle.putInt(keyForField(FIELD_MIN_VIDEO_FRAMERATE), minVideoFrameRate); + bundle.putInt(keyForField(FIELD_MIN_VIDEO_BITRATE), minVideoBitrate); + bundle.putInt(keyForField(FIELD_VIEWPORT_WIDTH), viewportWidth); + bundle.putInt(keyForField(FIELD_VIEWPORT_HEIGHT), viewportHeight); + bundle.putBoolean( + keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE), viewportOrientationMayChange); + bundle.putStringArray( + keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES), + preferredVideoMimeTypes.toArray(new String[0])); + // Audio + bundle.putStringArray( + keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES), + preferredAudioLanguages.toArray(new String[0])); + bundle.putInt(keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS), preferredAudioRoleFlags); + bundle.putInt(keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT), maxAudioChannelCount); + bundle.putInt(keyForField(FIELD_MAX_AUDIO_BITRATE), maxAudioBitrate); + bundle.putStringArray( + keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES), + preferredAudioMimeTypes.toArray(new String[0])); + // Text + bundle.putStringArray( + keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0])); + bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags); + bundle.putBoolean( + keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage); + // General + bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate); + bundle.putBoolean( + keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate); + + return bundle; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeList(preferredAudioLanguages); - dest.writeInt(preferredAudioRoleFlags); - dest.writeList(preferredTextLanguages); - dest.writeInt(preferredTextRoleFlags); - Util.writeBoolean(dest, selectUndeterminedTextLanguage); - // Video - dest.writeInt(maxVideoWidth); - dest.writeInt(maxVideoHeight); - dest.writeInt(maxVideoFrameRate); - dest.writeInt(maxVideoBitrate); - dest.writeInt(minVideoWidth); - dest.writeInt(minVideoHeight); - dest.writeInt(minVideoFrameRate); - dest.writeInt(minVideoBitrate); - dest.writeInt(viewportWidth); - dest.writeInt(viewportHeight); - Util.writeBoolean(dest, viewportOrientationMayChange); - dest.writeList(preferredVideoMimeTypes); - // Audio - dest.writeInt(maxAudioChannelCount); - dest.writeInt(maxAudioBitrate); - dest.writeList(preferredAudioMimeTypes); - // General - Util.writeBoolean(dest, forceLowestBitrate); - Util.writeBoolean(dest, forceHighestSupportedBitrate); + /** Object that can restore {@code TrackSelectionParameters} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> new Builder(bundle).build(); + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/BundleableUtils.java b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleableUtils.java index adc9514f7d..1a29691dd8 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/BundleableUtils.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/BundleableUtils.java @@ -15,7 +15,11 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.castNonNull; + import android.os.Bundle; +import android.util.SparseArray; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.common.collect.ImmutableList; @@ -68,13 +72,41 @@ public final class BundleableUtils { Bundleable.Creator creator, List bundleList) { ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < bundleList.size(); i++) { - Bundle bundle = bundleList.get(i); + Bundle bundle = checkNotNull(bundleList.get(i)); // Fail fast during parsing. T bundleable = creator.fromBundle(bundle); builder.add(bundleable); } return builder.build(); } + /** + * Converts a list of {@link Bundle} to a list of {@link Bundleable}. Returns {@code defaultValue} + * if {@code bundleList} is null. + */ + public static List fromBundleNullableList( + Bundleable.Creator creator, @Nullable List bundleList, List defaultValue) { + return (bundleList == null) ? defaultValue : fromBundleList(creator, bundleList); + } + + /** + * Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link + * Bundleable}. Returns {@code defaultValue} if {@code bundleSparseArray} is null. + */ + public static SparseArray fromBundleNullableSparseArray( + Bundleable.Creator creator, + @Nullable SparseArray bundleSparseArray, + SparseArray defaultValue) { + if (bundleSparseArray == null) { + return defaultValue; + } + // Can't use ImmutableList as it doesn't support null elements. + SparseArray result = new SparseArray<>(bundleSparseArray.size()); + for (int i = 0; i < bundleSparseArray.size(); i++) { + result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i))); + } + return result; + } + /** * Converts a list of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that the * returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList} @@ -88,5 +120,31 @@ public final class BundleableUtils { return arrayList; } + /** + * Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link + * Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link + * Bundle#putSparseParcelableArray} conveniently. + */ + public static SparseArray toBundleSparseArray( + SparseArray bundleableSparseArray) { + SparseArray sparseArray = new SparseArray<>(bundleableSparseArray.size()); + for (int i = 0; i < bundleableSparseArray.size(); i++) { + sparseArray.put(bundleableSparseArray.keyAt(i), bundleableSparseArray.valueAt(i).toBundle()); + } + return sparseArray; + } + + /** + * Set the application class loader to the given {@link Bundle} if no class loader is present. + * + *

    This assumes that all classes unparceled from {@code bundle} are sharing the class loader of + * {@code BundleableUtils}. + */ + public static void ensureClassLoader(@Nullable Bundle bundle) { + if (bundle != null) { + bundle.setClassLoader(castNonNull(BundleableUtils.class.getClassLoader())); + } + } + private BundleableUtils() {} } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java index 38fe968668..17ca09a747 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java @@ -15,17 +15,20 @@ */ package com.google.android.exoplayer2.video; -import android.os.Parcel; -import android.os.Parcelable; +import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import org.checkerframework.dataflow.qual.Pure; /** Stores color info. */ -public final class ColorInfo implements Parcelable { +public final class ColorInfo implements Bundleable { /** * Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per @@ -116,16 +119,6 @@ public final class ColorInfo implements Parcelable { this.hdrStaticInfo = hdrStaticInfo; } - @SuppressWarnings("ResourceType") - /* package */ ColorInfo(Parcel in) { - colorSpace = in.readInt(); - colorRange = in.readInt(); - colorTransfer = in.readInt(); - boolean hasHdrStaticInfo = Util.readBoolean(in); - hdrStaticInfo = hasHdrStaticInfo ? in.createByteArray() : null; - } - - // Parcelable implementation. @Override public boolean equals(@Nullable Object obj) { if (this == obj) { @@ -167,32 +160,42 @@ public final class ColorInfo implements Parcelable { return hashCode; } - @Override - public int describeContents() { - return 0; - } + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_COLOR_SPACE, + FIELD_COLOR_RANGE, + FIELD_COLOR_TRANSFER, + FIELD_HDR_STATIC_INFO, + }) + private @interface FieldNumber {} + + private static final int FIELD_COLOR_SPACE = 0; + private static final int FIELD_COLOR_RANGE = 1; + private static final int FIELD_COLOR_TRANSFER = 2; + private static final int FIELD_HDR_STATIC_INFO = 3; @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(colorSpace); - dest.writeInt(colorRange); - dest.writeInt(colorTransfer); - Util.writeBoolean(dest, hdrStaticInfo != null); - if (hdrStaticInfo != null) { - dest.writeByteArray(hdrStaticInfo); - } + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_COLOR_SPACE), colorSpace); + bundle.putInt(keyForField(FIELD_COLOR_RANGE), colorRange); + bundle.putInt(keyForField(FIELD_COLOR_TRANSFER), colorTransfer); + bundle.putByteArray(keyForField(FIELD_HDR_STATIC_INFO), hdrStaticInfo); + return bundle; } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public ColorInfo createFromParcel(Parcel in) { - return new ColorInfo(in); - } + public static final Creator CREATOR = + bundle -> + new ColorInfo( + bundle.getInt(keyForField(FIELD_COLOR_SPACE), Format.NO_VALUE), + bundle.getInt(keyForField(FIELD_COLOR_RANGE), Format.NO_VALUE), + bundle.getInt(keyForField(FIELD_COLOR_TRANSFER), Format.NO_VALUE), + bundle.getByteArray(keyForField(FIELD_HDR_STATIC_INFO))); - @Override - public ColorInfo[] newArray(int size) { - return new ColorInfo[size]; - } - }; + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java index 1ad888c868..87fc725029 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java @@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_MP4; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_WEBM; import static com.google.common.truth.Truth.assertThat; -import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.ExoMediaCrypto; @@ -46,21 +45,15 @@ public final class FormatTest { } @Test - public void parcelFormat_createsEqualFormat_exceptExoMediaCryptoType() { - Format formatToParcel = createTestFormat(); + public void roundTripViaBundle_ofParameters_yieldsEqualInstanceExceptExoMediaCryptoType() { + Format formatToBundle = createTestFormat(); - Parcel parcel = Parcel.obtain(); - formatToParcel.writeToParcel(parcel, 0); - parcel.setDataPosition(0); + Format formatFromBundle = Format.CREATOR.fromBundle(formatToBundle.toBundle()); - Format formatFromParcel = Format.CREATOR.createFromParcel(parcel); - Format expectedFormat = - formatToParcel.buildUpon().setExoMediaCryptoType(UnsupportedMediaCrypto.class).build(); - - assertThat(formatFromParcel.exoMediaCryptoType).isEqualTo(UnsupportedMediaCrypto.class); - assertThat(formatFromParcel).isEqualTo(expectedFormat); - - parcel.recycle(); + assertThat(formatFromBundle.exoMediaCryptoType).isEqualTo(UnsupportedMediaCrypto.class); + assertThat(formatFromBundle) + .isEqualTo( + formatToBundle.buildUpon().setExoMediaCryptoType(UnsupportedMediaCrypto.class).build()); } private static Format createTestFormat() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index 2d78d4409d..778999d2cb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.C.FormatSupport; import com.google.android.exoplayer2.source.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.BundleableUtils; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Documented; @@ -234,7 +235,9 @@ public final class ExoPlaybackException extends PlaybackException { rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME)); rendererIndex = bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET); - rendererFormat = bundle.getParcelable(keyForField(FIELD_RENDERER_FORMAT)); + rendererFormat = + BundleableUtils.fromNullableBundle( + Format.CREATOR, bundle.getBundle(keyForField(FIELD_RENDERER_FORMAT))); rendererFormatSupport = bundle.getInt( keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED); @@ -396,7 +399,8 @@ public final class ExoPlaybackException extends PlaybackException { bundle.putInt(keyForField(FIELD_TYPE), type); bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName); bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex); - bundle.putParcelable(keyForField(FIELD_RENDERER_FORMAT), rendererFormat); + bundle.putBundle( + keyForField(FIELD_RENDERER_FORMAT), BundleableUtils.toNullableBundle(rendererFormat)); bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport); bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable); return bundle; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 91c4a79d3a..cca7bc1873 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -17,13 +17,14 @@ package com.google.android.exoplayer2.trackselection; import android.content.Context; import android.graphics.Point; -import android.os.Parcel; -import android.os.Parcelable; +import android.os.Bundle; import android.text.TextUtils; import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.FormatSupport; import com.google.android.exoplayer2.ExoPlaybackException; @@ -39,11 +40,15 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.BundleableUtils; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.common.primitives.Ints; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -239,6 +244,66 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererDisabledFlags = initialValues.rendererDisabledFlags.clone(); } + @SuppressWarnings("method.invocation") // Only setter are invoked. + private ParametersBuilder(Bundle bundle) { + super(bundle); + Parameters defaultValue = Parameters.DEFAULT_WITHOUT_CONTEXT; + // Video + setExceedVideoConstraintsIfNecessary( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY), + defaultValue.exceedVideoConstraintsIfNecessary)); + setAllowVideoMixedMimeTypeAdaptiveness( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS), + defaultValue.allowVideoMixedMimeTypeAdaptiveness)); + setAllowVideoNonSeamlessAdaptiveness( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), + defaultValue.allowVideoNonSeamlessAdaptiveness)); + // Audio + setExceedAudioConstraintsIfNecessary( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY), + defaultValue.exceedAudioConstraintsIfNecessary)); + setAllowAudioMixedMimeTypeAdaptiveness( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS), + defaultValue.allowAudioMixedMimeTypeAdaptiveness)); + setAllowAudioMixedSampleRateAdaptiveness( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS), + defaultValue.allowAudioMixedSampleRateAdaptiveness)); + setAllowAudioMixedChannelCountAdaptiveness( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), + defaultValue.allowAudioMixedChannelCountAdaptiveness)); + // Text + setDisabledTextTrackSelectionFlags( + bundle.getInt( + Parameters.keyForField(Parameters.FIELD_DISABLED_TEXT_TRACK_SELECTION_FLAGS), + defaultValue.disabledTextTrackSelectionFlags)); + // General + setExceedRendererCapabilitiesIfNecessary( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY), + defaultValue.exceedRendererCapabilitiesIfNecessary)); + setTunnelingEnabled( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_TUNNELING_ENABLED), + defaultValue.tunnelingEnabled)); + setAllowMultipleAdaptiveSelections( + bundle.getBoolean( + Parameters.keyForField(Parameters.FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS), + defaultValue.allowMultipleAdaptiveSelections)); + + selectionOverrides = new SparseArray<>(); + setSelectionOverridesFromBundle(bundle); + + rendererDisabledFlags = new SparseBooleanArray(); + setRendererDisableFlagsFromBundle(bundle); + } + // Video @Override @@ -724,12 +789,52 @@ public class DefaultTrackSelector extends MappingTrackSelector { } return clone; } + + private void setSelectionOverridesFromBundle(Bundle bundle) { + @Nullable + int[] rendererIndexes = + bundle.getIntArray( + Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_RENDERER_INDEXES)); + List trackGroupArrays = + BundleableUtils.fromBundleNullableList( + TrackGroupArray.CREATOR, + bundle.getParcelableArrayList( + Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS)), + /* defaultValue= */ ImmutableList.of()); + SparseArray selectionOverrides = + BundleableUtils.fromBundleNullableSparseArray( + SelectionOverride.CREATOR, + bundle.getSparseParcelableArray( + Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES)), + /* defaultValue= */ new SparseArray<>()); + + if (rendererIndexes == null || rendererIndexes.length != trackGroupArrays.size()) { + return; // Incorrect format, ignore all overrides. + } + for (int i = 0; i < rendererIndexes.length; i++) { + int rendererIndex = rendererIndexes[i]; + TrackGroupArray groups = trackGroupArrays.get(i); + @Nullable SelectionOverride selectionOverride = selectionOverrides.get(i); + setSelectionOverride(rendererIndex, groups, selectionOverride); + } + } + + private void setRendererDisableFlagsFromBundle(Bundle bundle) { + int[] rendererIndexes = + bundle.getIntArray(Parameters.keyForField(Parameters.FIELD_RENDERER_DISABLED_INDEXES)); + if (rendererIndexes == null) { + return; + } + for (int rendererIndex : rendererIndexes) { + setRendererDisabled(rendererIndex, true); + } + } } /** * Extends {@link Parameters} by adding fields that are specific to {@link DefaultTrackSelector}. */ - public static final class Parameters extends TrackSelectionParameters implements Parcelable { + public static final class Parameters extends TrackSelectionParameters implements Bundleable { /** * An instance with default values, except those obtained from the {@link Context}. @@ -755,19 +860,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { */ @Deprecated public static final Parameters DEFAULT = DEFAULT_WITHOUT_CONTEXT; - public static final Creator CREATOR = - new Creator() { - - @Override - public Parameters createFromParcel(Parcel in) { - return new Parameters(in); - } - - @Override - public Parameters[] newArray(int size) { - return new Parameters[size]; - } - }; /** * Bitmask of selection flags that are disabled for text track selections. See {@link * C.SelectionFlags}. The default value is {@code 0} (i.e. no flags). @@ -868,28 +960,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererDisabledFlags = builder.rendererDisabledFlags; } - /* package */ Parameters(Parcel in) { - super(in); - // Video - this.exceedVideoConstraintsIfNecessary = Util.readBoolean(in); - this.allowVideoMixedMimeTypeAdaptiveness = Util.readBoolean(in); - this.allowVideoNonSeamlessAdaptiveness = Util.readBoolean(in); - // Audio - this.exceedAudioConstraintsIfNecessary = Util.readBoolean(in); - this.allowAudioMixedMimeTypeAdaptiveness = Util.readBoolean(in); - this.allowAudioMixedSampleRateAdaptiveness = Util.readBoolean(in); - this.allowAudioMixedChannelCountAdaptiveness = Util.readBoolean(in); - // Text - this.disabledTextTrackSelectionFlags = in.readInt(); - // General - this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); - this.tunnelingEnabled = Util.readBoolean(in); - this.allowMultipleAdaptiveSelections = Util.readBoolean(in); - // Overrides - this.selectionOverrides = readSelectionOverrides(in); - this.rendererDisabledFlags = Util.castNonNull(in.readSparseBooleanArray()); - } - /** * Returns whether the renderer is disabled. * @@ -928,6 +998,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** Creates a new {@link ParametersBuilder}, copying the initial values from this instance. */ + @Override public ParametersBuilder buildUpon() { return new ParametersBuilder(this); } @@ -987,80 +1058,141 @@ public class DefaultTrackSelector extends MappingTrackSelector { return result; } - // Parcelable implementation. + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY, + FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS, + FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS, + FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY, + FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS, + FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS, + FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS, + FIELD_DISABLED_TEXT_TRACK_SELECTION_FLAGS, + FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY, + FIELD_TUNNELING_ENABLED, + FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS, + FIELD_SELECTION_OVERRIDES_RENDERER_INDEXES, + FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS, + FIELD_SELECTION_OVERRIDES, + FIELD_RENDERER_DISABLED_INDEXES, + }) + private @interface FieldNumber {} + + // Start at 1000 to avoid conflict with the base class fields. + private static final int FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY = 1000; + private static final int FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS = 1001; + private static final int FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS = 1002; + private static final int FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY = 1003; + private static final int FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS = 1004; + private static final int FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS = 1005; + private static final int FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS = 1006; + private static final int FIELD_DISABLED_TEXT_TRACK_SELECTION_FLAGS = 1007; + private static final int FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY = 1008; + private static final int FIELD_TUNNELING_ENABLED = 1009; + private static final int FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS = 1010; + private static final int FIELD_SELECTION_OVERRIDES_RENDERER_INDEXES = 1011; + private static final int FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS = 1012; + private static final int FIELD_SELECTION_OVERRIDES = 1013; + private static final int FIELD_RENDERER_DISABLED_INDEXES = 1014; @Override - public int describeContents() { - return 0; - } + public Bundle toBundle() { + Bundle bundle = super.toBundle(); - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); // Video - Util.writeBoolean(dest, exceedVideoConstraintsIfNecessary); - Util.writeBoolean(dest, allowVideoMixedMimeTypeAdaptiveness); - Util.writeBoolean(dest, allowVideoNonSeamlessAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY), + exceedVideoConstraintsIfNecessary); + bundle.putBoolean( + keyForField(FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS), + allowVideoMixedMimeTypeAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), + allowVideoNonSeamlessAdaptiveness); // Audio - Util.writeBoolean(dest, exceedAudioConstraintsIfNecessary); - Util.writeBoolean(dest, allowAudioMixedMimeTypeAdaptiveness); - Util.writeBoolean(dest, allowAudioMixedSampleRateAdaptiveness); - Util.writeBoolean(dest, allowAudioMixedChannelCountAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY), + exceedAudioConstraintsIfNecessary); + bundle.putBoolean( + keyForField(FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS), + allowAudioMixedMimeTypeAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS), + allowAudioMixedSampleRateAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), + allowAudioMixedChannelCountAdaptiveness); // Text - dest.writeInt(disabledTextTrackSelectionFlags); + bundle.putInt( + keyForField(FIELD_DISABLED_TEXT_TRACK_SELECTION_FLAGS), disabledTextTrackSelectionFlags); // General - Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); - Util.writeBoolean(dest, tunnelingEnabled); - Util.writeBoolean(dest, allowMultipleAdaptiveSelections); - // Overrides - writeSelectionOverridesToParcel(dest, selectionOverrides); - dest.writeSparseBooleanArray(rendererDisabledFlags); + bundle.putBoolean( + keyForField(FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY), + exceedRendererCapabilitiesIfNecessary); + bundle.putBoolean(keyForField(FIELD_TUNNELING_ENABLED), tunnelingEnabled); + bundle.putBoolean( + keyForField(FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS), allowMultipleAdaptiveSelections); + + putSelectionOverridesToBundle(bundle, selectionOverrides); + putRendererDisabledFlagsToBundle(bundle, rendererDisabledFlags); + + return bundle; } - // Static utility methods. + /** Object that can restore {@code Parameters} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> new ParametersBuilder(bundle).build(); - private static SparseArray> - readSelectionOverrides(Parcel in) { - int renderersWithOverridesCount = in.readInt(); - SparseArray> selectionOverrides = - new SparseArray<>(renderersWithOverridesCount); - for (int i = 0; i < renderersWithOverridesCount; i++) { - int rendererIndex = in.readInt(); - int overrideCount = in.readInt(); - Map overrides = - new HashMap<>(overrideCount); - for (int j = 0; j < overrideCount; j++) { - TrackGroupArray trackGroups = - Assertions.checkNotNull(in.readParcelable(TrackGroupArray.class.getClassLoader())); - @Nullable - SelectionOverride override = in.readParcelable(SelectionOverride.class.getClassLoader()); - overrides.put(trackGroups, override); - } - selectionOverrides.put(rendererIndex, overrides); - } - return selectionOverrides; + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); } - private static void writeSelectionOverridesToParcel( - Parcel dest, + /** + * Bundles selection overrides in 3 arrays of equal length. Each triplet of matching index is - + * the selection override (stored in a sparse array as they can be null) - the trackGroupArray + * of that override - the rendererIndex of that override + */ + private static void putSelectionOverridesToBundle( + Bundle bundle, SparseArray> selectionOverrides) { - int renderersWithOverridesCount = selectionOverrides.size(); - dest.writeInt(renderersWithOverridesCount); - for (int i = 0; i < renderersWithOverridesCount; i++) { + ArrayList rendererIndexes = new ArrayList<>(); + ArrayList trackGroupArrays = new ArrayList<>(); + SparseArray selections = new SparseArray<>(); + + for (int i = 0; i < selectionOverrides.size(); i++) { int rendererIndex = selectionOverrides.keyAt(i); - Map overrides = - selectionOverrides.valueAt(i); - int overrideCount = overrides.size(); - dest.writeInt(rendererIndex); - dest.writeInt(overrideCount); for (Map.Entry override : - overrides.entrySet()) { - dest.writeParcelable(override.getKey(), /* parcelableFlags= */ 0); - dest.writeParcelable(override.getValue(), /* parcelableFlags= */ 0); + selectionOverrides.valueAt(i).entrySet()) { + @Nullable SelectionOverride selection = override.getValue(); + if (selection != null) { + selections.put(trackGroupArrays.size(), selection); + } + trackGroupArrays.add(override.getKey()); + rendererIndexes.add(rendererIndex); } + bundle.putIntArray( + keyForField(FIELD_SELECTION_OVERRIDES_RENDERER_INDEXES), Ints.toArray(rendererIndexes)); + bundle.putParcelableArrayList( + keyForField(FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS), + BundleableUtils.toBundleArrayList(trackGroupArrays)); + bundle.putSparseParcelableArray( + keyForField(FIELD_SELECTION_OVERRIDES), + BundleableUtils.toBundleSparseArray(selections)); } } + private static void putRendererDisabledFlagsToBundle( + Bundle bundle, SparseBooleanArray rendererDisabledFlags) { + int[] rendererIndexes = new int[rendererDisabledFlags.size()]; + for (int i = 0; i < rendererDisabledFlags.size(); i++) { + rendererIndexes[i] = rendererDisabledFlags.keyAt(i); + } + bundle.putIntArray(keyForField(FIELD_RENDERER_DISABLED_INDEXES), rendererIndexes); + } + private static boolean areRendererDisabledFlagsEqual( SparseBooleanArray first, SparseBooleanArray second) { int firstSize = first.size(); @@ -1113,7 +1245,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** A track selection override. */ - public static final class SelectionOverride implements Parcelable { + public static final class SelectionOverride implements Bundleable { public final int groupIndex; public final int[] tracks; @@ -1121,6 +1253,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { public final int type; /** + * Constructs a {@code SelectionOverride} to override tracks of a group. + * * @param groupIndex The overriding track group index. * @param tracks The overriding track indices within the track group. */ @@ -1129,6 +1263,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** + * Constructs a {@code SelectionOverride} of the given type to override tracks of a group. + * * @param groupIndex The overriding track group index. * @param tracks The overriding track indices within the track group. * @param type The type that will be returned from {@link TrackSelection#getType()}. @@ -1141,14 +1277,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { Arrays.sort(this.tracks); } - /* package */ SelectionOverride(Parcel in) { - groupIndex = in.readInt(); - length = in.readByte(); - tracks = new int[length]; - in.readIntArray(tracks); - type = in.readInt(); - } - /** Returns whether this override contains the specified track index. */ public boolean containsTrack(int track) { for (int overrideTrack : tracks) { @@ -1179,34 +1307,44 @@ public class DefaultTrackSelector extends MappingTrackSelector { && type == other.type; } - // Parcelable implementation. + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_GROUP_INDEX, + FIELD_TRACKS, + FIELD_TRACK_TYPE, + }) + private @interface FieldNumber {} + + private static final int FIELD_GROUP_INDEX = 0; + private static final int FIELD_TRACKS = 1; + private static final int FIELD_TRACK_TYPE = 2; @Override - public int describeContents() { - return 0; + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_GROUP_INDEX), groupIndex); + bundle.putIntArray(keyForField(FIELD_TRACKS), tracks); + bundle.putInt(keyForField(FIELD_TRACK_TYPE), type); + return bundle; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(groupIndex); - dest.writeInt(tracks.length); - dest.writeIntArray(tracks); - dest.writeInt(type); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - - @Override - public SelectionOverride createFromParcel(Parcel in) { - return new SelectionOverride(in); - } - - @Override - public SelectionOverride[] newArray(int size) { - return new SelectionOverride[size]; - } + /** Object that can restore {@code SelectionOverride} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + int groupIndex = bundle.getInt(keyForField(FIELD_GROUP_INDEX), -1); + @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); + int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), -1); + Assertions.checkArgument(groupIndex >= 0 && trackType >= 0); + Assertions.checkNotNull(tracks); + return new SelectionOverride(groupIndex, tracks, trackType); }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java index 75653e86b6..c6794d06e6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source; import static com.google.common.truth.Truth.assertThat; -import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.MimeTypes; @@ -29,7 +28,7 @@ import org.junit.runner.RunWith; public final class TrackGroupArrayTest { @Test - public void parcelable() { + public void roundTripViaBundle_ofTrackGroupArray_yieldsEqualInstance() { Format.Builder formatBuilder = new Format.Builder(); Format format1 = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build(); Format format2 = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_AAC).build(); @@ -38,15 +37,11 @@ public final class TrackGroupArrayTest { TrackGroup trackGroup1 = new TrackGroup(format1, format2); TrackGroup trackGroup2 = new TrackGroup(format3); - TrackGroupArray trackGroupArrayToParcel = new TrackGroupArray(trackGroup1, trackGroup2); + TrackGroupArray trackGroupArrayToBundle = new TrackGroupArray(trackGroup1, trackGroup2); - Parcel parcel = Parcel.obtain(); - trackGroupArrayToParcel.writeToParcel(parcel, 0); - parcel.setDataPosition(0); + TrackGroupArray trackGroupArrayFromBundle = + TrackGroupArray.CREATOR.fromBundle(trackGroupArrayToBundle.toBundle()); - TrackGroupArray trackGroupArrayFromParcel = TrackGroupArray.CREATOR.createFromParcel(parcel); - assertThat(trackGroupArrayFromParcel).isEqualTo(trackGroupArrayToParcel); - - parcel.recycle(); + assertThat(trackGroupArrayFromBundle).isEqualTo(trackGroupArrayToBundle); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java index ba42463279..1644c4dfaa 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source; import static com.google.common.truth.Truth.assertThat; -import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.MimeTypes; @@ -29,20 +28,15 @@ import org.junit.runner.RunWith; public final class TrackGroupTest { @Test - public void parcelable() { + public void roundTripViaBundle_ofTrackGroup_yieldsEqualInstance() { Format.Builder formatBuilder = new Format.Builder(); Format format1 = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build(); Format format2 = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_AAC).build(); - TrackGroup trackGroupToParcel = new TrackGroup(format1, format2); + TrackGroup trackGroupToBundle = new TrackGroup(format1, format2); - Parcel parcel = Parcel.obtain(); - trackGroupToParcel.writeToParcel(parcel, 0); - parcel.setDataPosition(0); + TrackGroup trackGroupFromBundle = TrackGroup.CREATOR.fromBundle(trackGroupToBundle.toBundle()); - TrackGroup trackGroupFromParcel = TrackGroup.CREATOR.createFromParcel(parcel); - assertThat(trackGroupFromParcel).isEqualTo(trackGroupToParcel); - - parcel.recycle(); + assertThat(trackGroupFromBundle).isEqualTo(trackGroupToBundle); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 81959d35d8..eb2a052d87 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -29,9 +29,9 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import android.content.Context; -import android.os.Parcel; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; @@ -139,37 +139,26 @@ public final class DefaultTrackSelectorTest { assertThat(parameters.buildUpon().build()).isEqualTo(parameters); } - /** Tests {@link Parameters} {@link android.os.Parcelable} implementation. */ + /** Tests {@link Parameters} {@link Bundleable} implementation. */ @Test - public void parameters_parcelAndUnParcelable() { - Parameters parametersToParcel = buildParametersForEqualsTest(); + public void roundTripViaBundle_ofParameters_yieldsEqualInstance() { + Parameters parametersToBundle = buildParametersForEqualsTest(); - Parcel parcel = Parcel.obtain(); - parametersToParcel.writeToParcel(parcel, 0); - parcel.setDataPosition(0); + Parameters parametersFromBundle = Parameters.CREATOR.fromBundle(parametersToBundle.toBundle()); - Parameters parametersFromParcel = Parameters.CREATOR.createFromParcel(parcel); - assertThat(parametersFromParcel).isEqualTo(parametersToParcel); - - parcel.recycle(); + assertThat(parametersFromBundle).isEqualTo(parametersToBundle); } - /** Tests {@link SelectionOverride}'s {@link android.os.Parcelable} implementation. */ + /** Tests {@link SelectionOverride}'s {@link Bundleable} implementation. */ @Test - public void selectionOverrideParcelable() { - int[] tracks = new int[] {2, 3}; - SelectionOverride selectionOverrideToParcel = - new SelectionOverride(/* groupIndex= */ 1, tracks); + public void roundTripViaBundle_ofSelectionOverride_yieldsEqualInstance() { + SelectionOverride selectionOverrideToBundle = + new SelectionOverride(/* groupIndex= */ 1, /* tracks...= */ 2, 3); - Parcel parcel = Parcel.obtain(); - selectionOverrideToParcel.writeToParcel(parcel, 0); - parcel.setDataPosition(0); + SelectionOverride selectionOverrideFromBundle = + SelectionOverride.CREATOR.fromBundle(selectionOverrideToBundle.toBundle()); - SelectionOverride selectionOverrideFromParcel = - SelectionOverride.CREATOR.createFromParcel(parcel); - assertThat(selectionOverrideFromParcel).isEqualTo(selectionOverrideToParcel); - - parcel.recycle(); + assertThat(selectionOverrideFromBundle).isEqualTo(selectionOverrideToBundle); } /** Tests that a null override clears a track selection. */ @@ -1764,7 +1753,13 @@ public final class DefaultTrackSelectorTest { /* rendererIndex= */ 2, new TrackGroupArray(VIDEO_TRACK_GROUP), new SelectionOverride(0, 1)) + .setSelectionOverride( + /* rendererIndex= */ 2, new TrackGroupArray(AUDIO_TRACK_GROUP), /* override= */ null) + .setSelectionOverride( + /* rendererIndex= */ 5, new TrackGroupArray(VIDEO_TRACK_GROUP), /* override= */ null) + .setRendererDisabled(1, true) .setRendererDisabled(3, true) + .setRendererDisabled(5, false) .build(); }