From 4477eec38abfca5b86c7eba68fe4424116eefd25 Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 15 Nov 2022 11:08:58 +0000 Subject: [PATCH] Move progress computation to TransformerInternal AssetLoader will have multiple implementations and be customizable. We want to remove the responsibility of computing the progress from this class and centralize the logic in TransformerInternal. PiperOrigin-RevId: 488608890 --- .../transformer/BaseSamplePipeline.java | 19 +++++-- .../transformer/ExoPlayerAssetLoader.java | 36 +++---------- .../transformer/SamplePipeline.java | 6 +++ .../transformer/TransformerInternal.java | 51 ++++++++++++++++++- 4 files changed, 76 insertions(+), 36 deletions(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/BaseSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/BaseSamplePipeline.java index 95b523466fa..b54a27669dc 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/BaseSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/BaseSamplePipeline.java @@ -18,12 +18,14 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; +import static java.lang.Math.max; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -38,6 +40,7 @@ @Nullable private DecoderInputBuffer inputBuffer; private boolean muxerWrapperTrackAdded; + private long currentPositionMs; private boolean isEnded; public BaseSamplePipeline( @@ -65,9 +68,11 @@ public DecoderInputBuffer dequeueInputBuffer() throws TransformationException { @Override public void queueInputBuffer() throws TransformationException { - checkNotNull(inputBuffer); + DecoderInputBuffer inputBuffer = checkNotNull(this.inputBuffer); + currentPositionMs = + max(currentPositionMs, Util.usToMs(inputBuffer.timeUs - streamStartPositionUs)); checkNotNull(inputBuffer.data); - if (!shouldDropInputBuffer()) { + if (!shouldDropInputBuffer(inputBuffer)) { queueInputBufferInternal(); } } @@ -82,6 +87,11 @@ public boolean isEnded() { return isEnded; } + @Override + public long getCurrentPositionMs() { + return currentPositionMs; + } + @Nullable protected abstract DecoderInputBuffer dequeueInputBufferInternal() throws TransformationException; @@ -103,8 +113,8 @@ public boolean isEnded() { * Preprocesses an {@linkplain DecoderInputBuffer input buffer} queued to the pipeline and returns * whether it should be dropped. */ - @RequiresNonNull({"inputBuffer", "inputBuffer.data"}) - private boolean shouldDropInputBuffer() { + @RequiresNonNull("#1.data") + private boolean shouldDropInputBuffer(DecoderInputBuffer inputBuffer) { ByteBuffer inputBytes = inputBuffer.data; if (sefVideoSlowMotionFlattener == null || inputBuffer.isEndOfStream()) { @@ -112,7 +122,6 @@ private boolean shouldDropInputBuffer() { } long presentationTimeUs = inputBuffer.timeUs - streamOffsetUs; - DecoderInputBuffer inputBuffer = this.inputBuffer; boolean shouldDropInputBuffer = sefVideoSlowMotionFlattener.dropOrTransformSample(inputBytes, presentationTimeUs); if (shouldDropInputBuffer) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java index f39d62e382a..3f5d649ccc8 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java @@ -20,12 +20,7 @@ import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS; import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_MAX_BUFFER_MS; import static com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS; -import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_AVAILABLE; -import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION; -import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE; -import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static java.lang.Math.min; import android.content.Context; import android.os.Handler; @@ -48,12 +43,15 @@ import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; /* package */ final class ExoPlayerAssetLoader { public interface Listener { + void onDurationMs(long durationMs); + void onTrackRegistered(); void onAllTracksRegistered(); @@ -74,7 +72,6 @@ SamplePipeline onTrackAdded(Format format, long streamStartPositionUs, long stre private final Clock clock; @Nullable private ExoPlayer player; - private @Transformer.ProgressState int progressState; public ExoPlayerAssetLoader( Context context, @@ -89,7 +86,6 @@ public ExoPlayerAssetLoader( this.mediaSourceFactory = mediaSourceFactory; this.looper = looper; this.clock = clock; - progressState = PROGRESS_STATE_NO_TRANSFORMATION; } public void start(MediaItem mediaItem, Listener listener) { @@ -125,22 +121,9 @@ public void start(MediaItem mediaItem, Listener listener) { player.setMediaItem(mediaItem); player.addListener(new PlayerListener(listener)); player.prepare(); - - progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; - } - - public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) { - if (progressState == PROGRESS_STATE_AVAILABLE) { - Player player = checkNotNull(this.player); - long durationMs = player.getDuration(); - long positionMs = player.getCurrentPosition(); - progressHolder.progress = min((int) (positionMs * 100 / durationMs), 99); - } - return progressState; } public void release() { - progressState = PROGRESS_STATE_NO_TRANSFORMATION; if (player != null) { player.release(); player = null; @@ -191,6 +174,7 @@ public Renderer[] createRenderers( private final class PlayerListener implements Player.Listener { private final Listener listener; + private boolean hasSentDuration; public PlayerListener(Listener listener) { this.listener = listener; @@ -205,20 +189,14 @@ public void onPlaybackStateChanged(int state) { @Override public void onTimelineChanged(Timeline timeline, int reason) { - if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) { + if (hasSentDuration) { return; } Timeline.Window window = new Timeline.Window(); timeline.getWindow(/* windowIndex= */ 0, window); if (!window.isPlaceholder) { - long durationUs = window.durationUs; - // Make progress permanently unavailable if the duration is unknown, so that it doesn't jump - // to a high value at the end of the transformation if the duration is set once the media is - // entirely loaded. - progressState = - durationUs <= 0 || durationUs == C.TIME_UNSET - ? PROGRESS_STATE_UNAVAILABLE - : PROGRESS_STATE_AVAILABLE; + listener.onDurationMs(Util.usToMs(window.durationUs)); + hasSentDuration = true; checkNotNull(player).play(); } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SamplePipeline.java index 7d592bbc843..3fc6ba231d0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SamplePipeline.java @@ -49,4 +49,10 @@ /** Releases all resources held by the pipeline. */ void release(); + + /** + * Returns the current timestamp being processed in the track, in milliseconds. This is the + * largest timestamp queued minus the stream start time, or 0 if no input has been queued. + */ + long getCurrentPositionMs(); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java index 41c6602401e..48baba028d6 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java @@ -16,6 +16,12 @@ package com.google.android.exoplayer2.transformer; +import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_AVAILABLE; +import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION; +import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE; +import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY; +import static java.lang.Math.min; + import android.content.Context; import android.os.Looper; import androidx.annotation.Nullable; @@ -32,6 +38,8 @@ import com.google.android.exoplayer2.util.FrameProcessor; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; /* package */ final class TransformerInternal { @@ -50,6 +58,10 @@ public interface Listener { private final FrameProcessor.Factory frameProcessorFactory; private final DebugViewProvider debugViewProvider; private final ExoPlayerAssetLoader exoPlayerAssetLoader; + private final List samplePipelines; + + private @Transformer.ProgressState int progressState; + private long durationMs; public TransformerInternal( Context context, @@ -74,6 +86,8 @@ public TransformerInternal( exoPlayerAssetLoader = new ExoPlayerAssetLoader( context, removeAudio, removeVideo, mediaSourceFactory, looper, clock); + samplePipelines = new ArrayList<>(/* initialCapacity= */ 2); + progressState = PROGRESS_STATE_NO_TRANSFORMATION; } public void start( @@ -84,16 +98,34 @@ public void start( AssetLoaderListener assetLoaderListener = new AssetLoaderListener(mediaItem, muxerWrapper, listener, fallbackListener); exoPlayerAssetLoader.start(mediaItem, assetLoaderListener); + progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; } public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) { - return exoPlayerAssetLoader.getProgress(progressHolder); + if (progressState == PROGRESS_STATE_AVAILABLE) { + long positionMs = getCurrentPositionMs(); + progressHolder.progress = min((int) (positionMs * 100 / durationMs), 99); + } + return progressState; } public void release() { + samplePipelines.clear(); + progressState = PROGRESS_STATE_NO_TRANSFORMATION; exoPlayerAssetLoader.release(); } + private long getCurrentPositionMs() { + if (samplePipelines.isEmpty()) { + return 0; + } + long positionMsSum = 0; + for (int i = 0; i < samplePipelines.size(); i++) { + positionMsSum += samplePipelines.get(i).getCurrentPositionMs(); + } + return positionMsSum / samplePipelines.size(); + } + private class AssetLoaderListener implements ExoPlayerAssetLoader.Listener { private final MediaItem mediaItem; @@ -114,6 +146,18 @@ public AssetLoaderListener( this.fallbackListener = fallbackListener; } + @Override + public void onDurationMs(long durationMs) { + // Make progress permanently unavailable if the duration is unknown, so that it doesn't jump + // to a high value at the end of the transformation if the duration is set once the media is + // entirely loaded. + progressState = + durationMs <= 0 || durationMs == C.TIME_UNSET + ? PROGRESS_STATE_UNAVAILABLE + : PROGRESS_STATE_AVAILABLE; + TransformerInternal.this.durationMs = durationMs; + } + @Override public void onTrackRegistered() { trackRegistered = true; @@ -132,7 +176,10 @@ public void onAllTracksRegistered() { public SamplePipeline onTrackAdded( Format format, long streamStartPositionUs, long streamOffsetUs) throws TransformationException { - return getSamplePipeline(format, streamStartPositionUs, streamOffsetUs); + SamplePipeline samplePipeline = + getSamplePipeline(format, streamStartPositionUs, streamOffsetUs); + samplePipelines.add(samplePipeline); + return samplePipeline; } @Override