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 95b523466f..b54a27669d 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 @@ package com.google.android.exoplayer2.transformer; 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 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Nullable private DecoderInputBuffer inputBuffer; private boolean muxerWrapperTrackAdded; + private long currentPositionMs; private boolean isEnded; public BaseSamplePipeline( @@ -65,9 +68,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @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 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return isEnded; } + @Override + public long getCurrentPositionMs() { + return currentPositionMs; + } + @Nullable protected abstract DecoderInputBuffer dequeueInputBufferInternal() throws TransformationException; @@ -103,8 +113,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * 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 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } 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 f39d62e382..3f5d649ccc 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_FO 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.source.MediaSource; 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 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; private final Clock clock; @Nullable private ExoPlayer player; - private @Transformer.ProgressState int progressState; public ExoPlayerAssetLoader( Context context, @@ -89,7 +86,6 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; 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 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; 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 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; 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 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener; @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 7d592bbc84..3fc6ba231d 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 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; /** 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 41c6602401..48baba028d 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.Effect; 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 @@ import com.google.common.collect.ImmutableList; 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 @@ import com.google.common.collect.ImmutableList; 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 @@ import com.google.common.collect.ImmutableList; 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 @@ import com.google.common.collect.ImmutableList; 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 @@ import com.google.common.collect.ImmutableList; 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