diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerSeekTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerSeekTest.java index ba73b266ea..f13f857200 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerSeekTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerSeekTest.java @@ -23,6 +23,8 @@ import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET; import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.collect.Iterables.getLast; +import static com.google.common.collect.Iterables.skip; +import static com.google.common.collect.Iterables.transform; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -53,6 +55,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; @@ -64,20 +67,26 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; /** - * Instrumentation tests for {@link CompositionPlayer} {@linkplain CompositionPlayer#seekTo + * Instrumentation tests for {@link CompositionPlayer} {@linkplain CompositionPlayer#seekTo(long) * seeking}. */ @RunWith(AndroidJUnit4.class) public class CompositionPlayerSeekTest { - private static final long TEST_TIMEOUT_MS = isRunningOnEmulator() ? 2000_000 : 1000_000; + private static final long TEST_TIMEOUT_MS = isRunningOnEmulator() ? 20_000 : 10_000; - private static final MediaItem VIDEO_MEDIA_ITEM = MediaItem.fromUri(MP4_ASSET.uri); private static final long VIDEO_DURATION_US = MP4_ASSET.videoDurationUs; + private static final MediaItemConfig VIDEO_MEDIA_ITEM = + new MediaItemConfig(MediaItem.fromUri(MP4_ASSET.uri), VIDEO_DURATION_US); private static final ImmutableList VIDEO_TIMESTAMPS_US = MP4_ASSET.videoTimestampsUs; - private static final MediaItem IMAGE_MEDIA_ITEM = - new MediaItem.Builder().setUri(PNG_ASSET.uri).setImageDurationMs(200).build(); private static final long IMAGE_DURATION_US = 200_000; + private static final MediaItemConfig IMAGE_MEDIA_ITEM = + new MediaItemConfig( + new MediaItem.Builder() + .setUri(PNG_ASSET.uri) + .setImageDurationMs(usToMs(IMAGE_DURATION_US)) + .build(), + IMAGE_DURATION_US); // 200 ms at 30 fps (default frame rate) private static final ImmutableList IMAGE_TIMESTAMPS_US = ImmutableList.of(0L, 33_333L, 66_667L, 100_000L, 133_333L, 166_667L); @@ -118,22 +127,13 @@ public class CompositionPlayerSeekTest { @Test public void seekToZero_afterPlayingSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram = - new InputTimestampRecordingShaderProgram(); - EditedMediaItem video = - createEditedMediaItem( - VIDEO_MEDIA_ITEM, - VIDEO_DURATION_US, - /* videoEffect= */ (GlEffect) - (context, useHdr) -> inputTimestampRecordingShaderProgram); ImmutableList sequenceTimestampsUs = new ImmutableList.Builder() // Plays the first video .addAll(VIDEO_TIMESTAMPS_US) // Plays the second video .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); // Seeked after the first playback ends, so the timestamps are repeated twice. ImmutableList expectedTimestampsUs = @@ -141,53 +141,137 @@ public class CompositionPlayerSeekTest { .addAll(sequenceTimestampsUs) .addAll(sequenceTimestampsUs) .build(); - CountDownLatch videoGraphEnded = new CountDownLatch(1); - getInstrumentation() - .runOnMainSync( - () -> { - compositionPlayer = - new CompositionPlayer.Builder(applicationContext) - .setPreviewingVideoGraphFactory( - new ListenerCapturingVideoGraphFactory(videoGraphEnded)) - .build(); - // Set a surface on the player even though there is no UI on this test. We need a - // surface otherwise the player will skip/drop video frames. - compositionPlayer.setVideoSurfaceView(surfaceView); - compositionPlayer.addListener(playerTestListener); - compositionPlayer.setComposition( - new Composition.Builder(new EditedMediaItemSequence.Builder(video, video).build()) - .build()); - compositionPlayer.prepare(); - compositionPlayer.play(); - }); - playerTestListener.waitUntilPlayerEnded(); - playerTestListener.resetStatus(); - getInstrumentation().runOnMainSync(() -> compositionPlayer.seekTo(0)); - playerTestListener.waitUntilPlayerEnded(); - assertThat(videoGraphEnded.await(VIDEO_GRAPH_END_TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs()) + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), /* seekTimeMs= */ 0)) .isEqualTo(expectedTimestampsUs); } @Test - public void seekToZero_afterPlayingSingleSequenceOfTwoImages() throws Exception { - InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram = - new InputTimestampRecordingShaderProgram(); - EditedMediaItem image = - createEditedMediaItem( - IMAGE_MEDIA_ITEM, - IMAGE_DURATION_US, - /* videoEffect= */ (GlEffect) - (context, useHdr) -> inputTimestampRecordingShaderProgram); + public void seekToFirstVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception { + maybeSkipTest(); + // Skips the first three video frames + long seekTimeMs = 100; ImmutableList sequenceTimestampsUs = new ImmutableList.Builder() // Plays the first video - .addAll(IMAGE_TIMESTAMPS_US) + .addAll(VIDEO_TIMESTAMPS_US) // Plays the second video .addAll( - Iterables.transform( - IMAGE_TIMESTAMPS_US, timestampUs -> IMAGE_DURATION_US + timestampUs)) + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the first video skipping the first three frames + .addAll(skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3)) + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToStartOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception { + maybeSkipTest(); + // Seeks to the end of the first video + long seekTimeMs = usToMs(VIDEO_DURATION_US); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the second video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the second video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception { + maybeSkipTest(); + // Skips the first three image frames of the second image. + long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the second video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the second video skipping the first three frames + .addAll( + transform( + skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3), + timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception { + maybeSkipTest(); + // Seeks to the end of the second video + long seekTimeMs = usToMs(2 * VIDEO_DURATION_US); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the second video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the last frame of the second video + .add(1991633L) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToAfterEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception { + maybeSkipTest(); + long seekTimeMs = usToMs(3 * VIDEO_DURATION_US); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the second video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the last frame of the second video + .add(1991633L) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToZero_afterPlayingSingleSequenceOfTwoImages() throws Exception { + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the second image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) .build(); // Seeked after the first playback ends, so the timestamps are repeated twice. ImmutableList expectedTimestampsUs = @@ -195,40 +279,232 @@ public class CompositionPlayerSeekTest { .addAll(sequenceTimestampsUs) .addAll(sequenceTimestampsUs) .build(); - CountDownLatch videoGraphEnded = new CountDownLatch(1); - getInstrumentation() - .runOnMainSync( - () -> { - compositionPlayer = - new CompositionPlayer.Builder(applicationContext) - .setPreviewingVideoGraphFactory( - new ListenerCapturingVideoGraphFactory(videoGraphEnded)) - .build(); - // Set a surface on the player even though there is no UI on this test. We need a - // surface otherwise the player will skip/drop video frames. - compositionPlayer.setVideoSurfaceView(surfaceView); - compositionPlayer.addListener(playerTestListener); - compositionPlayer.setComposition( - new Composition.Builder(new EditedMediaItemSequence.Builder(image, image).build()) - .build()); - compositionPlayer.prepare(); - compositionPlayer.play(); - }); - playerTestListener.waitUntilPlayerEnded(); - playerTestListener.resetStatus(); - getInstrumentation().runOnMainSync(() -> compositionPlayer.seekTo(0)); - playerTestListener.waitUntilPlayerEnded(); - assertThat(videoGraphEnded.await(VIDEO_GRAPH_END_TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs()) + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM), /* seekTimeMs= */ 0)) .isEqualTo(expectedTimestampsUs); } + @Test + public void seekToFirstImage_afterPlayingSingleSequenceOfTwoImages() throws Exception { + // Skips the first three image frames. + long seekTimeMs = 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the second image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + // Plays the first image skipping the first three frames + .addAll(skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3)) + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToStartOfSecondImage_afterPlayingSingleSequenceOfTwoImages() throws Exception { + // Seeks to the start of the second image + long seekTimeMs = usToMs(IMAGE_DURATION_US); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the second image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + // Plays the second image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToSecondImage_afterPlayingSingleSequenceOfTwoImages() throws Exception { + // Skips the first three image frames of the second image. + long seekTimeMs = usToMs(IMAGE_DURATION_US) + 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the first image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the second image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + // Plays the second image skipping the first three framees + .addAll( + transform( + skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3), + timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToZero_afterPlayingSingleSequenceOfVideoAndImage() throws Exception { + maybeSkipTest(); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + // Seeked after the first playback ends, so the timestamps are repeated twice. + ImmutableList expectedTimestampsUs = + new ImmutableList.Builder() + .addAll(sequenceTimestampsUs) + .addAll(sequenceTimestampsUs) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM), /* seekTimeMs= */ 0)) + .isEqualTo(expectedTimestampsUs); + } + + @Test + public void seekToVideo_afterPlayingSingleSequenceOfVideoAndImage() throws Exception { + maybeSkipTest(); + // Skips three video frames + long seekTimeMs = 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the video skipping the first three frames + .addAll(skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3)) + // Plays the image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToImage_afterPlayingSingleSequenceOfVideoAndImage() throws Exception { + maybeSkipTest(); + // Skips video frames and three image frames + long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the video + .addAll(VIDEO_TIMESTAMPS_US) + // Plays the image + .addAll( + transform(IMAGE_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + // Plays the image + .addAll( + transform( + skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3), + timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToZero_afterPlayingSingleSequenceOfImageAndVideo() throws Exception { + maybeSkipTest(); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + // Seeked after the first playback ends, so the timestamps are repeated twice. + ImmutableList expectedTimestampsUs = + new ImmutableList.Builder() + .addAll(sequenceTimestampsUs) + .addAll(sequenceTimestampsUs) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM), /* seekTimeMs= */ 0)) + .isEqualTo(expectedTimestampsUs); + } + + @Test + public void seekToImage_afterPlayingSingleSequenceOfImageAndVideo() throws Exception { + maybeSkipTest(); + // Skips three image frames + long seekTimeMs = 100; + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + // Seek skipping 3 image frames + .addAll(skip(IMAGE_TIMESTAMPS_US, 3)) + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + + @Test + public void seekToVideo_afterPlayingSingleSequenceOfImageAndVideo() throws Exception { + maybeSkipTest(); + // Skips to the first video frame. + long seekTimeMs = usToMs(IMAGE_DURATION_US); + ImmutableList sequenceTimestampsUs = + new ImmutableList.Builder() + // Plays the image + .addAll(IMAGE_TIMESTAMPS_US) + // Plays the video + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + // Plays the video again + .addAll( + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (IMAGE_DURATION_US + timestampUs))) + .build(); + + assertThat( + playSequenceUntilEndedAndSeekAndGetTimestampsUs( + ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM), seekTimeMs)) + .isEqualTo(sequenceTimestampsUs); + } + @Test public void seekToZero_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 15; ImmutableList expectedTimestampsUs = new ImmutableList.Builder() @@ -239,52 +515,22 @@ public class CompositionPlayerSeekTest { .addAll(VIDEO_TIMESTAMPS_US) // Plays the second video .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, /* seekTimeMs= */ 0); + mediaItems, numberOfFramesBeforeSeeking, /* seekTimeMs= */ 0); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToFirstMedia_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() + public void seekToSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); - int numberOfFramesBeforeSeeking = 15; - // 100ms into the first video, should skip the first 3 frames. - long seekTimeMs = 100; - ImmutableList expectedTimestampsUs = - new ImmutableList.Builder() - // Plays the first 15 frames of the first video - .addAll( - Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) - // Seek, skipping the first 3 frames of the first video - .addAll(Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3)) - // Plays the second video - .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) - .build(); - - ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); - - assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); - } - - @Test - public void seekToSecondMedia_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() - throws Exception { - maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 15; // 100ms into the second video, should skip the first 3 frames. long seekTimeMs = 1124; @@ -295,24 +541,23 @@ public class CompositionPlayerSeekTest { Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) // Skipping the first 3 frames of the second video .addAll( - Iterables.transform( + transform( Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3), - timestampUs -> VIDEO_DURATION_US + timestampUs)) + timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToFirstMedia_duringPlayingSecondVideoInSingleSequenceOfTwoVideos() + public void seekToFirstVideo_duringPlayingSecondVideoInSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 45; // 100ms into the first video, should skip the first 3 frames. long seekTimeMs = 100; @@ -322,62 +567,28 @@ public class CompositionPlayerSeekTest { .addAll(VIDEO_TIMESTAMPS_US) // Play the first 15 frames of the seconds video .addAll( - Iterables.transform( + transform( Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ 15), - timestampUs -> VIDEO_DURATION_US + timestampUs)) + timestampUs -> (VIDEO_DURATION_US + timestampUs))) // Seek to the first, skipping the first 3 frames. .addAll(Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3)) // Plays the second video .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToSecondMedia_duringPlayingSecondVideoInSingleSequenceOfTwoVideos() + public void seekToEndOfFirstVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); - int numberOfFramesBeforeSeeking = 45; - // 100ms into the second video, should skip the first 3 frames. - long seekTimeMs = 1124; - ImmutableList expectedTimestampsUs = - new ImmutableList.Builder() - // Play first video - .addAll(VIDEO_TIMESTAMPS_US) - // Play the first 15 frames of the seconds video - .addAll( - Iterables.transform( - Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ 15), - timestampUs -> VIDEO_DURATION_US + timestampUs)) - // Seek to the second, skipping the first 3 frames. - .addAll( - Iterables.transform( - Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3), - timestampUs -> VIDEO_DURATION_US + timestampUs)) - .build(); - - ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); - - assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); - } - - @Test - public void seekToEndOfFirstMedia_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() - throws Exception { - maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 15; // Seek to the duration of the first video. long seekTimeMs = usToMs(VIDEO_DURATION_US); @@ -388,13 +599,11 @@ public class CompositionPlayerSeekTest { Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) // Plays the second video .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) + transform(VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @@ -403,8 +612,8 @@ public class CompositionPlayerSeekTest { public void seekToEndOfSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 15; // Seek to after the composition ends. long seekTimeMs = 10_000_000L; @@ -418,16 +627,15 @@ public class CompositionPlayerSeekTest { .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToImage_fromSameImage() throws Exception { - ImmutableList mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(IMAGE_DURATION_US); + public void seekToFirstImage_duringPlayingFirstImageInSequenceOfTwoImages() throws Exception { + maybeSkipTest(); + ImmutableList mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 2; // Should skip the first 3 frames. long seekTimeMs = 100; @@ -441,16 +649,15 @@ public class CompositionPlayerSeekTest { .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToImage_fromOtherImage() throws Exception { - ImmutableList mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(IMAGE_DURATION_US, IMAGE_DURATION_US); + public void seekToSecondImage_duringPlayingFirstImageInSequenceOfTwoImages() throws Exception { + ImmutableList mediaItems = + ImmutableList.of(IMAGE_MEDIA_ITEM, IMAGE_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 2; // Should skip the first 3 frames of the second image. long seekTimeMs = Util.usToMs(IMAGE_DURATION_US) + 100; @@ -461,23 +668,22 @@ public class CompositionPlayerSeekTest { Iterables.limit(IMAGE_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) // Skipping the first 3 frames of the second image .addAll( - Iterables.transform( + transform( Iterables.skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3), - timestampUs -> IMAGE_DURATION_US + timestampUs)) + timestampUs -> (IMAGE_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToImage_fromVideoInVideoImageSequence() throws Exception { + public void seekToImage_duringPlayingFirstImageInSequenceOfVideoAndImage() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, IMAGE_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 15; // Should skip the first 3 frames of the image. long seekTimeMs = Util.usToMs(VIDEO_DURATION_US) + 100; @@ -488,88 +694,22 @@ public class CompositionPlayerSeekTest { Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) // Skipping the first 3 frames of the image .addAll( - Iterables.transform( + transform( Iterables.skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3), - timestampUs -> VIDEO_DURATION_US + timestampUs)) + timestampUs -> (VIDEO_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @Test - public void seekToImage_fromVideoInImageVideoSequence() throws Exception { + public void seekToVideo_duringPlayingFirstImageInSequenceOfImageAndVideo() throws Exception { maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(IMAGE_DURATION_US, VIDEO_DURATION_US); - // Plays all 6 image frames, play 9 video frames and seek. - int numberOfFramesBeforeSeeking = 15; - // Should skip the first 3 frames of the image. - long seekTimeMs = 100; - ImmutableList expectedTimestampsUs = - new ImmutableList.Builder() - // Play the image frames - .addAll(IMAGE_TIMESTAMPS_US) - // Play the first 9 frames of the video - .addAll( - Iterables.transform( - Iterables.limit(VIDEO_TIMESTAMPS_US, /* limitSize= */ 9), - timestampUs -> IMAGE_DURATION_US + timestampUs)) - // Skipping the first 3 frames of the image - .addAll(Iterables.skip(IMAGE_TIMESTAMPS_US, /* numberToSkip= */ 3)) - // Play the video - .addAll( - Iterables.transform( - VIDEO_TIMESTAMPS_US, timestampUs -> IMAGE_DURATION_US + timestampUs)) - .build(); - - ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); - - assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); - } - - @Test - public void seekToVideo_fromImageInVideoImageSequence() throws Exception { - maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(VIDEO_DURATION_US, IMAGE_DURATION_US); - // Play all the video, seek after playing 3 frames of image. - int numberOfFramesBeforeSeeking = 33; - // Should skip the first 3 frames of the video. - long seekTimeMs = 100; - ImmutableList expectedTimestampsUs = - new ImmutableList.Builder() - // Play the video - .addAll(VIDEO_TIMESTAMPS_US) - // Play the first 3 frames of the image - .addAll( - Iterables.transform( - Iterables.limit(IMAGE_TIMESTAMPS_US, /* limitSize= */ 3), - timestampUs -> VIDEO_DURATION_US + timestampUs)) - // Skipping the first 3 frames of the video - .addAll(Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3)) - .addAll( - Iterables.transform( - IMAGE_TIMESTAMPS_US, timestampUs -> VIDEO_DURATION_US + timestampUs)) - .build(); - - ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); - - assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); - } - - @Test - public void seekToVideo_fromImageInImageVideoSequence() throws Exception { - maybeSkipTest(); - ImmutableList mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM); - ImmutableList durationsUs = ImmutableList.of(IMAGE_DURATION_US, VIDEO_DURATION_US); + ImmutableList mediaItems = + ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM); int numberOfFramesBeforeSeeking = 3; // Should skip the first 3 frames of the video. long seekTimeMs = Util.usToMs(IMAGE_DURATION_US) + 100; @@ -580,14 +720,13 @@ public class CompositionPlayerSeekTest { Iterables.limit(IMAGE_TIMESTAMPS_US, /* limitSize= */ numberOfFramesBeforeSeeking)) // Skipping the first 3 frames of the video .addAll( - Iterables.transform( + transform( Iterables.skip(VIDEO_TIMESTAMPS_US, /* numberToSkip= */ 3), - timestampUs -> IMAGE_DURATION_US + timestampUs)) + timestampUs -> (IMAGE_DURATION_US + timestampUs))) .build(); ImmutableList actualTimestampsUs = - playSequenceAndGetTimestampsUs( - mediaItems, durationsUs, numberOfFramesBeforeSeeking, seekTimeMs); + playSequenceAndGetTimestampsUs(mediaItems, numberOfFramesBeforeSeeking, seekTimeMs); assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs); } @@ -606,21 +745,24 @@ public class CompositionPlayerSeekTest { * frames, in microsecond. */ private ImmutableList playSequenceAndGetTimestampsUs( - List mediaItems, - List durationsUs, - int numberOfFramesBeforeSeeking, - long seekTimeMs) + List mediaItems, int numberOfFramesBeforeSeeking, long seekTimeMs) throws Exception { ResettableCountDownLatch frameCountBeforeBlockLatch = new ResettableCountDownLatch(numberOfFramesBeforeSeeking); InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram = createInputTimestampRecordingShaderProgram(frameCountBeforeBlockLatch); - Effect videoEffect = (GlEffect) (context, useHdr) -> inputTimestampRecordingShaderProgram; - List editedMediaItems = - createEditedMediaItems(mediaItems, durationsUs, videoEffect); CountDownLatch videoGraphEnded = new CountDownLatch(1); AtomicReference<@NullableType PlaybackException> playbackException = new AtomicReference<>(); + List editedMediaItems = new ArrayList<>(); + for (int i = 0; i < mediaItems.size(); i++) { + editedMediaItems.add( + createEditedMediaItem( + mediaItems.get(i), + /* videoEffect= */ (GlEffect) + (context, useHdr) -> inputTimestampRecordingShaderProgram)); + } + getInstrumentation() .runOnMainSync( () -> { @@ -663,6 +805,62 @@ public class CompositionPlayerSeekTest { return inputTimestampRecordingShaderProgram.getInputTimestampsUs(); } + /** + * Plays the {@linkplain MediaItemConfig media items} until playback ends, seeks to {@code + * seekTimeMs}, resumes playback until it ends, and returns the timestamps of the processed + * frames, in microsecond. + */ + private ImmutableList playSequenceUntilEndedAndSeekAndGetTimestampsUs( + List mediaItems, long seekTimeMs) + throws PlaybackException, TimeoutException { + InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram = + new InputTimestampRecordingShaderProgram(); + CountDownLatch videoGraphEnded = new CountDownLatch(1); + AtomicReference<@NullableType PlaybackException> playbackException = new AtomicReference<>(); + + List editedMediaItems = new ArrayList<>(); + for (int i = 0; i < mediaItems.size(); i++) { + editedMediaItems.add( + createEditedMediaItem( + mediaItems.get(i), + /* videoEffect= */ (GlEffect) + (context, useHdr) -> inputTimestampRecordingShaderProgram)); + } + + getInstrumentation() + .runOnMainSync( + () -> { + compositionPlayer = + new CompositionPlayer.Builder(applicationContext) + .setPreviewingVideoGraphFactory( + new ListenerCapturingVideoGraphFactory(videoGraphEnded)) + .build(); + // Set a surface on the player even though there is no UI on this test. We need a + // surface otherwise the player will skip/drop video frames. + compositionPlayer.setVideoSurfaceView(surfaceView); + compositionPlayer.addListener(playerTestListener); + compositionPlayer.addListener( + new Player.Listener() { + @Override + public void onPlayerError(PlaybackException error) { + playbackException.set(error); + } + }); + compositionPlayer.setComposition( + new Composition.Builder( + new EditedMediaItemSequence.Builder(editedMediaItems).build()) + .build()); + compositionPlayer.prepare(); + compositionPlayer.play(); + }); + playerTestListener.waitUntilPlayerEnded(); + playerTestListener.resetStatus(); + getInstrumentation().runOnMainSync(() -> compositionPlayer.seekTo(seekTimeMs)); + playerTestListener.waitUntilPlayerEnded(); + + return inputTimestampRecordingShaderProgram.getInputTimestampsUs(); + } + /** * Creates an {@link InputTimestampRecordingShaderProgram} that blocks input after receiving the * number of frames specified by the provided {@link ResettableCountDownLatch}. @@ -713,26 +911,14 @@ public class CompositionPlayerSeekTest { /** * Returns a list of {@linkplain EditedMediaItem EditedMediaItems}. * - * @param mediaItems The {@linkplain MediaItem MediaItems} that should be wrapped. - * @param durationsUs The durations of the {@linkplain EditedMediaItem EditedMediaItems}, in - * microseconds. + * @param mediaItemConfig The {@link MediaItemConfig}. * @param videoEffect The {@link Effect} to apply to each {@link EditedMediaItem}. * @return A list of {@linkplain EditedMediaItem EditedMediaItems}. */ - private static List createEditedMediaItems( - List mediaItems, List durationsUs, Effect videoEffect) { - List editedMediaItems = new ArrayList<>(); - for (int i = 0; i < mediaItems.size(); i++) { - editedMediaItems.add( - createEditedMediaItem(mediaItems.get(i), durationsUs.get(i), videoEffect)); - } - return editedMediaItems; - } - private static EditedMediaItem createEditedMediaItem( - MediaItem mediaItem, long durationUs, Effect videoEffect) { - return new EditedMediaItem.Builder(mediaItem) - .setDurationUs(durationUs) + MediaItemConfig mediaItemConfig, Effect videoEffect) { + return new EditedMediaItem.Builder(mediaItemConfig.mediaItem) + .setDurationUs(mediaItemConfig.durationUs) .setEffects( new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.of(videoEffect))) .build(); @@ -804,6 +990,7 @@ public class CompositionPlayerSeekTest { } private static final class ResettableCountDownLatch { + private CountDownLatch latch; public ResettableCountDownLatch(int count) { @@ -832,4 +1019,15 @@ public class CompositionPlayerSeekTest { latch = new CountDownLatch(count); } } + + private static final class MediaItemConfig { + + public final MediaItem mediaItem; + public final long durationUs; + + public MediaItemConfig(MediaItem mediaItem, long durationUs) { + this.mediaItem = mediaItem; + this.durationUs = durationUs; + } + } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java index dea5221962..cc273d8b9d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java @@ -472,7 +472,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; MediaSource.MediaPeriodId mediaPeriodId) throws ExoPlaybackException { checkState(getTimeline().getWindowCount() == 1); - super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId); streamStartPositionUs = startPositionUs; // The media item might have been repeated in the sequence. int mediaItemIndex = getTimeline().getIndexOfPeriod(mediaPeriodId.periodUid); @@ -481,6 +480,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; timestampIterator = createTimestampIterator(/* positionUs= */ startPositionUs); videoEffects = currentEditedMediaItem.effects.videoEffects; inputStreamPending = true; + super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId); } @Override