mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Fix composition player repeat mode
Some checks in SingleInputVideoGraph were causing CompositionPlayer to throw for a single media item sequence when repeat mode was enabled. The reason was that, in this case, no new input stream is registered to the VideoFrameProcessor. PiperOrigin-RevId: 715409509
This commit is contained in:
parent
2361624222
commit
fbf9be2f00
@ -25,8 +25,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.DebugViewProvider;
|
||||
import androidx.media3.common.Effect;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.SurfaceInfo;
|
||||
import androidx.media3.common.VideoCompositorSettings;
|
||||
import androidx.media3.common.VideoFrameProcessingException;
|
||||
@ -34,7 +32,6 @@ import androidx.media3.common.VideoFrameProcessor;
|
||||
import androidx.media3.common.VideoGraph;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/** A {@link VideoGraph} that handles one input stream. */
|
||||
@ -111,17 +108,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
||||
/* listenerExecutor= */ MoreExecutors.directExecutor(),
|
||||
new VideoFrameProcessor.Listener() {
|
||||
private long lastProcessedFramePresentationTimeUs;
|
||||
private boolean isEnded;
|
||||
|
||||
@Override
|
||||
public void onInputStreamRegistered(
|
||||
@VideoFrameProcessor.InputType int inputType,
|
||||
Format format,
|
||||
List<Effect> effects) {
|
||||
// An input stream could be registered after VideoFrameProcessor ends, following
|
||||
// a flush() for example.
|
||||
isEnded = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputSizeChanged(int width, int height) {
|
||||
@ -135,12 +121,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
||||
|
||||
@Override
|
||||
public void onOutputFrameAvailableForRendering(long presentationTimeUs) {
|
||||
if (isEnded) {
|
||||
onError(
|
||||
new VideoFrameProcessingException(
|
||||
"onOutputFrameAvailableForRendering() received after onEnded()"));
|
||||
return;
|
||||
}
|
||||
// Frames are rendered automatically.
|
||||
if (presentationTimeUs == 0) {
|
||||
hasProducedFrameWithTimestampZero = true;
|
||||
@ -157,11 +137,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
||||
|
||||
@Override
|
||||
public void onEnded() {
|
||||
if (isEnded) {
|
||||
onError(new VideoFrameProcessingException("onEnded() received multiple times"));
|
||||
return;
|
||||
}
|
||||
isEnded = true;
|
||||
listenerExecutor.execute(
|
||||
() -> listener.onEnded(lastProcessedFramePresentationTimeUs));
|
||||
}
|
||||
|
@ -16,19 +16,28 @@
|
||||
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION;
|
||||
import static androidx.media3.common.Player.REPEAT_MODE_ALL;
|
||||
import static androidx.media3.common.Player.REPEAT_MODE_OFF;
|
||||
import static androidx.media3.common.util.Util.isRunningOnEmulator;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.media3.common.Effect;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.effect.GlEffect;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
@ -241,8 +250,9 @@ public class CompositionPlaybackTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_sequenceOfThreeVideosWithRemovingFirstAndLastAudio_succeeds()
|
||||
throws Exception {
|
||||
public void
|
||||
playback_sequenceOfThreeVideosWithRemovingFirstAndLastAudio_effectsReceiveCorrectTimestamps()
|
||||
throws Exception {
|
||||
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
||||
new InputTimestampRecordingShaderProgram();
|
||||
EditedMediaItem videoEditedMediaItem =
|
||||
@ -291,7 +301,9 @@ public class CompositionPlaybackTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_sequenceOfThreeVideosWithRemovingMiddleAudio_succeeds() throws Exception {
|
||||
public void
|
||||
playback_sequenceOfThreeVideosWithRemovingMiddleAudio_effectsReceiveCorrectTimestamps()
|
||||
throws Exception {
|
||||
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
||||
new InputTimestampRecordingShaderProgram();
|
||||
EditedMediaItem videoEditedMediaItem =
|
||||
@ -450,4 +462,52 @@ public class CompositionPlaybackTest {
|
||||
assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs())
|
||||
.isEqualTo(expectedTimestampsUs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_withRepeatModeSet_succeeds() throws Exception {
|
||||
EditedMediaItem editedMediaItem =
|
||||
new EditedMediaItem.Builder(VIDEO_MEDIA_ITEM).setDurationUs(VIDEO_DURATION_US).build();
|
||||
Composition composition =
|
||||
new Composition.Builder(new EditedMediaItemSequence.Builder(editedMediaItem).build())
|
||||
.build();
|
||||
CountDownLatch repetitionEndedLatch = new CountDownLatch(2);
|
||||
AtomicReference<@NullableType PlaybackException> playbackException = new AtomicReference<>();
|
||||
|
||||
getInstrumentation()
|
||||
.runOnMainSync(
|
||||
() -> {
|
||||
player = new CompositionPlayer.Builder(context).build();
|
||||
player.addListener(playerTestListener);
|
||||
player.addListener(
|
||||
new Player.Listener() {
|
||||
@Override
|
||||
public void onPositionDiscontinuity(
|
||||
Player.PositionInfo oldPosition,
|
||||
Player.PositionInfo newPosition,
|
||||
int reason) {
|
||||
if (reason == DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
repetitionEndedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
playbackException.set(error);
|
||||
while (repetitionEndedLatch.getCount() > 0) {
|
||||
repetitionEndedLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
player.setComposition(composition);
|
||||
player.setRepeatMode(REPEAT_MODE_ALL);
|
||||
player.prepare();
|
||||
player.play();
|
||||
});
|
||||
boolean latchTimedOut = !repetitionEndedLatch.await(TEST_TIMEOUT_MS, MILLISECONDS);
|
||||
|
||||
assertThat(playbackException.get()).isNull();
|
||||
assertThat(latchTimedOut).isFalse();
|
||||
getInstrumentation().runOnMainSync(() -> player.setRepeatMode(REPEAT_MODE_OFF));
|
||||
playerTestListener.waitUntilPlayerEnded();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user