Add microsecond precision to MediaItem.ClippingConfiguration
PiperOrigin-RevId: 578881990
This commit is contained in:
parent
dab9eb33a4
commit
3253f1b5cd
@ -18,6 +18,8 @@ package androidx.media3.common;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
import static androidx.media3.common.util.Util.usToMs;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -1837,20 +1839,20 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
/** Builder for {@link ClippingConfiguration} instances. */
|
||||
public static final class Builder {
|
||||
private long startPositionMs;
|
||||
private long endPositionMs;
|
||||
private long startPositionUs;
|
||||
private long endPositionUs;
|
||||
private boolean relativeToLiveWindow;
|
||||
private boolean relativeToDefaultPosition;
|
||||
private boolean startsAtKeyFrame;
|
||||
|
||||
/** Creates a new instance with default values. */
|
||||
public Builder() {
|
||||
endPositionMs = C.TIME_END_OF_SOURCE;
|
||||
endPositionUs = C.TIME_END_OF_SOURCE;
|
||||
}
|
||||
|
||||
private Builder(ClippingConfiguration clippingConfiguration) {
|
||||
startPositionMs = clippingConfiguration.startPositionMs;
|
||||
endPositionMs = clippingConfiguration.endPositionMs;
|
||||
startPositionUs = clippingConfiguration.startPositionUs;
|
||||
endPositionUs = clippingConfiguration.endPositionUs;
|
||||
relativeToLiveWindow = clippingConfiguration.relativeToLiveWindow;
|
||||
relativeToDefaultPosition = clippingConfiguration.relativeToDefaultPosition;
|
||||
startsAtKeyFrame = clippingConfiguration.startsAtKeyFrame;
|
||||
@ -1862,8 +1864,18 @@ public final class MediaItem implements Bundleable {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setStartPositionMs(@IntRange(from = 0) long startPositionMs) {
|
||||
Assertions.checkArgument(startPositionMs >= 0);
|
||||
this.startPositionMs = startPositionMs;
|
||||
return setStartPositionUs(msToUs(startPositionMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional start position in microseconds which must be a value larger than or equal
|
||||
* to zero (Default: 0).
|
||||
*/
|
||||
@UnstableApi
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setStartPositionUs(@IntRange(from = 0) long startPositionUs) {
|
||||
Assertions.checkArgument(startPositionUs >= 0);
|
||||
this.startPositionUs = startPositionUs;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1874,8 +1886,19 @@ public final class MediaItem implements Bundleable {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setEndPositionMs(long endPositionMs) {
|
||||
Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0);
|
||||
this.endPositionMs = endPositionMs;
|
||||
return setEndPositionUs(msToUs(endPositionMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional end position in milliseconds which must be a value larger than or equal
|
||||
* to zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media
|
||||
* (Default: {@link C#TIME_END_OF_SOURCE}).
|
||||
*/
|
||||
@UnstableApi
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setEndPositionUs(long endPositionUs) {
|
||||
Assertions.checkArgument(endPositionUs == C.TIME_END_OF_SOURCE || endPositionUs >= 0);
|
||||
this.endPositionUs = endPositionUs;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1932,12 +1955,23 @@ public final class MediaItem implements Bundleable {
|
||||
@IntRange(from = 0)
|
||||
public final long startPositionMs;
|
||||
|
||||
/** The start position in microseconds. This is a value larger than or equal to zero. */
|
||||
@UnstableApi
|
||||
@IntRange(from = 0)
|
||||
public final long startPositionUs;
|
||||
|
||||
/**
|
||||
* The end position in milliseconds. This is a value larger than or equal to zero or {@link
|
||||
* C#TIME_END_OF_SOURCE} to play to the end of the stream.
|
||||
*/
|
||||
public final long endPositionMs;
|
||||
|
||||
/**
|
||||
* The end position in microseconds. This is a value larger than or equal to zero or {@link
|
||||
* C#TIME_END_OF_SOURCE} to play to the end of the stream.
|
||||
*/
|
||||
@UnstableApi public final long endPositionUs;
|
||||
|
||||
/**
|
||||
* Whether the clipping of active media periods moves with a live window. If {@code false},
|
||||
* playback ends when it reaches {@link #endPositionMs}.
|
||||
@ -1954,8 +1988,10 @@ public final class MediaItem implements Bundleable {
|
||||
public final boolean startsAtKeyFrame;
|
||||
|
||||
private ClippingConfiguration(Builder builder) {
|
||||
this.startPositionMs = builder.startPositionMs;
|
||||
this.endPositionMs = builder.endPositionMs;
|
||||
this.startPositionMs = usToMs(builder.startPositionUs);
|
||||
this.endPositionMs = usToMs(builder.endPositionUs);
|
||||
this.startPositionUs = builder.startPositionUs;
|
||||
this.endPositionUs = builder.endPositionUs;
|
||||
this.relativeToLiveWindow = builder.relativeToLiveWindow;
|
||||
this.relativeToDefaultPosition = builder.relativeToDefaultPosition;
|
||||
this.startsAtKeyFrame = builder.startsAtKeyFrame;
|
||||
@ -1977,8 +2013,8 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
ClippingConfiguration other = (ClippingConfiguration) obj;
|
||||
|
||||
return startPositionMs == other.startPositionMs
|
||||
&& endPositionMs == other.endPositionMs
|
||||
return startPositionUs == other.startPositionUs
|
||||
&& endPositionUs == other.endPositionUs
|
||||
&& relativeToLiveWindow == other.relativeToLiveWindow
|
||||
&& relativeToDefaultPosition == other.relativeToDefaultPosition
|
||||
&& startsAtKeyFrame == other.startsAtKeyFrame;
|
||||
@ -1986,8 +2022,8 @@ public final class MediaItem implements Bundleable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (startPositionMs ^ (startPositionMs >>> 32));
|
||||
result = 31 * result + (int) (endPositionMs ^ (endPositionMs >>> 32));
|
||||
int result = (int) (startPositionUs ^ (startPositionUs >>> 32));
|
||||
result = 31 * result + (int) (endPositionUs ^ (endPositionUs >>> 32));
|
||||
result = 31 * result + (relativeToLiveWindow ? 1 : 0);
|
||||
result = 31 * result + (relativeToDefaultPosition ? 1 : 0);
|
||||
result = 31 * result + (startsAtKeyFrame ? 1 : 0);
|
||||
@ -2001,6 +2037,8 @@ public final class MediaItem implements Bundleable {
|
||||
private static final String FIELD_RELATIVE_TO_LIVE_WINDOW = Util.intToStringMaxRadix(2);
|
||||
private static final String FIELD_RELATIVE_TO_DEFAULT_POSITION = Util.intToStringMaxRadix(3);
|
||||
private static final String FIELD_STARTS_AT_KEY_FRAME = Util.intToStringMaxRadix(4);
|
||||
static final String FIELD_START_POSITION_US = Util.intToStringMaxRadix(5);
|
||||
static final String FIELD_END_POSITION_US = Util.intToStringMaxRadix(6);
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@ -2012,6 +2050,12 @@ public final class MediaItem implements Bundleable {
|
||||
if (endPositionMs != UNSET.endPositionMs) {
|
||||
bundle.putLong(FIELD_END_POSITION_MS, endPositionMs);
|
||||
}
|
||||
if (startPositionUs != UNSET.startPositionUs) {
|
||||
bundle.putLong(FIELD_START_POSITION_US, startPositionUs);
|
||||
}
|
||||
if (endPositionUs != UNSET.endPositionUs) {
|
||||
bundle.putLong(FIELD_END_POSITION_US, endPositionUs);
|
||||
}
|
||||
if (relativeToLiveWindow != UNSET.relativeToLiveWindow) {
|
||||
bundle.putBoolean(FIELD_RELATIVE_TO_LIVE_WINDOW, relativeToLiveWindow);
|
||||
}
|
||||
@ -2027,25 +2071,38 @@ public final class MediaItem implements Bundleable {
|
||||
/** An object that can restore {@link ClippingConfiguration} from a {@link Bundle}. */
|
||||
@UnstableApi
|
||||
public static final Creator<ClippingProperties> CREATOR =
|
||||
bundle ->
|
||||
new ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(
|
||||
bundle.getLong(
|
||||
FIELD_START_POSITION_MS, /* defaultValue= */ UNSET.startPositionMs))
|
||||
.setEndPositionMs(
|
||||
bundle.getLong(FIELD_END_POSITION_MS, /* defaultValue= */ UNSET.endPositionMs))
|
||||
.setRelativeToLiveWindow(
|
||||
bundle.getBoolean(
|
||||
FIELD_RELATIVE_TO_LIVE_WINDOW,
|
||||
/* defaultValue= */ UNSET.relativeToLiveWindow))
|
||||
.setRelativeToDefaultPosition(
|
||||
bundle.getBoolean(
|
||||
FIELD_RELATIVE_TO_DEFAULT_POSITION,
|
||||
/* defaultValue= */ UNSET.relativeToDefaultPosition))
|
||||
.setStartsAtKeyFrame(
|
||||
bundle.getBoolean(
|
||||
FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame))
|
||||
.buildClippingProperties();
|
||||
bundle -> {
|
||||
ClippingConfiguration.Builder clippingConfiguration =
|
||||
new ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(
|
||||
bundle.getLong(
|
||||
FIELD_START_POSITION_MS, /* defaultValue= */ UNSET.startPositionMs))
|
||||
.setEndPositionMs(
|
||||
bundle.getLong(
|
||||
FIELD_END_POSITION_MS, /* defaultValue= */ UNSET.endPositionMs))
|
||||
.setRelativeToLiveWindow(
|
||||
bundle.getBoolean(
|
||||
FIELD_RELATIVE_TO_LIVE_WINDOW,
|
||||
/* defaultValue= */ UNSET.relativeToLiveWindow))
|
||||
.setRelativeToDefaultPosition(
|
||||
bundle.getBoolean(
|
||||
FIELD_RELATIVE_TO_DEFAULT_POSITION,
|
||||
/* defaultValue= */ UNSET.relativeToDefaultPosition))
|
||||
.setStartsAtKeyFrame(
|
||||
bundle.getBoolean(
|
||||
FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame));
|
||||
long startPositionUs =
|
||||
bundle.getLong(FIELD_START_POSITION_US, /* defaultValue= */ UNSET.startPositionUs);
|
||||
if (startPositionUs != UNSET.startPositionUs) {
|
||||
clippingConfiguration.setStartPositionUs(startPositionUs);
|
||||
}
|
||||
long endPositionUs =
|
||||
bundle.getLong(FIELD_END_POSITION_US, /* defaultValue= */ UNSET.endPositionUs);
|
||||
if (endPositionUs != UNSET.endPositionUs) {
|
||||
clippingConfiguration.setEndPositionUs(endPositionUs);
|
||||
}
|
||||
return clippingConfiguration.buildClippingProperties();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.MediaItem.ClippingConfiguration.FIELD_END_POSITION_US;
|
||||
import static androidx.media3.common.MediaItem.ClippingConfiguration.FIELD_START_POSITION_US;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@ -438,7 +440,9 @@ public class MediaItemTest {
|
||||
// Please refrain from altering default values since doing so would cause issues with backwards
|
||||
// compatibility.
|
||||
assertThat(clippingConfiguration.startPositionMs).isEqualTo(0L);
|
||||
assertThat(clippingConfiguration.startPositionUs).isEqualTo(0L);
|
||||
assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
assertThat(clippingConfiguration.endPositionUs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
assertThat(clippingConfiguration.relativeToLiveWindow).isFalse();
|
||||
assertThat(clippingConfiguration.relativeToDefaultPosition).isFalse();
|
||||
assertThat(clippingConfiguration.startsAtKeyFrame).isFalse();
|
||||
@ -468,6 +472,7 @@ public class MediaItemTest {
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(1000L)
|
||||
.setEndPositionUs(2000_031L)
|
||||
.setStartsAtKeyFrame(true)
|
||||
.build();
|
||||
|
||||
@ -477,6 +482,51 @@ public class MediaItemTest {
|
||||
assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createClippingConfigurationInstance_viaBundleWithOnlyMs_yieldsEqualInstance() {
|
||||
// Creates instance by setting some non-default values
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(1000L)
|
||||
.setEndPositionMs(2000L)
|
||||
.setStartsAtKeyFrame(true)
|
||||
.build();
|
||||
Bundle clippingConfigurationBundle = clippingConfiguration.toBundle();
|
||||
clippingConfigurationBundle.remove(FIELD_START_POSITION_US);
|
||||
clippingConfigurationBundle.remove(FIELD_END_POSITION_US);
|
||||
|
||||
MediaItem.ClippingConfiguration clippingConfigurationFromBundle =
|
||||
MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
||||
|
||||
assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createClippingConfigurationInstance_setsStartPositionInMsAndUs_fieldsAreConsistent() {
|
||||
// Creates instance by setting some non-default values
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(1000L)
|
||||
.setStartPositionUs(200_203L)
|
||||
.build();
|
||||
|
||||
assertThat(clippingConfiguration.startPositionMs).isEqualTo(200L);
|
||||
assertThat(clippingConfiguration.startPositionUs).isEqualTo(200_203L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createClippingConfigurationInstance_setsEndPositionInMsAndUs_fieldsAreConsistent() {
|
||||
// Creates instance by setting some non-default values
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setEndPositionUs(1000L)
|
||||
.setEndPositionMs(C.TIME_END_OF_SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clippingConfigurationBuilder_throwsOnInvalidValues() {
|
||||
MediaItem.ClippingConfiguration.Builder clippingConfigurationBuilder =
|
||||
|
@ -19,6 +19,7 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.usToMs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
@ -154,16 +155,17 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
||||
@CanIgnoreReturnValue
|
||||
public Builder add(MediaItem mediaItem, long initialPlaceholderDurationMs) {
|
||||
checkNotNull(mediaItem);
|
||||
checkStateNotNull(
|
||||
mediaSourceFactory,
|
||||
"Must use useDefaultMediaSourceFactory or setMediaSourceFactory first.");
|
||||
if (initialPlaceholderDurationMs == C.TIME_UNSET
|
||||
&& mediaItem.clippingConfiguration.endPositionMs != C.TIME_END_OF_SOURCE) {
|
||||
// If the item is going to be clipped, we can provide a placeholder duration automatically.
|
||||
initialPlaceholderDurationMs =
|
||||
mediaItem.clippingConfiguration.endPositionMs
|
||||
- mediaItem.clippingConfiguration.startPositionMs;
|
||||
usToMs(
|
||||
mediaItem.clippingConfiguration.endPositionUs
|
||||
- mediaItem.clippingConfiguration.startPositionUs);
|
||||
}
|
||||
checkStateNotNull(
|
||||
mediaSourceFactory,
|
||||
"Must use useDefaultMediaSourceFactory or setMediaSourceFactory first.");
|
||||
return add(mediaSourceFactory.createMediaSource(mediaItem), initialPlaceholderDurationMs);
|
||||
}
|
||||
|
||||
@ -364,7 +366,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
||||
if (timeOffsetUs == null) {
|
||||
return mediaTimeMs;
|
||||
}
|
||||
return mediaTimeMs + Util.usToMs(timeOffsetUs);
|
||||
return mediaTimeMs + usToMs(timeOffsetUs);
|
||||
}
|
||||
|
||||
private boolean handleMessage(Message msg) {
|
||||
|
@ -554,15 +554,15 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
// internal methods
|
||||
|
||||
private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
|
||||
if (mediaItem.clippingConfiguration.startPositionMs == 0
|
||||
&& mediaItem.clippingConfiguration.endPositionMs == C.TIME_END_OF_SOURCE
|
||||
if (mediaItem.clippingConfiguration.startPositionUs == 0
|
||||
&& mediaItem.clippingConfiguration.endPositionUs == C.TIME_END_OF_SOURCE
|
||||
&& !mediaItem.clippingConfiguration.relativeToDefaultPosition) {
|
||||
return mediaSource;
|
||||
}
|
||||
return new ClippingMediaSource(
|
||||
mediaSource,
|
||||
msToUs(mediaItem.clippingConfiguration.startPositionMs),
|
||||
msToUs(mediaItem.clippingConfiguration.endPositionMs),
|
||||
mediaItem.clippingConfiguration.startPositionUs,
|
||||
mediaItem.clippingConfiguration.endPositionUs,
|
||||
/* enableInitialDiscontinuity= */ !mediaItem.clippingConfiguration.startsAtKeyFrame,
|
||||
/* allowDynamicClippingUpdates= */ mediaItem.clippingConfiguration.relativeToLiveWindow,
|
||||
mediaItem.clippingConfiguration.relativeToDefaultPosition);
|
||||
|
@ -17,7 +17,6 @@ package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.media3.common.C;
|
||||
@ -276,12 +275,12 @@ public final class EditedMediaItem {
|
||||
} else {
|
||||
MediaItem.ClippingConfiguration clippingConfiguration = mediaItem.clippingConfiguration;
|
||||
checkArgument(!clippingConfiguration.relativeToDefaultPosition);
|
||||
if (clippingConfiguration.endPositionMs == C.TIME_END_OF_SOURCE) {
|
||||
presentationDurationUs = durationUs - msToUs(clippingConfiguration.startPositionMs);
|
||||
if (clippingConfiguration.endPositionUs == C.TIME_END_OF_SOURCE) {
|
||||
presentationDurationUs = durationUs - clippingConfiguration.startPositionUs;
|
||||
} else {
|
||||
checkArgument(clippingConfiguration.endPositionMs <= durationUs);
|
||||
checkArgument(clippingConfiguration.endPositionUs <= durationUs);
|
||||
presentationDurationUs =
|
||||
msToUs(clippingConfiguration.endPositionMs - clippingConfiguration.startPositionMs);
|
||||
clippingConfiguration.endPositionUs - clippingConfiguration.startPositionUs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,25 @@ public final class EditedMediaItemBuilderTest {
|
||||
assertThat(editedMediaItem.presentationDurationUs).isEqualTo(200_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duration_withClippingConfigurationAndStartEndPositionInUs() {
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setStartPositionUs(300_000)
|
||||
.setEndPositionUs(500_000)
|
||||
.build();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri("Uri")
|
||||
.setClippingConfiguration(clippingConfiguration)
|
||||
.build();
|
||||
|
||||
EditedMediaItem editedMediaItem =
|
||||
new EditedMediaItem.Builder(mediaItem).setDurationUs(1_000_000).build();
|
||||
|
||||
assertThat(editedMediaItem.presentationDurationUs).isEqualTo(200_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duration_withClippingConfigurationAndStartPosition() {
|
||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||
|
Loading…
x
Reference in New Issue
Block a user