Add ExoPlayerAssetLoader
Just move some code around for now, to start setting up the overall structure. PiperOrigin-RevId: 487229329 (cherry picked from commit 95f37b4df8475e5eaf09da0a5d4dee04a81646a0)
This commit is contained in:
parent
d262f76047
commit
c89ceb878d
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_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_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.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DebugViewProvider;
|
||||
import androidx.media3.common.Effect;
|
||||
import androidx.media3.common.FrameProcessor;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.exoplayer.DefaultLoadControl;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.Renderer;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.text.TextOutput;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/* package */ final class ExoPlayerAssetLoader {
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void onEnded();
|
||||
|
||||
void onError(Exception e);
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final TransformationRequest transformationRequest;
|
||||
private final ImmutableList<Effect> videoEffects;
|
||||
private final boolean removeAudio;
|
||||
private final boolean removeVideo;
|
||||
private final MediaSource.Factory mediaSourceFactory;
|
||||
private final Codec.DecoderFactory decoderFactory;
|
||||
private final Codec.EncoderFactory encoderFactory;
|
||||
private final FrameProcessor.Factory frameProcessorFactory;
|
||||
private final Looper looper;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
private final Clock clock;
|
||||
|
||||
private @MonotonicNonNull MuxerWrapper muxerWrapper;
|
||||
@Nullable private ExoPlayer player;
|
||||
private @Transformer.ProgressState int progressState;
|
||||
|
||||
public ExoPlayerAssetLoader(
|
||||
Context context,
|
||||
TransformationRequest transformationRequest,
|
||||
ImmutableList<Effect> videoEffects,
|
||||
boolean removeAudio,
|
||||
boolean removeVideo,
|
||||
MediaSource.Factory mediaSourceFactory,
|
||||
Codec.DecoderFactory decoderFactory,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
FrameProcessor.Factory frameProcessorFactory,
|
||||
Looper looper,
|
||||
DebugViewProvider debugViewProvider,
|
||||
Clock clock) {
|
||||
this.context = context;
|
||||
this.transformationRequest = transformationRequest;
|
||||
this.videoEffects = videoEffects;
|
||||
this.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.mediaSourceFactory = mediaSourceFactory;
|
||||
this.decoderFactory = decoderFactory;
|
||||
this.encoderFactory = encoderFactory;
|
||||
this.frameProcessorFactory = frameProcessorFactory;
|
||||
this.looper = looper;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
this.clock = clock;
|
||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||
}
|
||||
|
||||
public void start(
|
||||
MediaItem mediaItem,
|
||||
MuxerWrapper muxerWrapper,
|
||||
Listener listener,
|
||||
FallbackListener fallbackListener,
|
||||
Transformer.AsyncErrorListener asyncErrorListener) {
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
|
||||
trackSelector.setParameters(
|
||||
new DefaultTrackSelector.Parameters.Builder(context)
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.build());
|
||||
// Arbitrarily decrease buffers for playback so that samples start being sent earlier to the
|
||||
// muxer (rebuffers are less problematic for the transformation use case).
|
||||
DefaultLoadControl loadControl =
|
||||
new DefaultLoadControl.Builder()
|
||||
.setBufferDurationsMs(
|
||||
DEFAULT_MIN_BUFFER_MS,
|
||||
DEFAULT_MAX_BUFFER_MS,
|
||||
DEFAULT_BUFFER_FOR_PLAYBACK_MS / 10,
|
||||
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10)
|
||||
.build();
|
||||
ExoPlayer.Builder playerBuilder =
|
||||
new ExoPlayer.Builder(
|
||||
context,
|
||||
new RenderersFactoryImpl(
|
||||
context,
|
||||
muxerWrapper,
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
transformationRequest,
|
||||
mediaItem.clippingConfiguration.startsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
fallbackListener,
|
||||
asyncErrorListener,
|
||||
debugViewProvider))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
.setLooper(looper);
|
||||
if (clock != Clock.DEFAULT) {
|
||||
// Transformer.Builder#setClock is also @VisibleForTesting, so if we're using a non-default
|
||||
// clock we must be in a test context.
|
||||
@SuppressWarnings("VisibleForTests")
|
||||
ExoPlayer.Builder unusedForAnnotation = playerBuilder.setClock(clock);
|
||||
}
|
||||
|
||||
player = playerBuilder.build();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RenderersFactoryImpl implements RenderersFactory {
|
||||
|
||||
private final Context context;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final TransformerMediaClock mediaClock;
|
||||
private final boolean removeAudio;
|
||||
private final boolean removeVideo;
|
||||
private final TransformationRequest transformationRequest;
|
||||
private final boolean clippingStartsAtKeyFrame;
|
||||
private final ImmutableList<Effect> videoEffects;
|
||||
private final FrameProcessor.Factory frameProcessorFactory;
|
||||
private final Codec.EncoderFactory encoderFactory;
|
||||
private final Codec.DecoderFactory decoderFactory;
|
||||
private final FallbackListener fallbackListener;
|
||||
private final Transformer.AsyncErrorListener asyncErrorListener;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
|
||||
public RenderersFactoryImpl(
|
||||
Context context,
|
||||
MuxerWrapper muxerWrapper,
|
||||
boolean removeAudio,
|
||||
boolean removeVideo,
|
||||
TransformationRequest transformationRequest,
|
||||
boolean clippingStartsAtKeyFrame,
|
||||
ImmutableList<Effect> videoEffects,
|
||||
FrameProcessor.Factory frameProcessorFactory,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
Codec.DecoderFactory decoderFactory,
|
||||
FallbackListener fallbackListener,
|
||||
Transformer.AsyncErrorListener asyncErrorListener,
|
||||
DebugViewProvider debugViewProvider) {
|
||||
this.context = context;
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
this.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.transformationRequest = transformationRequest;
|
||||
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
|
||||
this.videoEffects = videoEffects;
|
||||
this.frameProcessorFactory = frameProcessorFactory;
|
||||
this.encoderFactory = encoderFactory;
|
||||
this.decoderFactory = decoderFactory;
|
||||
this.fallbackListener = fallbackListener;
|
||||
this.asyncErrorListener = asyncErrorListener;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
mediaClock = new TransformerMediaClock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderer[] createRenderers(
|
||||
Handler eventHandler,
|
||||
VideoRendererEventListener videoRendererEventListener,
|
||||
AudioRendererEventListener audioRendererEventListener,
|
||||
TextOutput textRendererOutput,
|
||||
MetadataOutput metadataRendererOutput) {
|
||||
int rendererCount = removeAudio || removeVideo ? 1 : 2;
|
||||
Renderer[] renderers = new Renderer[rendererCount];
|
||||
int index = 0;
|
||||
if (!removeAudio) {
|
||||
renderers[index] =
|
||||
new TransformerAudioRenderer(
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener);
|
||||
index++;
|
||||
}
|
||||
if (!removeVideo) {
|
||||
renderers[index] =
|
||||
new TransformerVideoRenderer(
|
||||
context,
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
clippingStartsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener,
|
||||
debugViewProvider);
|
||||
index++;
|
||||
}
|
||||
return renderers;
|
||||
}
|
||||
}
|
||||
|
||||
private final class PlayerListener implements Player.Listener {
|
||||
|
||||
private final Listener listener;
|
||||
|
||||
public PlayerListener(Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(int state) {
|
||||
if (state == Player.STATE_ENDED) {
|
||||
listener.onEnded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||
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;
|
||||
checkNotNull(player).play();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
if (checkNotNull(muxerWrapper).getTrackCount() == 0) {
|
||||
listener.onError(new IllegalStateException("The output does not contain any tracks."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
listener.onError(error);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,11 +18,6 @@ package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_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_MIN_BUFFER_MS;
|
||||
import static java.lang.Math.min;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.content.Context;
|
||||
@ -41,9 +36,6 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.ListenerSet;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -51,17 +43,8 @@ import androidx.media3.common.util.Util;
|
||||
import androidx.media3.effect.GlEffect;
|
||||
import androidx.media3.effect.GlEffectsFrameProcessor;
|
||||
import androidx.media3.effect.GlMatrixTransformation;
|
||||
import androidx.media3.exoplayer.DefaultLoadControl;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.Renderer;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.text.TextOutput;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory;
|
||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -571,12 +554,12 @@ public final class Transformer {
|
||||
private final Looper looper;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
private final Clock clock;
|
||||
private final ExoPlayerAssetLoader exoPlayerAssetLoader;
|
||||
|
||||
@Nullable private MuxerWrapper muxerWrapper;
|
||||
@Nullable private ExoPlayer player;
|
||||
@Nullable private String outputPath;
|
||||
@Nullable private ParcelFileDescriptor outputParcelFileDescriptor;
|
||||
private @ProgressState int progressState;
|
||||
private boolean transformationInProgress;
|
||||
private boolean isCancelling;
|
||||
|
||||
private Transformer(
|
||||
@ -609,7 +592,20 @@ public final class Transformer {
|
||||
this.looper = looper;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
this.clock = clock;
|
||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||
exoPlayerAssetLoader =
|
||||
new ExoPlayerAssetLoader(
|
||||
context,
|
||||
transformationRequest,
|
||||
videoEffects,
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
mediaSourceFactory,
|
||||
decoderFactory,
|
||||
encoderFactory,
|
||||
frameProcessorFactory,
|
||||
looper,
|
||||
debugViewProvider,
|
||||
clock);
|
||||
}
|
||||
|
||||
/** Returns a {@link Transformer.Builder} initialized with the values of this instance. */
|
||||
@ -721,66 +717,26 @@ public final class Transformer {
|
||||
|
||||
private void startTransformationInternal(MediaItem mediaItem) {
|
||||
verifyApplicationThread();
|
||||
if (player != null) {
|
||||
if (transformationInProgress) {
|
||||
throw new IllegalStateException("There is already a transformation in progress.");
|
||||
}
|
||||
TransformerPlayerListener playerListener = new TransformerPlayerListener(mediaItem, looper);
|
||||
transformationInProgress = true;
|
||||
ComponentListener componentListener = new ComponentListener(mediaItem, looper);
|
||||
MuxerWrapper muxerWrapper =
|
||||
new MuxerWrapper(
|
||||
outputPath,
|
||||
outputParcelFileDescriptor,
|
||||
muxerFactory,
|
||||
/* asyncErrorListener= */ playerListener);
|
||||
/* asyncErrorListener= */ componentListener);
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
|
||||
trackSelector.setParameters(
|
||||
new DefaultTrackSelector.ParametersBuilder(context)
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.build());
|
||||
// Arbitrarily decrease buffers for playback so that samples start being sent earlier to the
|
||||
// muxer (rebuffers are less problematic for the transformation use case).
|
||||
DefaultLoadControl loadControl =
|
||||
new DefaultLoadControl.Builder()
|
||||
.setBufferDurationsMs(
|
||||
DEFAULT_MIN_BUFFER_MS,
|
||||
DEFAULT_MAX_BUFFER_MS,
|
||||
DEFAULT_BUFFER_FOR_PLAYBACK_MS / 10,
|
||||
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10)
|
||||
.build();
|
||||
ExoPlayer.Builder playerBuilder =
|
||||
new ExoPlayer.Builder(
|
||||
context,
|
||||
new TransformerRenderersFactory(
|
||||
context,
|
||||
muxerWrapper,
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
transformationRequest,
|
||||
mediaItem.clippingConfiguration.startsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
new FallbackListener(mediaItem, listeners, transformationRequest),
|
||||
/* asyncErrorListener= */ playerListener,
|
||||
debugViewProvider))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
.setLooper(looper);
|
||||
if (clock != Clock.DEFAULT) {
|
||||
// Transformer.Builder#setClock is also @VisibleForTesting, so if we're using a non-default
|
||||
// clock we must be in a test context.
|
||||
@SuppressWarnings("VisibleForTests")
|
||||
ExoPlayer.Builder unusedForAnnotation = playerBuilder.setClock(clock);
|
||||
}
|
||||
|
||||
player = playerBuilder.build();
|
||||
player.setMediaItem(mediaItem);
|
||||
player.addListener(playerListener);
|
||||
player.prepare();
|
||||
|
||||
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||
FallbackListener fallbackListener =
|
||||
new FallbackListener(mediaItem, listeners, transformationRequest);
|
||||
exoPlayerAssetLoader.start(
|
||||
mediaItem,
|
||||
muxerWrapper,
|
||||
/* listener= */ componentListener,
|
||||
fallbackListener,
|
||||
/* asyncErrorListener= */ componentListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -806,13 +762,7 @@ public final class Transformer {
|
||||
*/
|
||||
public @ProgressState int getProgress(ProgressHolder progressHolder) {
|
||||
verifyApplicationThread();
|
||||
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;
|
||||
return exoPlayerAssetLoader.getProgress(progressHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -821,6 +771,7 @@ public final class Transformer {
|
||||
* @throws IllegalStateException If this method is called from the wrong thread.
|
||||
*/
|
||||
public void cancel() {
|
||||
verifyApplicationThread();
|
||||
isCancelling = true;
|
||||
try {
|
||||
releaseResources(/* forCancellation= */ true);
|
||||
@ -840,12 +791,8 @@ public final class Transformer {
|
||||
* is false.
|
||||
*/
|
||||
private void releaseResources(boolean forCancellation) throws TransformationException {
|
||||
verifyApplicationThread();
|
||||
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
|
||||
if (player != null) {
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
transformationInProgress = false;
|
||||
exoPlayerAssetLoader.release();
|
||||
if (muxerWrapper != null) {
|
||||
try {
|
||||
muxerWrapper.release(forCancellation);
|
||||
@ -883,149 +830,50 @@ public final class Transformer {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
private static final class TransformerRenderersFactory implements RenderersFactory {
|
||||
|
||||
private final Context context;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
private final TransformerMediaClock mediaClock;
|
||||
private final boolean removeAudio;
|
||||
private final boolean removeVideo;
|
||||
private final TransformationRequest transformationRequest;
|
||||
private final boolean clippingStartsAtKeyFrame;
|
||||
private final ImmutableList<Effect> videoEffects;
|
||||
private final FrameProcessor.Factory frameProcessorFactory;
|
||||
private final Codec.EncoderFactory encoderFactory;
|
||||
private final Codec.DecoderFactory decoderFactory;
|
||||
private final FallbackListener fallbackListener;
|
||||
private final AsyncErrorListener asyncErrorListener;
|
||||
private final DebugViewProvider debugViewProvider;
|
||||
|
||||
public TransformerRenderersFactory(
|
||||
Context context,
|
||||
MuxerWrapper muxerWrapper,
|
||||
boolean removeAudio,
|
||||
boolean removeVideo,
|
||||
TransformationRequest transformationRequest,
|
||||
boolean clippingStartsAtKeyFrame,
|
||||
ImmutableList<Effect> videoEffects,
|
||||
FrameProcessor.Factory frameProcessorFactory,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
Codec.DecoderFactory decoderFactory,
|
||||
FallbackListener fallbackListener,
|
||||
AsyncErrorListener asyncErrorListener,
|
||||
DebugViewProvider debugViewProvider) {
|
||||
this.context = context;
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
this.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.transformationRequest = transformationRequest;
|
||||
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
|
||||
this.videoEffects = videoEffects;
|
||||
this.frameProcessorFactory = frameProcessorFactory;
|
||||
this.encoderFactory = encoderFactory;
|
||||
this.decoderFactory = decoderFactory;
|
||||
this.fallbackListener = fallbackListener;
|
||||
this.asyncErrorListener = asyncErrorListener;
|
||||
this.debugViewProvider = debugViewProvider;
|
||||
mediaClock = new TransformerMediaClock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderer[] createRenderers(
|
||||
Handler eventHandler,
|
||||
VideoRendererEventListener videoRendererEventListener,
|
||||
AudioRendererEventListener audioRendererEventListener,
|
||||
TextOutput textRendererOutput,
|
||||
MetadataOutput metadataRendererOutput) {
|
||||
int rendererCount = removeAudio || removeVideo ? 1 : 2;
|
||||
Renderer[] renderers = new Renderer[rendererCount];
|
||||
int index = 0;
|
||||
if (!removeAudio) {
|
||||
renderers[index] =
|
||||
new TransformerAudioRenderer(
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener);
|
||||
index++;
|
||||
}
|
||||
if (!removeVideo) {
|
||||
renderers[index] =
|
||||
new TransformerVideoRenderer(
|
||||
context,
|
||||
muxerWrapper,
|
||||
mediaClock,
|
||||
transformationRequest,
|
||||
clippingStartsAtKeyFrame,
|
||||
videoEffects,
|
||||
frameProcessorFactory,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
asyncErrorListener,
|
||||
fallbackListener,
|
||||
debugViewProvider);
|
||||
index++;
|
||||
}
|
||||
return renderers;
|
||||
}
|
||||
/** Listener for exceptions that occur during a transformation. */
|
||||
/* package */ interface AsyncErrorListener {
|
||||
/**
|
||||
* Called when a {@link TransformationException} occurs.
|
||||
*
|
||||
* <p>Can be called from any thread.
|
||||
*/
|
||||
void onTransformationException(TransformationException exception);
|
||||
}
|
||||
|
||||
private final class TransformerPlayerListener implements Player.Listener, AsyncErrorListener {
|
||||
private final class ComponentListener
|
||||
implements ExoPlayerAssetLoader.Listener, AsyncErrorListener {
|
||||
|
||||
private final MediaItem mediaItem;
|
||||
private final Handler handler;
|
||||
|
||||
public TransformerPlayerListener(MediaItem mediaItem, Looper looper) {
|
||||
public ComponentListener(MediaItem mediaItem, Looper looper) {
|
||||
this.mediaItem = mediaItem;
|
||||
handler = new Handler(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(int state) {
|
||||
if (state == Player.STATE_ENDED) {
|
||||
handleTransformationEnded(/* exception= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||
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;
|
||||
checkNotNull(player).play();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
if (checkNotNull(muxerWrapper).getTrackCount() == 0) {
|
||||
handleTransformationEnded(
|
||||
TransformationException.createForUnexpected(
|
||||
new IllegalStateException("The output does not contain any tracks.")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
public void onError(Exception e) {
|
||||
TransformationException transformationException =
|
||||
TransformationException.createForPlaybackException(error);
|
||||
e instanceof PlaybackException
|
||||
? TransformationException.createForPlaybackException((PlaybackException) e)
|
||||
: TransformationException.createForUnexpected(e);
|
||||
handleTransformationException(transformationException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnded() {
|
||||
handleTransformationEnded(/* exception= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformationException(TransformationException exception) {
|
||||
if (Looper.myLooper() == looper) {
|
||||
handleTransformationException(exception);
|
||||
} else {
|
||||
handler.post(() -> handleTransformationException(exception));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTransformationException(TransformationException transformationException) {
|
||||
if (isCancelling) {
|
||||
// Resources are already being released.
|
||||
@ -1078,24 +926,5 @@ public final class Transformer {
|
||||
}
|
||||
listeners.flushEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformationException(TransformationException exception) {
|
||||
if (Looper.myLooper() == looper) {
|
||||
handleTransformationException(exception);
|
||||
} else {
|
||||
handler.post(() -> handleTransformationException(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for exceptions that occur during a transformation. */
|
||||
/* package */ interface AsyncErrorListener {
|
||||
/**
|
||||
* Called when a {@link TransformationException} occurs.
|
||||
*
|
||||
* <p>Can be called from any thread.
|
||||
*/
|
||||
void onTransformationException(TransformationException exception);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user