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_MEDIA_METADATA = 2;
|
||||||
private static final int FIELD_CLIPPING_PROPERTIES = 3;
|
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
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@ -1282,46 +1288,51 @@ public final class MediaItem implements Bundleable {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore {@link MediaItem} from a {@link Bundle}. */
|
/**
|
||||||
public static final Creator<MediaItem> CREATOR =
|
* Object that can restore {@link MediaItem} from a {@link Bundle}.
|
||||||
bundle -> {
|
*
|
||||||
String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID)));
|
* <p>The {@link #playbackProperties} of a restored instance will always be {@code null}.
|
||||||
@Nullable
|
*/
|
||||||
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
|
public static final Creator<MediaItem> CREATOR = MediaItem::fromBundle;
|
||||||
LiveConfiguration liveConfiguration;
|
|
||||||
if (liveConfigurationBundle == null) {
|
private static MediaItem fromBundle(Bundle bundle) {
|
||||||
liveConfiguration = LiveConfiguration.UNSET;
|
String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID)));
|
||||||
} else {
|
@Nullable
|
||||||
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
|
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
|
||||||
}
|
LiveConfiguration liveConfiguration;
|
||||||
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA));
|
if (liveConfigurationBundle == null) {
|
||||||
MediaMetadata mediaMetadata;
|
liveConfiguration = LiveConfiguration.UNSET;
|
||||||
if (mediaMetadataBundle == null) {
|
} else {
|
||||||
mediaMetadata = new MediaMetadata.Builder().build();
|
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
|
||||||
} else {
|
}
|
||||||
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
|
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA));
|
||||||
}
|
MediaMetadata mediaMetadata;
|
||||||
@Nullable
|
if (mediaMetadataBundle == null) {
|
||||||
Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
|
mediaMetadata = new MediaMetadata.Builder().build();
|
||||||
ClippingProperties clippingProperties;
|
} else {
|
||||||
if (clippingPropertiesBundle == null) {
|
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
|
||||||
clippingProperties =
|
}
|
||||||
new ClippingProperties(
|
@Nullable
|
||||||
/* startPositionMs= */ 0,
|
Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
|
||||||
/* endPositionMs= */ C.TIME_END_OF_SOURCE,
|
ClippingProperties clippingProperties;
|
||||||
/* relativeToLiveWindow= */ false,
|
if (clippingPropertiesBundle == null) {
|
||||||
/* relativeToDefaultPosition= */ false,
|
clippingProperties =
|
||||||
/* startsAtKeyFrame= */ false);
|
new ClippingProperties(
|
||||||
} else {
|
/* startPositionMs= */ 0,
|
||||||
clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle);
|
/* endPositionMs= */ C.TIME_END_OF_SOURCE,
|
||||||
}
|
/* relativeToLiveWindow= */ false,
|
||||||
return new MediaItem(
|
/* relativeToDefaultPosition= */ false,
|
||||||
mediaId,
|
/* startsAtKeyFrame= */ false);
|
||||||
clippingProperties,
|
} else {
|
||||||
/* playbackProperties= */ null,
|
clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle);
|
||||||
liveConfiguration,
|
}
|
||||||
mediaMetadata);
|
return new MediaItem(
|
||||||
};
|
mediaId,
|
||||||
|
clippingProperties,
|
||||||
|
/* playbackProperties= */ null,
|
||||||
|
liveConfiguration,
|
||||||
|
mediaMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static String keyForField(@FieldNumber int field) {
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
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
|
* <p style="align:center"><img src="doc-files/timeline-window.svg" alt="Information defined by a
|
||||||
* timeline window">
|
* 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}.
|
* 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();
|
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 =
|
private static final MediaItem EMPTY_MEDIA_ITEM =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setMediaId("com.google.android.exoplayer2.Timeline")
|
.setMediaId("com.google.android.exoplayer2.Timeline")
|
||||||
@ -213,14 +215,6 @@ public abstract class Timeline {
|
|||||||
*/
|
*/
|
||||||
public boolean isPlaceholder;
|
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
|
* 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
|
* 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;
|
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
|
* The position of the start of this window relative to the start of the first period belonging
|
||||||
* to it, in microseconds.
|
* to it, in microseconds.
|
||||||
@ -404,6 +404,142 @@ public abstract class Timeline {
|
|||||||
result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32));
|
result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32));
|
||||||
return result;
|
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());
|
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
|
@Test
|
||||||
public void roundtripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() {
|
public void roundtripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() {
|
||||||
Timeline.Period period = new Timeline.Period();
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user