Add async error listener to transformer to avoid exception wrapping.

This internal listener avoids wrapping the TransformationExceptions
in PlaybackExceptions that are handled via the Player.Listener and
is also used for FrameProcessingExceptions which already avoided
the PlaybackException layer previously.

This listener will also be useful in follow-ups for encoder-related
TransformationExceptions that are thrown in the SurfaceProvider that
will be called on the GL thread.

PiperOrigin-RevId: 452074575
(cherry picked from commit 35b5147eb1404698132d9b063c905202c22e31d8)
This commit is contained in:
hschlueter 2022-05-31 17:01:02 +00:00 committed by microkatz
parent 22ee071b53
commit 27f23041cf
4 changed files with 62 additions and 53 deletions

View File

@ -763,7 +763,7 @@ public final class Transformer {
encoderFactory,
decoderFactory,
new FallbackListener(mediaItem, listeners, transformationRequest),
playerListener,
/* asyncErrorListener= */ playerListener,
debugViewProvider))
.setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector)
@ -877,7 +877,7 @@ public final class Transformer {
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final FallbackListener fallbackListener;
private final FrameProcessorChain.Listener frameProcessorChainListener;
private final AsyncErrorListener asyncErrorListener;
private final Transformer.DebugViewProvider debugViewProvider;
public TransformerRenderersFactory(
@ -891,7 +891,7 @@ public final class Transformer {
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
FallbackListener fallbackListener,
FrameProcessorChain.Listener frameProcessorChainListener,
AsyncErrorListener asyncErrorListener,
Transformer.DebugViewProvider debugViewProvider) {
this.context = context;
this.muxerWrapper = muxerWrapper;
@ -903,7 +903,7 @@ public final class Transformer {
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.fallbackListener = fallbackListener;
this.frameProcessorChainListener = frameProcessorChainListener;
this.asyncErrorListener = asyncErrorListener;
this.debugViewProvider = debugViewProvider;
mediaClock = new TransformerMediaClock();
}
@ -926,6 +926,7 @@ public final class Transformer {
transformationRequest,
encoderFactory,
decoderFactory,
asyncErrorListener,
fallbackListener);
index++;
}
@ -940,8 +941,8 @@ public final class Transformer {
videoFrameEffects,
encoderFactory,
decoderFactory,
asyncErrorListener,
fallbackListener,
frameProcessorChainListener,
debugViewProvider);
index++;
}
@ -949,8 +950,7 @@ public final class Transformer {
}
}
private final class TransformerPlayerListener
implements Player.Listener, FrameProcessorChain.Listener {
private final class TransformerPlayerListener implements Player.Listener, AsyncErrorListener {
private final MediaItem mediaItem;
private final MuxerWrapper muxerWrapper;
@ -1001,11 +1001,12 @@ public final class Transformer {
@Override
public void onPlayerError(PlaybackException error) {
@Nullable Throwable cause = error.getCause();
TransformationException transformationException =
cause instanceof TransformationException
? (TransformationException) cause
: TransformationException.createForPlaybackException(error);
TransformationException.createForPlaybackException(error);
handleTransformationException(transformationException);
}
private void handleTransformationException(TransformationException transformationException) {
if (isCancelling) {
// Resources are already being released.
listeners.queueEvent(
@ -1057,12 +1058,22 @@ public final class Transformer {
}
@Override
public void onFrameProcessingError(FrameProcessingException exception) {
handler.post(
() ->
handleTransformationEnded(
TransformationException.createForFrameProcessorChain(
exception, TransformationException.ERROR_CODE_GL_PROCESSING_FAILED)));
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);
}
}

View File

@ -42,8 +42,15 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData;
TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener) {
super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformationRequest, fallbackListener);
super(
C.TRACK_TYPE_AUDIO,
muxerWrapper,
mediaClock,
transformationRequest,
asyncErrorListener,
fallbackListener);
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
decoderInputBuffer =

View File

@ -22,10 +22,8 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.BaseRenderer;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.MediaClock;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
@ -39,11 +37,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
protected final MuxerWrapper muxerWrapper;
protected final TransformerMediaClock mediaClock;
protected final TransformationRequest transformationRequest;
protected final Transformer.AsyncErrorListener asyncErrorListener;
protected final FallbackListener fallbackListener;
protected boolean isRendererStarted;
protected boolean muxerWrapperTrackAdded;
protected boolean muxerWrapperTrackEnded;
private boolean isTransformationRunning;
private boolean muxerWrapperTrackAdded;
private boolean muxerWrapperTrackEnded;
protected long streamOffsetUs;
protected long streamStartPositionUs;
protected @MonotonicNonNull SamplePipeline samplePipeline;
@ -53,11 +52,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
TransformationRequest transformationRequest,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener) {
super(trackType);
this.muxerWrapper = muxerWrapper;
this.mediaClock = mediaClock;
this.transformationRequest = transformationRequest;
this.asyncErrorListener = asyncErrorListener;
this.fallbackListener = fallbackListener;
}
@ -91,17 +92,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
@Override
public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
public final void render(long positionUs, long elapsedRealtimeUs) {
try {
if (!isRendererStarted || isEnded() || !ensureConfigured()) {
if (!isTransformationRunning || isEnded() || !ensureConfigured()) {
return;
}
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
} catch (TransformationException e) {
throw wrapTransformationException(e);
isTransformationRunning = false;
asyncErrorListener.onTransformationException(e);
} catch (Muxer.MuxerException e) {
throw wrapTransformationException(
isTransformationRunning = false;
asyncErrorListener.onTransformationException(
TransformationException.createForMuxer(
e, TransformationException.ERROR_CODE_MUXING_FAILED));
}
@ -122,12 +125,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
protected final void onStarted() {
isRendererStarted = true;
isTransformationRunning = true;
}
@Override
protected final void onStopped() {
isRendererStarted = false;
isTransformationRunning = false;
}
@Override
@ -225,23 +228,4 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return false;
}
}
/**
* Returns an {@link ExoPlaybackException} wrapping the {@link TransformationException}.
*
* <p>This temporary wrapping is needed due to the dependence on ExoPlayer's BaseRenderer. {@link
* Transformer} extracts the {@link TransformationException} from this {@link
* ExoPlaybackException} again.
*/
private ExoPlaybackException wrapTransformationException(
TransformationException transformationException) {
return ExoPlaybackException.createForRenderer(
transformationException,
"Transformer",
getIndex(),
/* rendererFormat= */ null,
C.FORMAT_HANDLED,
/* isRecoverable= */ false,
PlaybackException.ERROR_CODE_UNSPECIFIED);
}
}

View File

@ -39,7 +39,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final ImmutableList<GlEffect> effects;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final FrameProcessorChain.Listener frameProcessorChainListener;
private final Transformer.DebugViewProvider debugViewProvider;
private final DecoderInputBuffer decoderInputBuffer;
@ -54,16 +53,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
ImmutableList<GlEffect> effects,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener,
FrameProcessorChain.Listener frameProcessorChainListener,
Transformer.DebugViewProvider debugViewProvider) {
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformationRequest, fallbackListener);
super(
C.TRACK_TYPE_VIDEO,
muxerWrapper,
mediaClock,
transformationRequest,
asyncErrorListener,
fallbackListener);
this.context = context;
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
this.effects = effects;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.frameProcessorChainListener = frameProcessorChainListener;
this.debugViewProvider = debugViewProvider;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
@ -102,7 +106,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
encoderFactory,
muxerWrapper.getSupportedSampleMimeTypes(getTrackType()),
fallbackListener,
frameProcessorChainListener,
/* frameProcessorChainListener= */ exception ->
asyncErrorListener.onTransformationException(
TransformationException.createForFrameProcessorChain(
exception, TransformationException.ERROR_CODE_GL_PROCESSING_FAILED)),
debugViewProvider);
}
if (transformationRequest.flattenForSlowMotion) {