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.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.DebugViewProvider;
|
import androidx.media3.common.DebugViewProvider;
|
||||||
import androidx.media3.common.Effect;
|
|
||||||
import androidx.media3.common.Format;
|
|
||||||
import androidx.media3.common.SurfaceInfo;
|
import androidx.media3.common.SurfaceInfo;
|
||||||
import androidx.media3.common.VideoCompositorSettings;
|
import androidx.media3.common.VideoCompositorSettings;
|
||||||
import androidx.media3.common.VideoFrameProcessingException;
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
@ -34,7 +32,6 @@ import androidx.media3.common.VideoFrameProcessor;
|
|||||||
import androidx.media3.common.VideoGraph;
|
import androidx.media3.common.VideoGraph;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/** A {@link VideoGraph} that handles one input stream. */
|
/** A {@link VideoGraph} that handles one input stream. */
|
||||||
@ -111,17 +108,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
|||||||
/* listenerExecutor= */ MoreExecutors.directExecutor(),
|
/* listenerExecutor= */ MoreExecutors.directExecutor(),
|
||||||
new VideoFrameProcessor.Listener() {
|
new VideoFrameProcessor.Listener() {
|
||||||
private long lastProcessedFramePresentationTimeUs;
|
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
|
@Override
|
||||||
public void onOutputSizeChanged(int width, int height) {
|
public void onOutputSizeChanged(int width, int height) {
|
||||||
@ -135,12 +121,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOutputFrameAvailableForRendering(long presentationTimeUs) {
|
public void onOutputFrameAvailableForRendering(long presentationTimeUs) {
|
||||||
if (isEnded) {
|
|
||||||
onError(
|
|
||||||
new VideoFrameProcessingException(
|
|
||||||
"onOutputFrameAvailableForRendering() received after onEnded()"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Frames are rendered automatically.
|
// Frames are rendered automatically.
|
||||||
if (presentationTimeUs == 0) {
|
if (presentationTimeUs == 0) {
|
||||||
hasProducedFrameWithTimestampZero = true;
|
hasProducedFrameWithTimestampZero = true;
|
||||||
@ -157,11 +137,6 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnded() {
|
public void onEnded() {
|
||||||
if (isEnded) {
|
|
||||||
onError(new VideoFrameProcessingException("onEnded() received multiple times"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isEnded = true;
|
|
||||||
listenerExecutor.execute(
|
listenerExecutor.execute(
|
||||||
() -> listener.onEnded(lastProcessedFramePresentationTimeUs));
|
() -> listener.onEnded(lastProcessedFramePresentationTimeUs));
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,28 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
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.common.util.Util.isRunningOnEmulator;
|
||||||
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.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 android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.MediaItem;
|
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.media3.effect.GlEffect;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
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.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -241,7 +250,8 @@ public class CompositionPlaybackTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_sequenceOfThreeVideosWithRemovingFirstAndLastAudio_succeeds()
|
public void
|
||||||
|
playback_sequenceOfThreeVideosWithRemovingFirstAndLastAudio_effectsReceiveCorrectTimestamps()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
||||||
new InputTimestampRecordingShaderProgram();
|
new InputTimestampRecordingShaderProgram();
|
||||||
@ -291,7 +301,9 @@ public class CompositionPlaybackTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_sequenceOfThreeVideosWithRemovingMiddleAudio_succeeds() throws Exception {
|
public void
|
||||||
|
playback_sequenceOfThreeVideosWithRemovingMiddleAudio_effectsReceiveCorrectTimestamps()
|
||||||
|
throws Exception {
|
||||||
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram =
|
||||||
new InputTimestampRecordingShaderProgram();
|
new InputTimestampRecordingShaderProgram();
|
||||||
EditedMediaItem videoEditedMediaItem =
|
EditedMediaItem videoEditedMediaItem =
|
||||||
@ -450,4 +462,52 @@ public class CompositionPlaybackTest {
|
|||||||
assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs())
|
assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs())
|
||||||
.isEqualTo(expectedTimestampsUs);
|
.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