mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add video prewarming to CompositionPlayer
PiperOrigin-RevId: 725153751
This commit is contained in:
parent
aa6183e883
commit
cadecf0219
@ -23,6 +23,7 @@ import static androidx.media3.common.util.Util.isRunningOnEmulator;
|
|||||||
import static androidx.media3.common.util.Util.usToMs;
|
import static androidx.media3.common.util.Util.usToMs;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET;
|
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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
@ -42,13 +43,19 @@ import java.util.concurrent.TimeoutException;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.AssumptionViolatedException;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TestName;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** Playback tests for {@link CompositionPlayer} */
|
/** Playback tests for {@link CompositionPlayer} */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class CompositionPlaybackTest {
|
public class CompositionPlaybackTest {
|
||||||
|
|
||||||
|
@Rule public final TestName testName = new TestName();
|
||||||
|
|
||||||
private static final long TEST_TIMEOUT_MS = isRunningOnEmulator() ? 20_000 : 10_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 MediaItem VIDEO_MEDIA_ITEM = MediaItem.fromUri(MP4_ASSET.uri);
|
||||||
private static final long VIDEO_DURATION_US = MP4_ASSET.videoDurationUs;
|
private static final long VIDEO_DURATION_US = MP4_ASSET.videoDurationUs;
|
||||||
@ -66,8 +73,14 @@ public class CompositionPlaybackTest {
|
|||||||
private final Context context = getInstrumentation().getContext().getApplicationContext();
|
private final Context context = getInstrumentation().getContext().getApplicationContext();
|
||||||
private final PlayerTestListener playerTestListener = new PlayerTestListener(TEST_TIMEOUT_MS);
|
private final PlayerTestListener playerTestListener = new PlayerTestListener(TEST_TIMEOUT_MS);
|
||||||
|
|
||||||
|
private String testId;
|
||||||
private @MonotonicNonNull CompositionPlayer player;
|
private @MonotonicNonNull CompositionPlayer player;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
testId = testName.getMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
getInstrumentation()
|
getInstrumentation()
|
||||||
@ -209,6 +222,12 @@ public class CompositionPlaybackTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_sequenceOfImageAndVideo_effectsReceiveCorrectTimestamps() throws Exception {
|
public void playback_sequenceOfImageAndVideo_effectsReceiveCorrectTimestamps() throws Exception {
|
||||||
|
if (isRunningOnEmulator()) {
|
||||||
|
// The MediaCodec decoder's output surface is sometimes dropping frames on emulator despite
|
||||||
|
// using MediaFormat.KEY_ALLOW_FRAME_DROP.
|
||||||
|
recordTestSkipped(context, testId, /* reason= */ "Skipped due to surface dropping frames");
|
||||||
|
throw new AssumptionViolatedException("Skipped due to surface dropping frames");
|
||||||
|
}
|
||||||
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
||||||
new InputTimestampRecordingShaderProgram();
|
new InputTimestampRecordingShaderProgram();
|
||||||
Effect videoEffect = (GlEffect) (context, useHdr) -> inputTimestampRecordingShaderProgram;
|
Effect videoEffect = (GlEffect) (context, useHdr) -> inputTimestampRecordingShaderProgram;
|
||||||
|
@ -126,7 +126,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToZero_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToZero_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
new ImmutableList.Builder<Long>()
|
new ImmutableList.Builder<Long>()
|
||||||
// Plays the first video
|
// Plays the first video
|
||||||
@ -150,7 +153,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToFirstVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToFirstVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Skips the first three video frames
|
// Skips the first three video frames
|
||||||
long seekTimeMs = 100;
|
long seekTimeMs = 100;
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -174,7 +180,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToStartOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToStartOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Seeks to the end of the first video
|
// Seeks to the end of the first video
|
||||||
long seekTimeMs = usToMs(VIDEO_DURATION_US);
|
long seekTimeMs = usToMs(VIDEO_DURATION_US);
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -197,7 +206,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Skips the first three image frames of the second image.
|
// Skips the first three image frames of the second image.
|
||||||
long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100;
|
long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100;
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -222,7 +234,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Seeks to the end of the second video
|
// Seeks to the end of the second video
|
||||||
long seekTimeMs = usToMs(2 * VIDEO_DURATION_US);
|
long seekTimeMs = usToMs(2 * VIDEO_DURATION_US);
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -244,7 +259,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToAfterEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToAfterEndOfSecondVideo_afterPlayingSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
long seekTimeMs = usToMs(3 * VIDEO_DURATION_US);
|
long seekTimeMs = usToMs(3 * VIDEO_DURATION_US);
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
new ImmutableList.Builder<Long>()
|
new ImmutableList.Builder<Long>()
|
||||||
@ -398,7 +416,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToZero_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
public void seekToZero_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
new ImmutableList.Builder<Long>()
|
new ImmutableList.Builder<Long>()
|
||||||
// Plays the video
|
// Plays the video
|
||||||
@ -422,7 +443,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToVideo_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
public void seekToVideo_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Skips three video frames
|
// Skips three video frames
|
||||||
long seekTimeMs = 100;
|
long seekTimeMs = 100;
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -447,7 +471,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToImage_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
public void seekToImage_afterPlayingSingleSequenceOfVideoAndImage() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Skips video frames and three image frames
|
// Skips video frames and three image frames
|
||||||
long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100;
|
long seekTimeMs = usToMs(VIDEO_DURATION_US) + 100;
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -472,7 +499,11 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToZero_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
public void seekToZero_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator()) {
|
||||||
|
// The MediaCodec decoder's output surface is sometimes dropping frames on emulator despite
|
||||||
|
// using MediaFormat.KEY_ALLOW_FRAME_DROP.
|
||||||
|
skipTest("Skipped due to surface dropping frames");
|
||||||
|
}
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
new ImmutableList.Builder<Long>()
|
new ImmutableList.Builder<Long>()
|
||||||
// Plays the image
|
// Plays the image
|
||||||
@ -496,7 +527,11 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToImage_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
public void seekToImage_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator()) {
|
||||||
|
// The MediaCodec decoder's output surface is sometimes dropping frames on emulator despite
|
||||||
|
// using MediaFormat.KEY_ALLOW_FRAME_DROP.
|
||||||
|
skipTest("Skipped due to surface dropping frames");
|
||||||
|
}
|
||||||
// Skips three image frames
|
// Skips three image frames
|
||||||
long seekTimeMs = 100;
|
long seekTimeMs = 100;
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -520,7 +555,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToVideo_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
public void seekToVideo_afterPlayingSingleSequenceOfImageAndVideo() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
// Skips to the first video frame.
|
// Skips to the first video frame.
|
||||||
long seekTimeMs = usToMs(IMAGE_DURATION_US);
|
long seekTimeMs = usToMs(IMAGE_DURATION_US);
|
||||||
ImmutableList<Long> sequenceTimestampsUs =
|
ImmutableList<Long> sequenceTimestampsUs =
|
||||||
@ -543,7 +581,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToZero_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception {
|
public void seekToZero_duringPlayingFirstVideoInSingleSequenceOfTwoVideos() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 15;
|
int numberOfFramesBeforeSeeking = 15;
|
||||||
@ -569,7 +610,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seekToSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
public void seekToSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 15;
|
int numberOfFramesBeforeSeeking = 15;
|
||||||
@ -596,7 +640,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seekToFirstVideo_duringPlayingSecondVideoInSingleSequenceOfTwoVideos()
|
public void seekToFirstVideo_duringPlayingSecondVideoInSingleSequenceOfTwoVideos()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 45;
|
int numberOfFramesBeforeSeeking = 45;
|
||||||
@ -627,7 +674,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seekToEndOfFirstVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
public void seekToEndOfFirstVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 15;
|
int numberOfFramesBeforeSeeking = 15;
|
||||||
@ -652,7 +702,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
@Test
|
@Test
|
||||||
public void seekToEndOfSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
public void seekToEndOfSecondVideo_duringPlayingFirstVideoInSingleSequenceOfTwoVideos()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 15;
|
int numberOfFramesBeforeSeeking = 15;
|
||||||
@ -675,7 +728,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToFirstImage_duringPlayingFirstImageInSequenceOfTwoImages() throws Exception {
|
public void seekToFirstImage_duringPlayingFirstImageInSequenceOfTwoImages() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM);
|
ImmutableList<MediaItemConfig> mediaItems = ImmutableList.of(IMAGE_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 2;
|
int numberOfFramesBeforeSeeking = 2;
|
||||||
// Should skip the first 3 frames.
|
// Should skip the first 3 frames.
|
||||||
@ -722,7 +778,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToImage_duringPlayingFirstImageInSequenceOfVideoAndImage() throws Exception {
|
public void seekToImage_duringPlayingFirstImageInSequenceOfVideoAndImage() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM);
|
ImmutableList.of(VIDEO_MEDIA_ITEM, IMAGE_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 15;
|
int numberOfFramesBeforeSeeking = 15;
|
||||||
@ -748,7 +807,10 @@ public class CompositionPlayerSeekTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekToVideo_duringPlayingFirstImageInSequenceOfImageAndVideo() throws Exception {
|
public void seekToVideo_duringPlayingFirstImageInSequenceOfImageAndVideo() throws Exception {
|
||||||
maybeSkipTest();
|
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
||||||
|
// The audio decoder is failing on API 31 emulator.
|
||||||
|
skipTest("Skipped due to failing decoder");
|
||||||
|
}
|
||||||
ImmutableList<MediaItemConfig> mediaItems =
|
ImmutableList<MediaItemConfig> mediaItems =
|
||||||
ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
ImmutableList.of(IMAGE_MEDIA_ITEM, VIDEO_MEDIA_ITEM);
|
||||||
int numberOfFramesBeforeSeeking = 3;
|
int numberOfFramesBeforeSeeking = 3;
|
||||||
@ -772,12 +834,9 @@ public class CompositionPlayerSeekTest {
|
|||||||
assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs);
|
assertThat(actualTimestampsUs).isEqualTo(expectedTimestampsUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeSkipTest() throws Exception {
|
private void skipTest(String reason) throws Exception {
|
||||||
if (isRunningOnEmulator() && Util.SDK_INT == 31) {
|
recordTestSkipped(applicationContext, testId, reason);
|
||||||
// The audio decoder is failing on API 31 emulator.
|
throw new AssumptionViolatedException(reason);
|
||||||
recordTestSkipped(applicationContext, testId, /* reason= */ "Skipped due to failing decoder");
|
|
||||||
throw new AssumptionViolatedException("Skipped due to failing decoder");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,7 @@ import static androidx.media3.common.PlaybackException.ERROR_CODE_VIDEO_FRAME_PR
|
|||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
|
import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
|
||||||
import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
|
import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ import android.content.Context;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
@ -36,7 +38,6 @@ import androidx.media3.common.Format;
|
|||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.ConstantRateTimestampIterator;
|
import androidx.media3.common.util.ConstantRateTimestampIterator;
|
||||||
import androidx.media3.common.util.Util;
|
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
import androidx.media3.exoplayer.Renderer;
|
import androidx.media3.exoplayer.Renderer;
|
||||||
import androidx.media3.exoplayer.RenderersFactory;
|
import androidx.media3.exoplayer.RenderersFactory;
|
||||||
@ -47,6 +48,7 @@ import androidx.media3.exoplayer.image.ImageDecoder;
|
|||||||
import androidx.media3.exoplayer.image.ImageOutput;
|
import androidx.media3.exoplayer.image.ImageOutput;
|
||||||
import androidx.media3.exoplayer.image.ImageRenderer;
|
import androidx.media3.exoplayer.image.ImageRenderer;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
||||||
|
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
||||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
@ -132,7 +134,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
eventHandler,
|
eventHandler,
|
||||||
videoRendererEventListener,
|
videoRendererEventListener,
|
||||||
sequence,
|
sequence,
|
||||||
videoSink,
|
new BufferingVideoSink(context),
|
||||||
requestToneMapping));
|
requestToneMapping));
|
||||||
renderers.add(
|
renderers.add(
|
||||||
new SequenceImageRenderer(sequence, checkStateNotNull(imageDecoderFactory), videoSink));
|
new SequenceImageRenderer(sequence, checkStateNotNull(imageDecoderFactory), videoSink));
|
||||||
@ -141,6 +143,27 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return renderers.toArray(new Renderer[0]);
|
return renderers.toArray(new Renderer[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Renderer createSecondaryRenderer(
|
||||||
|
Renderer renderer,
|
||||||
|
Handler eventHandler,
|
||||||
|
VideoRendererEventListener videoRendererEventListener,
|
||||||
|
AudioRendererEventListener audioRendererEventListener,
|
||||||
|
TextOutput textRendererOutput,
|
||||||
|
MetadataOutput metadataRendererOutput) {
|
||||||
|
if (isVideoPrewarmingEnabled() && renderer instanceof SequenceVideoRenderer) {
|
||||||
|
return new SequenceVideoRenderer(
|
||||||
|
context,
|
||||||
|
eventHandler,
|
||||||
|
videoRendererEventListener,
|
||||||
|
sequence,
|
||||||
|
new BufferingVideoSink(context),
|
||||||
|
requestToneMapping);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static long getOffsetToCompositionTimeUs(
|
private static long getOffsetToCompositionTimeUs(
|
||||||
EditedMediaItemSequence sequence, int mediaItemIndex, long offsetUs) {
|
EditedMediaItemSequence sequence, int mediaItemIndex, long offsetUs) {
|
||||||
// Reverse engineer how timestamps and offsets are computed with a ConcatenatingMediaSource2
|
// Reverse engineer how timestamps and offsets are computed with a ConcatenatingMediaSource2
|
||||||
@ -182,6 +205,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return sequence.editedMediaItems.get(index);
|
return sequence.editedMediaItems.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = 23)
|
||||||
|
private static boolean isVideoPrewarmingEnabled() {
|
||||||
|
return SDK_INT >= 23;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class SequenceAudioRenderer extends MediaCodecAudioRenderer {
|
private static final class SequenceAudioRenderer extends MediaCodecAudioRenderer {
|
||||||
private final EditedMediaItemSequence sequence;
|
private final EditedMediaItemSequence sequence;
|
||||||
private final AudioGraphInputAudioSink audioSink;
|
private final AudioGraphInputAudioSink audioSink;
|
||||||
@ -264,10 +292,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class SequenceVideoRenderer extends MediaCodecVideoRenderer {
|
private final class SequenceVideoRenderer extends MediaCodecVideoRenderer {
|
||||||
|
|
||||||
private final EditedMediaItemSequence sequence;
|
private final EditedMediaItemSequence sequence;
|
||||||
private final VideoSink videoSink;
|
private final BufferingVideoSink bufferingVideoSink;
|
||||||
private final boolean requestToneMapping;
|
private final boolean requestToneMapping;
|
||||||
|
|
||||||
private ImmutableList<Effect> pendingEffects;
|
private ImmutableList<Effect> pendingEffects;
|
||||||
@ -279,7 +307,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
Handler eventHandler,
|
Handler eventHandler,
|
||||||
VideoRendererEventListener videoRendererEventListener,
|
VideoRendererEventListener videoRendererEventListener,
|
||||||
EditedMediaItemSequence sequence,
|
EditedMediaItemSequence sequence,
|
||||||
VideoSink videoSink,
|
BufferingVideoSink bufferingVideoSink,
|
||||||
boolean requestToneMapping) {
|
boolean requestToneMapping) {
|
||||||
super(
|
super(
|
||||||
new Builder(context)
|
new Builder(context)
|
||||||
@ -291,14 +319,39 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
.setEventListener(videoRendererEventListener)
|
.setEventListener(videoRendererEventListener)
|
||||||
.setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)
|
.setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)
|
||||||
.setAssumedMinimumCodecOperatingRate(DEFAULT_FRAME_RATE)
|
.setAssumedMinimumCodecOperatingRate(DEFAULT_FRAME_RATE)
|
||||||
.setVideoSink(videoSink));
|
.setVideoSink(bufferingVideoSink));
|
||||||
this.sequence = sequence;
|
this.sequence = sequence;
|
||||||
this.videoSink = videoSink;
|
this.bufferingVideoSink = bufferingVideoSink;
|
||||||
this.requestToneMapping = requestToneMapping;
|
this.requestToneMapping = requestToneMapping;
|
||||||
this.pendingEffects = ImmutableList.of();
|
this.pendingEffects = ImmutableList.of();
|
||||||
experimentalEnableProcessedStreamChangedAtStart();
|
experimentalEnableProcessedStreamChangedAtStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
if (mayRenderStartOfStream) {
|
||||||
|
// Activate the BufferingVideoSink before calling super.onEnabled(), so that it points to a
|
||||||
|
// VideoSink when executing the super method.
|
||||||
|
activateBufferingVideoSink();
|
||||||
|
}
|
||||||
|
super.onEnabled(joining, mayRenderStartOfStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStarted() {
|
||||||
|
// Activate the BufferingVideoSink before calling super.onStarted(), so that it points to a
|
||||||
|
// VideoSink when executing the super method.
|
||||||
|
activateBufferingVideoSink();
|
||||||
|
super.onStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisabled() {
|
||||||
|
super.onDisabled();
|
||||||
|
deactivateBufferingVideoSink();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStreamChanged(
|
protected void onStreamChanged(
|
||||||
Format[] formats,
|
Format[] formats,
|
||||||
@ -333,13 +386,35 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
codecOperatingRate,
|
codecOperatingRate,
|
||||||
deviceNeedsNoPostProcessWorkaround,
|
deviceNeedsNoPostProcessWorkaround,
|
||||||
tunnelingAudioSessionId);
|
tunnelingAudioSessionId);
|
||||||
if (requestToneMapping && Util.SDK_INT >= 31) {
|
if (requestToneMapping && SDK_INT >= 31) {
|
||||||
mediaFormat.setInteger(
|
mediaFormat.setInteger(
|
||||||
MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
|
MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
|
||||||
}
|
}
|
||||||
return mediaFormat;
|
return mediaFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(@MessageType int messageType, @Nullable Object message)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
if (messageType == MSG_TRANSFER_RESOURCES) {
|
||||||
|
// Ignore MSG_TRANSFER_RESOURCES to avoid updating the VideoGraph's output surface.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.handleMessage(messageType, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
|
||||||
|
if (isVideoPrewarmingEnabled()
|
||||||
|
&& bufferingVideoSink.getVideoSink() == null
|
||||||
|
&& codecNeedsSetOutputSurfaceWorkaround(codecInfo.name)) {
|
||||||
|
// Wait until the BufferingVideoSink points to the effect VideoSink to init the codec, so
|
||||||
|
// that the codec output surface is set to the effect VideoSink input surface.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.shouldInitCodec(codecInfo);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long getBufferTimestampAdjustmentUs() {
|
protected long getBufferTimestampAdjustmentUs() {
|
||||||
return offsetToCompositionTimeUs;
|
return offsetToCompositionTimeUs;
|
||||||
@ -349,7 +424,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
protected void renderToEndOfStream() {
|
protected void renderToEndOfStream() {
|
||||||
super.renderToEndOfStream();
|
super.renderToEndOfStream();
|
||||||
if (isLastInSequence(getTimeline(), sequence, checkNotNull(currentEditedMediaItem))) {
|
if (isLastInSequence(getTimeline(), sequence, checkNotNull(currentEditedMediaItem))) {
|
||||||
videoSink.signalEndOfInput();
|
bufferingVideoSink.signalEndOfInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,6 +433,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
||||||
videoSink.onInputStreamChanged(inputType, format, pendingEffects);
|
videoSink.onInputStreamChanged(inputType, format, pendingEffects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activateBufferingVideoSink() {
|
||||||
|
if (bufferingVideoSink.getVideoSink() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VideoSink frameProcessingVideoSink = checkNotNull(SequenceRenderersFactory.this.videoSink);
|
||||||
|
bufferingVideoSink.setVideoSink(frameProcessingVideoSink);
|
||||||
|
@Nullable MediaCodecAdapter codec = getCodec();
|
||||||
|
if (isVideoPrewarmingEnabled()
|
||||||
|
&& frameProcessingVideoSink.isInitialized()
|
||||||
|
&& codec != null
|
||||||
|
&& !codecNeedsSetOutputSurfaceWorkaround(checkNotNull(getCodecInfo()).name)) {
|
||||||
|
setOutputSurfaceV23(codec, frameProcessingVideoSink.getInputSurface());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deactivateBufferingVideoSink() {
|
||||||
|
if (!isVideoPrewarmingEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bufferingVideoSink.setVideoSink(null);
|
||||||
|
// During a seek, it's possible for the renderer to be disabled without having been started.
|
||||||
|
// When this happens, the BufferingVideoSink can have pending operations, so they need to be
|
||||||
|
// cleared.
|
||||||
|
bufferingVideoSink.clearPendingOperations();
|
||||||
|
@Nullable MediaCodecAdapter codec = getCodec();
|
||||||
|
if (codec == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!codecNeedsSetOutputSurfaceWorkaround(checkNotNull(getCodecInfo()).name)) {
|
||||||
|
// Sets a placeholder surface
|
||||||
|
setOutputSurfaceV23(codec, bufferingVideoSink.getInputSurface());
|
||||||
|
} else {
|
||||||
|
releaseCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class SequenceImageRenderer extends ImageRenderer {
|
private static final class SequenceImageRenderer extends ImageRenderer {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user