Implement Bundleable for Timeline.Window
PiperOrigin-RevId: 361741354
This commit is contained in:
parent
ae7c1091e6
commit
a60ed20f1b
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user