mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
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
This commit is contained in:
parent
59aedcf309
commit
66dc6b3242
@ -18,11 +18,13 @@ package androidx.media3.transformer;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -38,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@Nullable private DecoderInputBuffer inputBuffer;
|
@Nullable private DecoderInputBuffer inputBuffer;
|
||||||
private boolean muxerWrapperTrackAdded;
|
private boolean muxerWrapperTrackAdded;
|
||||||
|
private long currentPositionMs;
|
||||||
private boolean isEnded;
|
private boolean isEnded;
|
||||||
|
|
||||||
public BaseSamplePipeline(
|
public BaseSamplePipeline(
|
||||||
@ -65,9 +68,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputBuffer() throws TransformationException {
|
public void queueInputBuffer() throws TransformationException {
|
||||||
checkNotNull(inputBuffer);
|
DecoderInputBuffer inputBuffer = checkNotNull(this.inputBuffer);
|
||||||
|
currentPositionMs =
|
||||||
|
max(currentPositionMs, Util.usToMs(inputBuffer.timeUs - streamStartPositionUs));
|
||||||
checkNotNull(inputBuffer.data);
|
checkNotNull(inputBuffer.data);
|
||||||
if (!shouldDropInputBuffer()) {
|
if (!shouldDropInputBuffer(inputBuffer)) {
|
||||||
queueInputBufferInternal();
|
queueInputBufferInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,6 +87,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
return isEnded;
|
return isEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPositionMs() {
|
||||||
|
return currentPositionMs;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected abstract DecoderInputBuffer dequeueInputBufferInternal() throws TransformationException;
|
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
|
* Preprocesses an {@linkplain DecoderInputBuffer input buffer} queued to the pipeline and returns
|
||||||
* whether it should be dropped.
|
* whether it should be dropped.
|
||||||
*/
|
*/
|
||||||
@RequiresNonNull({"inputBuffer", "inputBuffer.data"})
|
@RequiresNonNull("#1.data")
|
||||||
private boolean shouldDropInputBuffer() {
|
private boolean shouldDropInputBuffer(DecoderInputBuffer inputBuffer) {
|
||||||
ByteBuffer inputBytes = inputBuffer.data;
|
ByteBuffer inputBytes = inputBuffer.data;
|
||||||
|
|
||||||
if (sefVideoSlowMotionFlattener == null || inputBuffer.isEndOfStream()) {
|
if (sefVideoSlowMotionFlattener == null || inputBuffer.isEndOfStream()) {
|
||||||
@ -112,7 +122,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
long presentationTimeUs = inputBuffer.timeUs - streamOffsetUs;
|
long presentationTimeUs = inputBuffer.timeUs - streamOffsetUs;
|
||||||
DecoderInputBuffer inputBuffer = this.inputBuffer;
|
|
||||||
boolean shouldDropInputBuffer =
|
boolean shouldDropInputBuffer =
|
||||||
sefVideoSlowMotionFlattener.dropOrTransformSample(inputBytes, presentationTimeUs);
|
sefVideoSlowMotionFlattener.dropOrTransformSample(inputBytes, presentationTimeUs);
|
||||||
if (shouldDropInputBuffer) {
|
if (shouldDropInputBuffer) {
|
||||||
|
@ -21,11 +21,6 @@ import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PL
|
|||||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
||||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
|
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
|
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
|
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -39,6 +34,7 @@ import androidx.media3.common.Player;
|
|||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl;
|
import androidx.media3.exoplayer.DefaultLoadControl;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.Renderer;
|
import androidx.media3.exoplayer.Renderer;
|
||||||
@ -54,6 +50,8 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
|
|
||||||
|
void onDurationMs(long durationMs);
|
||||||
|
|
||||||
void onTrackRegistered();
|
void onTrackRegistered();
|
||||||
|
|
||||||
void onAllTracksRegistered();
|
void onAllTracksRegistered();
|
||||||
@ -74,7 +72,6 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
@Nullable private ExoPlayer player;
|
@Nullable private ExoPlayer player;
|
||||||
private @Transformer.ProgressState int progressState;
|
|
||||||
|
|
||||||
public ExoPlayerAssetLoader(
|
public ExoPlayerAssetLoader(
|
||||||
Context context,
|
Context context,
|
||||||
@ -89,7 +86,6 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
this.mediaSourceFactory = mediaSourceFactory;
|
this.mediaSourceFactory = mediaSourceFactory;
|
||||||
this.looper = looper;
|
this.looper = looper;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(MediaItem mediaItem, Listener listener) {
|
public void start(MediaItem mediaItem, Listener listener) {
|
||||||
@ -125,22 +121,9 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
player.setMediaItem(mediaItem);
|
player.setMediaItem(mediaItem);
|
||||||
player.addListener(new PlayerListener(listener));
|
player.addListener(new PlayerListener(listener));
|
||||||
player.prepare();
|
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() {
|
public void release() {
|
||||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.release();
|
player.release();
|
||||||
player = null;
|
player = null;
|
||||||
@ -191,6 +174,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
private final class PlayerListener implements Player.Listener {
|
private final class PlayerListener implements Player.Listener {
|
||||||
|
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
|
private boolean hasSentDuration;
|
||||||
|
|
||||||
public PlayerListener(Listener listener) {
|
public PlayerListener(Listener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@ -205,20 +189,14 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||||
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
if (hasSentDuration) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Timeline.Window window = new Timeline.Window();
|
Timeline.Window window = new Timeline.Window();
|
||||||
timeline.getWindow(/* windowIndex= */ 0, window);
|
timeline.getWindow(/* windowIndex= */ 0, window);
|
||||||
if (!window.isPlaceholder) {
|
if (!window.isPlaceholder) {
|
||||||
long durationUs = window.durationUs;
|
listener.onDurationMs(Util.usToMs(window.durationUs));
|
||||||
// Make progress permanently unavailable if the duration is unknown, so that it doesn't jump
|
hasSentDuration = true;
|
||||||
// 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;
|
|
||||||
checkNotNull(player).play();
|
checkNotNull(player).play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,4 +49,10 @@ import androidx.media3.decoder.DecoderInputBuffer;
|
|||||||
|
|
||||||
/** Releases all resources held by the pipeline. */
|
/** Releases all resources held by the pipeline. */
|
||||||
void release();
|
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();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -32,6 +38,8 @@ import androidx.media3.common.util.Clock;
|
|||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/* package */ final class TransformerInternal {
|
/* package */ final class TransformerInternal {
|
||||||
|
|
||||||
@ -50,6 +58,10 @@ import com.google.common.collect.ImmutableList;
|
|||||||
private final FrameProcessor.Factory frameProcessorFactory;
|
private final FrameProcessor.Factory frameProcessorFactory;
|
||||||
private final DebugViewProvider debugViewProvider;
|
private final DebugViewProvider debugViewProvider;
|
||||||
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
||||||
|
private final List<SamplePipeline> samplePipelines;
|
||||||
|
|
||||||
|
private @Transformer.ProgressState int progressState;
|
||||||
|
private long durationMs;
|
||||||
|
|
||||||
public TransformerInternal(
|
public TransformerInternal(
|
||||||
Context context,
|
Context context,
|
||||||
@ -74,6 +86,8 @@ import com.google.common.collect.ImmutableList;
|
|||||||
exoPlayerAssetLoader =
|
exoPlayerAssetLoader =
|
||||||
new ExoPlayerAssetLoader(
|
new ExoPlayerAssetLoader(
|
||||||
context, removeAudio, removeVideo, mediaSourceFactory, looper, clock);
|
context, removeAudio, removeVideo, mediaSourceFactory, looper, clock);
|
||||||
|
samplePipelines = new ArrayList<>(/* initialCapacity= */ 2);
|
||||||
|
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(
|
public void start(
|
||||||
@ -84,16 +98,34 @@ import com.google.common.collect.ImmutableList;
|
|||||||
AssetLoaderListener assetLoaderListener =
|
AssetLoaderListener assetLoaderListener =
|
||||||
new AssetLoaderListener(mediaItem, muxerWrapper, listener, fallbackListener);
|
new AssetLoaderListener(mediaItem, muxerWrapper, listener, fallbackListener);
|
||||||
exoPlayerAssetLoader.start(mediaItem, assetLoaderListener);
|
exoPlayerAssetLoader.start(mediaItem, assetLoaderListener);
|
||||||
|
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
|
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() {
|
public void release() {
|
||||||
|
samplePipelines.clear();
|
||||||
|
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||||
exoPlayerAssetLoader.release();
|
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 class AssetLoaderListener implements ExoPlayerAssetLoader.Listener {
|
||||||
|
|
||||||
private final MediaItem mediaItem;
|
private final MediaItem mediaItem;
|
||||||
@ -114,6 +146,18 @@ import com.google.common.collect.ImmutableList;
|
|||||||
this.fallbackListener = fallbackListener;
|
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
|
@Override
|
||||||
public void onTrackRegistered() {
|
public void onTrackRegistered() {
|
||||||
trackRegistered = true;
|
trackRegistered = true;
|
||||||
@ -132,7 +176,10 @@ import com.google.common.collect.ImmutableList;
|
|||||||
public SamplePipeline onTrackAdded(
|
public SamplePipeline onTrackAdded(
|
||||||
Format format, long streamStartPositionUs, long streamOffsetUs)
|
Format format, long streamStartPositionUs, long streamOffsetUs)
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
return getSamplePipeline(format, streamStartPositionUs, streamOffsetUs);
|
SamplePipeline samplePipeline =
|
||||||
|
getSamplePipeline(format, streamStartPositionUs, streamOffsetUs);
|
||||||
|
samplePipelines.add(samplePipeline);
|
||||||
|
return samplePipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user