Implement Bundleable for Timeline.Window

PiperOrigin-RevId: 361741354
This commit is contained in:
gyumin 2021-03-09 07:31:48 +00:00 committed by Ian Baker
parent ae7c1091e6
commit a60ed20f1b
3 changed files with 229 additions and 49 deletions

View File

@ -1272,6 +1272,12 @@ public final class MediaItem implements Bundleable {
private static final int FIELD_MEDIA_METADATA = 2;
private static final int FIELD_CLIPPING_PROPERTIES = 3;
/**
* {@inheritDoc}
*
* <p>It omits the {@link #playbackProperties} field. The {@link #playbackProperties} of an
* instance restored by {@link #CREATOR} will always be {@code null}.
*/
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
@ -1282,46 +1288,51 @@ public final class MediaItem implements Bundleable {
return bundle;
}
/** Object that can restore {@link MediaItem} from a {@link Bundle}. */
public static final Creator<MediaItem> CREATOR =
bundle -> {
String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID)));
@Nullable
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
LiveConfiguration liveConfiguration;
if (liveConfigurationBundle == null) {
liveConfiguration = LiveConfiguration.UNSET;
} else {
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
}
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA));
MediaMetadata mediaMetadata;
if (mediaMetadataBundle == null) {
mediaMetadata = new MediaMetadata.Builder().build();
} else {
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
}
@Nullable
Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
ClippingProperties clippingProperties;
if (clippingPropertiesBundle == null) {
clippingProperties =
new ClippingProperties(
/* startPositionMs= */ 0,
/* endPositionMs= */ C.TIME_END_OF_SOURCE,
/* relativeToLiveWindow= */ false,
/* relativeToDefaultPosition= */ false,
/* startsAtKeyFrame= */ false);
} else {
clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle);
}
return new MediaItem(
mediaId,
clippingProperties,
/* playbackProperties= */ null,
liveConfiguration,
mediaMetadata);
};
/**
* Object that can restore {@link MediaItem} from a {@link Bundle}.
*
* <p>The {@link #playbackProperties} of a restored instance will always be {@code null}.
*/
public static final Creator<MediaItem> CREATOR = MediaItem::fromBundle;
private static MediaItem fromBundle(Bundle bundle) {
String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID)));
@Nullable
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
LiveConfiguration liveConfiguration;
if (liveConfigurationBundle == null) {
liveConfiguration = LiveConfiguration.UNSET;
} else {
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
}
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA));
MediaMetadata mediaMetadata;
if (mediaMetadataBundle == null) {
mediaMetadata = new MediaMetadata.Builder().build();
} else {
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
}
@Nullable
Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
ClippingProperties clippingProperties;
if (clippingPropertiesBundle == null) {
clippingProperties =
new ClippingProperties(
/* startPositionMs= */ 0,
/* endPositionMs= */ C.TIME_END_OF_SOURCE,
/* relativeToLiveWindow= */ false,
/* relativeToDefaultPosition= */ false,
/* startsAtKeyFrame= */ false);
} else {
clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle);
}
return new MediaItem(
mediaId,
clippingProperties,
/* playbackProperties= */ null,
liveConfiguration,
mediaMetadata);
}
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);

View File

