From b8ccd6197bdf2ace7657a1cfaec4fe5efd78eaf6 Mon Sep 17 00:00:00 2001 From: hschlueter Date: Mon, 13 Dec 2021 15:00:43 +0000 Subject: [PATCH] Add TransformationException with initial subset of error codes. TransformationException will be used for all errors that occur during a transformation. PiperOrigin-RevId: 416032504 --- .../transformer/TransformationException.java | 203 ++++++++++++++++++ .../media3/transformer/Transformer.java | 27 ++- .../media3/transformer/TransformerTest.java | 8 +- 3 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java new file mode 100644 index 0000000000..7b0122f792 --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java @@ -0,0 +1,203 @@ +/* + * Copyright 2021 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 java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + +import android.os.SystemClock; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import androidx.media3.common.util.Clock; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Thrown when a non-locally recoverable transformation failure occurs. */ +@UnstableApi +public final class TransformationException extends Exception { + + /** + * Creates an instance for an unexpected exception. + * + *

If the exception is a runtime exception, error code {@link ERROR_CODE_FAILED_RUNTIME_CHECK} + * is used. Otherwise, the created instance has error code {@link ERROR_CODE_UNSPECIFIED}. + * + * @param cause The cause of the failure. + * @return The created instance. + */ + public static TransformationException createForUnexpected(Exception cause) { + if (cause instanceof RuntimeException) { + return new TransformationException( + "Unexpected runtime error", cause, ERROR_CODE_FAILED_RUNTIME_CHECK); + } + return new TransformationException("Unexpected error", cause, ERROR_CODE_UNSPECIFIED); + } + + /** + * Codes that identify causes of {@link Transformer} errors. + * + *

This list of errors may be extended in future versions. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) + @IntDef( + open = true, + value = { + ERROR_CODE_UNSPECIFIED, + ERROR_CODE_FAILED_RUNTIME_CHECK, + ERROR_CODE_DECODER_INIT_FAILED, + ERROR_CODE_DECODING_FAILED, + ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, + ERROR_CODE_ENCODER_INIT_FAILED, + ERROR_CODE_ENCODING_FAILED, + ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED, + ERROR_CODE_GL_INIT_FAILED, + ERROR_CODE_GL_PROCESSING_FAILED, + }) + public @interface ErrorCode {} + + // Miscellaneous errors (1xxx). + + /** Caused by an error whose cause could not be identified. */ + public static final int ERROR_CODE_UNSPECIFIED = 1000; + /** + * Caused by a failed runtime check. + * + *

This can happen when transformer reaches an invalid state. + */ + public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1001; + + // Decoding errors (2xxx). + + /** Caused by a decoder initialization failure. */ + public static final int ERROR_CODE_DECODER_INIT_FAILED = 2001; + /** Caused by a failure while trying to decode media samples. */ + public static final int ERROR_CODE_DECODING_FAILED = 2002; + /** Caused by trying to decode content whose format is not supported. */ + public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 2003; + + // Encoding errors (3xxx). + + /** Caused by an encoder initialization failure. */ + public static final int ERROR_CODE_ENCODER_INIT_FAILED = 3001; + /** Caused by a failure while trying to encode media samples. */ + public static final int ERROR_CODE_ENCODING_FAILED = 3002; + /** Caused by requesting to encode content in a format that is not supported by the device. */ + public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 3003; + + // GL errors (4xxx). + + /** Caused by a GL initialization failure. */ + public static final int ERROR_CODE_GL_INIT_FAILED = 4001; + /** Caused by a failure while using or releasing a GL program. */ + public static final int ERROR_CODE_GL_PROCESSING_FAILED = 4002; + + /** Returns the name of a given {@code errorCode}. */ + public static String getErrorCodeName(@ErrorCode int errorCode) { + switch (errorCode) { + case ERROR_CODE_UNSPECIFIED: + return "ERROR_CODE_UNSPECIFIED"; + case ERROR_CODE_FAILED_RUNTIME_CHECK: + return "ERROR_CODE_FAILED_RUNTIME_CHECK"; + case ERROR_CODE_DECODER_INIT_FAILED: + return "ERROR_CODE_DECODER_INIT_FAILED"; + case ERROR_CODE_DECODING_FAILED: + return "ERROR_CODE_DECODING_FAILED"; + case ERROR_CODE_DECODING_FORMAT_UNSUPPORTED: + return "ERROR_CODE_DECODING_FORMAT_UNSUPPORTED"; + case ERROR_CODE_ENCODER_INIT_FAILED: + return "ERROR_CODE_ENCODER_INIT_FAILED"; + case ERROR_CODE_ENCODING_FAILED: + return "ERROR_CODE_ENCODING_FAILED"; + case ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED: + return "ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED"; + case ERROR_CODE_GL_INIT_FAILED: + return "ERROR_CODE_GL_INIT_FAILED"; + case ERROR_CODE_GL_PROCESSING_FAILED: + return "ERROR_CODE_GL_PROCESSING_FAILED"; + default: + return "invalid error code"; + } + } + + /** + * Equivalent to {@link TransformationException#getErrorCodeName(int) + * TransformationException.getErrorCodeName(this.errorCode)}. + */ + public final String getErrorCodeName() { + return getErrorCodeName(errorCode); + } + + /** An error code which identifies the cause of the transformation failure. */ + public final @ErrorCode int errorCode; + + /** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */ + public final long timestampMs; + + /** + * Creates an instance. + * + * @param message See {@link #getMessage()}. + * @param cause See {@link #getCause()}. + * @param errorCode A number which identifies the cause of the error. May be one of the {@link + * ErrorCode ErrorCodes}. + */ + public TransformationException( + @Nullable String message, @Nullable Throwable cause, @ErrorCode int errorCode) { + super(message, cause); + this.errorCode = errorCode; + this.timestampMs = Clock.DEFAULT.elapsedRealtime(); + } + + /** + * Returns whether the error data associated to this exception equals the error data associated to + * {@code other}. + * + *

Note that this method does not compare the exceptions' stacktraces. + */ + public boolean errorInfoEquals(@Nullable TransformationException other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + @Nullable Throwable thisCause = getCause(); + @Nullable Throwable thatCause = other.getCause(); + if (thisCause != null && thatCause != null) { + if (!Util.areEqual(thisCause.getMessage(), thatCause.getMessage())) { + return false; + } + if (!Util.areEqual(thisCause.getClass(), thatCause.getClass())) { + return false; + } + } else if (thisCause != null || thatCause != null) { + return false; + } + return errorCode == other.errorCode + && Util.areEqual(getMessage(), other.getMessage()) + && timestampMs == other.timestampMs; + } +} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index a65a39beee..3324b6eac5 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -834,22 +834,37 @@ public final class Transformer { @Override public void onPlayerError(PlaybackException error) { + // TODO(internal b/209469847): Once TransformationException is used in transformer components, + // extract TransformationExceptions wrapped in the PlaybackExceptions here before passing them + // on. handleTransformationEnded(error); } private void handleTransformationEnded(@Nullable Exception exception) { + @Nullable Exception resourceReleaseException = null; try { releaseResources(/* forCancellation= */ false); } catch (IllegalStateException e) { - if (exception == null) { - exception = e; - } + // TODO(internal b/209469847): Use a TransformationException with a specific error code when + // the IllegalStateException is caused by the muxer. + resourceReleaseException = e; } - if (exception == null) { + if (exception == null && resourceReleaseException == null) { listener.onTransformationCompleted(mediaItem); - } else { - listener.onTransformationError(mediaItem, exception); + return; + } + + if (exception != null) { + listener.onTransformationError( + mediaItem, + exception instanceof TransformationException + ? exception + : TransformationException.createForUnexpected(exception)); + } + if (resourceReleaseException != null) { + listener.onTransformationError( + mediaItem, TransformationException.createForUnexpected(resourceReleaseException)); } } } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java index 70cd7ad41d..1baa87e527 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java @@ -240,8 +240,9 @@ public final class TransformerTest { transformer.startTransformation(mediaItem, outputPath); Exception exception = TransformerTestRunner.runUntilError(transformer); - assertThat(exception).isInstanceOf(ExoPlaybackException.class); - assertThat(exception).hasCauseThat().isInstanceOf(IOException.class); + assertThat(exception).isInstanceOf(TransformationException.class); + assertThat(exception).hasCauseThat().isInstanceOf(ExoPlaybackException.class); + assertThat(exception).hasCauseThat().hasCauseThat().isInstanceOf(IOException.class); } @Test @@ -253,7 +254,8 @@ public final class TransformerTest { transformer.startTransformation(mediaItem, outputPath); Exception exception = TransformerTestRunner.runUntilError(transformer); - assertThat(exception).isInstanceOf(IllegalStateException.class); + assertThat(exception).isInstanceOf(TransformationException.class); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalStateException.class); } @Test