From e8adbd9075357b05e58ddf676f32221963dd623f Mon Sep 17 00:00:00 2001 From: claincly Date: Tue, 24 Oct 2023 04:07:13 -0700 Subject: [PATCH] Require duration in EditedMediaItem in composition previewing Adds an additional field for actual presentation duration of the EditedMediaItem PiperOrigin-RevId: 576090574 --- .../media3/transformer/EditedMediaItem.java | 33 +++++++-- .../EditedMediaItemBuilderTest.java | 67 +++++++++++++++++++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java index 23a4e21e31..0cd1b2e9a0 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java @@ -17,6 +17,7 @@ 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; @@ -135,13 +136,15 @@ public final class EditedMediaItem { /** * Sets the duration of the output video in microseconds. * - *

This should be set for inputs that don't have an implicit duration (e.g. images). It will - * be ignored for inputs that do have an implicit duration (e.g. video). + *

For an input that doesn't have an intrinsic duration (e.g. images), this should be the + * desired presentation duration. Otherwise, this should be the duration of the full content + * that the {@linkplain MediaItem.LocalConfiguration#uri media URI} resolves to, before {@link + * MediaItem#clippingConfiguration} is applied. * *

No duration is set by default. */ @CanIgnoreReturnValue - public Builder setDurationUs(long durationUs) { + public Builder setDurationUs(@IntRange(from = 1) long durationUs) { checkArgument(durationUs > 0); this.durationUs = durationUs; return this; @@ -233,7 +236,10 @@ public final class EditedMediaItem { */ public final boolean flattenForSlowMotion; - /** The duration of the image in the output video, in microseconds. */ + /** + * The duration of the image in the output video for image {@link MediaItem}, or the media + * duration for other types of {@link MediaItem}, in microseconds. + */ public final long durationUs; /** The frame rate of the image in the output video, in frames per second. */ @@ -243,6 +249,9 @@ public final class EditedMediaItem { /** The {@link Effects} to apply to the {@link #mediaItem}. */ public final Effects effects; + /** The duration for which this {@code EditedMediaItem} should be presented, in microseconds. */ + public final long presentationDurationUs; + private EditedMediaItem( MediaItem mediaItem, boolean removeAudio, @@ -259,6 +268,22 @@ public final class EditedMediaItem { this.durationUs = durationUs; this.frameRate = frameRate; this.effects = effects; + + if (mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET) + || durationUs == C.TIME_UNSET) { + // TODO - b/290734981: Use presentationDurationUs for image presentation + presentationDurationUs = durationUs; + } else { + MediaItem.ClippingConfiguration clippingConfiguration = mediaItem.clippingConfiguration; + checkArgument(!clippingConfiguration.relativeToDefaultPosition); + if (clippingConfiguration.endPositionMs == C.TIME_END_OF_SOURCE) { + presentationDurationUs = durationUs - msToUs(clippingConfiguration.startPositionMs); + } else { + checkArgument(clippingConfiguration.endPositionMs <= durationUs); + presentationDurationUs = + msToUs(clippingConfiguration.endPositionMs - clippingConfiguration.startPositionMs); + } + } } /** Returns a {@link Builder} initialized with the values of this instance. */ diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java index 160db0222b..3231b30e0d 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.transformer; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import androidx.media3.common.MediaItem; @@ -53,4 +54,70 @@ public final class EditedMediaItemBuilderTest { IllegalArgumentException.class, () -> new EditedMediaItem.Builder(mediaItem).setFlattenForSlowMotion(true).build()); } + + @Test + public void duration_withoutClippingConfiguration() { + MediaItem mediaItem = MediaItem.fromUri("Uri"); + + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(mediaItem).setDurationUs(1_000).build(); + + assertThat(editedMediaItem.presentationDurationUs).isEqualTo(1_000); + } + + @Test + public void duration_withClippingConfigurationAndEndPosition() { + MediaItem.ClippingConfiguration clippingConfiguration = + new MediaItem.ClippingConfiguration.Builder().setEndPositionMs(500).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(500_000); + } + + @Test + public void duration_withClippingConfigurationAndStartEndPosition() { + MediaItem.ClippingConfiguration clippingConfiguration = + new MediaItem.ClippingConfiguration.Builder() + // 300_000us + .setStartPositionMs(300) + // 500_000us + .setEndPositionMs(500) + .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 = + new MediaItem.ClippingConfiguration.Builder() + // 300_000us + .setStartPositionMs(300) + .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(700_000); + } }