@ -136,13 +136,15 @@ public abstract class Timeline {
* <p style="align:center"><img src="doc-files/timeline-window.svg" alt="Information defined by a
* timeline window">
*/
public static final class Window {
public static final class Window implements Bundleable {
/**
* A {@link #uid} for a window that must be used for single-window {@link Timeline Timelines}.
*/
public static final Object SINGLE_WINDOW_UID = new Object();
private static final Object FAKE_WINDOW_UID = new Object();
private static final MediaItem EMPTY_MEDIA_ITEM =
new MediaItem.Builder()
.setMediaId("com.google.android.exoplayer2.Timeline")
@ -213,14 +215,6 @@ public abstract class Timeline {
*/
public boolean isPlaceholder;
/** The index of the first period that belongs to this window. */
public int firstPeriodIndex;
/**
* The index of the last period that belongs to this window.
*/
public int lastPeriodIndex;
/**
* The default position relative to the start of the window at which to begin playback, in
* microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
@ -234,6 +228,12 @@ public abstract class Timeline {
*/
public long durationUs;
/** The index of the first period that belongs to this window. */
public int firstPeriodIndex;
/** The index of the last period that belongs to this window. */
public int lastPeriodIndex;
/**
* The position of the start of this window relative to the start of the first period belonging
* to it, in microseconds.
@ -404,6 +404,142 @@ public abstract class Timeline {
result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32));
return result;
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FIELD_MEDIA_ITEM,
FIELD_PRESENTATION_START_TIME_MS,
FIELD_WINDOW_START_TIME_MS,
FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS,
FIELD_IS_SEEKABLE,
FIELD_IS_DYNAMIC,
FIELD_LIVE_CONFIGURATION,
FIELD_IS_PLACEHOLDER,
FIELD_DEFAULT_POSITION_US,
FIELD_DURATION_US,
FIELD_FIRST_PERIOD_INDEX,
FIELD_LAST_PERIOD_INDEX,
FIELD_POSITION_IN_FIRST_PERIOD_US,
})
private @interface FieldNumber {}
private static final int FIELD_MEDIA_ITEM = 1;
private static final int FIELD_PRESENTATION_START_TIME_MS = 2;
private static final int FIELD_WINDOW_START_TIME_MS = 3;
private static final int FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = 4;
private static final int FIELD_IS_SEEKABLE = 5;
private static final int FIELD_IS_DYNAMIC = 6;
private static final int FIELD_LIVE_CONFIGURATION = 7;
private static final int FIELD_IS_PLACEHOLDER = 8;
private static final int FIELD_DEFAULT_POSITION_US = 9;
private static final int FIELD_DURATION_US = 10;
private static final int FIELD_FIRST_PERIOD_INDEX = 11;
private static final int FIELD_LAST_PERIOD_INDEX = 12;
private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13;
/**
* {@inheritDoc}
*
* <p>It omits the {@link #uid} and {@link #manifest} fields. The {@link #uid} of an instance
* restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the
* instance will be {@code null}.
*/
// TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise.
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle());
bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs);
bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs);
bundle.putLong(
keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs);
bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable);
bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic);
@Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration;
if (liveConfiguration != null) {
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
}
bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder);
bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs);
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex);
bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex);
bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs);
return bundle;
}
/**
* Object that can restore {@link Period} from a {@link Bundle}.
*
* <p>The {@link #uid} of a restored instance will be a fake {@link Object} and the {@link
* #manifest} of the instance will be {@code null}.
*/
public static final Creator<Window> CREATOR = Window::fromBundle;
private static Window fromBundle(Bundle bundle) {
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
@Nullable
MediaItem mediaItem =
mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : null;
long presentationStartTimeMs =
bundle.getLong(
keyForField(FIELD_PRESENTATION_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET);
long windowStartTimeMs =
bundle.getLong(keyForField(FIELD_WINDOW_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET);
long elapsedRealtimeEpochOffsetMs =
bundle.getLong(
keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS),
/* defaultValue= */ C.TIME_UNSET);
boolean isSeekable =
bundle.getBoolean(keyForField(FIELD_IS_SEEKABLE), /* defaultValue= */ false);
boolean isDynamic =
bundle.getBoolean(keyForField(FIELD_IS_DYNAMIC), /* defaultValue= */ false);
@Nullable
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
@Nullable
MediaItem.LiveConfiguration liveConfiguration =
liveConfigurationBundle != null
? MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle)
: null;
boolean isPlaceHolder =
bundle.getBoolean(keyForField(FIELD_IS_PLACEHOLDER), /* defaultValue= */ false);
long defaultPositionUs =
bundle.getLong(keyForField(FIELD_DEFAULT_POSITION_US), /* defaultValue= */ 0);
long durationUs =
bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
int firstPeriodIndex =
bundle.getInt(keyForField(FIELD_FIRST_PERIOD_INDEX), /* defaultValue= */ 0);
int lastPeriodIndex =
bundle.getInt(keyForField(FIELD_LAST_PERIOD_INDEX), /* defaultValue= */ 0);
long positionInFirstPeriodUs =
bundle.getLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), /* defaultValue= */ 0);
Window window = new Window();
window.set(
FAKE_WINDOW_UID,
mediaItem,
/* manifest= */ null,
presentationStartTimeMs,
windowStartTimeMs,
elapsedRealtimeEpochOffsetMs,
isSeekable,
isDynamic,
liveConfiguration,
defaultPositionUs,
durationUs,
firstPeriodIndex,
lastPeriodIndex,
positionInFirstPeriodUs);
window.isPlaceholder = isPlaceHolder;
return window;
}
private static String keyForField(@Window.FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}
/**

View File

@ -201,6 +201,39 @@ public class TimelineTest {
assertThat(period.hashCode()).isEqualTo(otherPeriod.hashCode());
}
@Test
public void roundtripViaBundle_ofWindow_yieldsEqualInstanceExceptUidAndManifest() {
Timeline.Window window = new Timeline.Window();
window.uid = new Object();
window.mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
window.manifest = new Object();
window.presentationStartTimeMs = 111;
window.windowStartTimeMs = 222;
window.elapsedRealtimeEpochOffsetMs = 333;
window.isSeekable = true;
window.isDynamic = true;
window.liveConfiguration =
new LiveConfiguration(
/* targetOffsetMs= */ 1,
/* minOffsetMs= */ 2,
/* maxOffsetMs= */ 3,
/* minPlaybackSpeed= */ 0.5f,
/* maxPlaybackSpeed= */ 1.5f);
window.isPlaceholder = true;
window.defaultPositionUs = 444;
window.durationUs = 555;
window.firstPeriodIndex = 6;
window.lastPeriodIndex = 7;
window.positionInFirstPeriodUs = 888;
Timeline.Window restoredWindow = Timeline.Window.CREATOR.fromBundle(window.toBundle());
assertThat(restoredWindow.manifest).isNull();
window.uid = restoredWindow.uid;
window.manifest = null;
assertThat(restoredWindow).isEqualTo(window);
}
@Test
public void roundtripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() {
Timeline.Period period = new Timeline.Period();