diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java index 03909481fb..524802aa02 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java @@ -25,10 +25,12 @@ import android.os.SystemClock; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableBiMap; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,6 +39,167 @@ import java.lang.annotation.Target; /** Thrown when a non-locally recoverable transformation failure occurs. */ public final class TransformationException extends Exception { + /** + * Codes that identify causes of {@link Transformer} errors. + * + *

This list of errors may be extended in future versions. The underlying values may also + * change, so it is best to avoid relying on them directly without using the constants. + */ + // TODO(b/209469847): Update the javadoc once the underlying values are fixed. + @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_IO_UNSPECIFIED, + ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, + ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, + ERROR_CODE_IO_BAD_HTTP_STATUS, + ERROR_CODE_IO_FILE_NOT_FOUND, + ERROR_CODE_IO_NO_PERMISSION, + ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, + ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, + 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, + ERROR_CODE_AUDIO_PROCESSOR_INIT_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; + + // Input/Output errors (2xxx). + + /** Caused by an Input/Output error which could not be identified. */ + public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; + /** + * Caused by a network connection failure. + * + *

The following is a non-exhaustive list of possible reasons: + * + *

+ */ + public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2001; + /** Caused by a network timeout, meaning the server is taking too long to fulfill a request. */ + public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2002; + /** + * Caused by a server returning a resource with an invalid "Content-Type" HTTP header value. + * + *

For example, this can happen when the player is expecting a piece of media, but the server + * returns a paywall HTML page, with content type "text/html". + */ + public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2003; + /** Caused by an HTTP server returning an unexpected HTTP response status code. */ + public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; + /** Caused by a non-existent file. */ + public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; + /** + * Caused by lack of permission to perform an IO operation. For example, lack of permission to + * access internet or external storage. + */ + public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; + /** + * Caused by the player trying to access cleartext HTTP traffic (meaning http:// rather than + * https://) when the app's Network Security Configuration does not permit it. + */ + public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; + /** Caused by reading data out of the data bound. */ + public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008; + + // Decoding errors (3xxx). + + /** Caused by a decoder initialization failure. */ + public static final int ERROR_CODE_DECODER_INIT_FAILED = 3001; + /** Caused by a failure while trying to decode media samples. */ + public static final int ERROR_CODE_DECODING_FAILED = 3002; + /** Caused by trying to decode content whose format is not supported. */ + public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 3003; + + // Encoding errors (4xxx). + + /** Caused by an encoder initialization failure. */ + public static final int ERROR_CODE_ENCODER_INIT_FAILED = 4001; + /** Caused by a failure while trying to encode media samples. */ + public static final int ERROR_CODE_ENCODING_FAILED = 4002; + /** 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 = 4003; + + // Video editing errors (5xxx). + + /** Caused by a GL initialization failure. */ + public static final int ERROR_CODE_GL_INIT_FAILED = 5001; + /** Caused by a failure while using or releasing a GL program. */ + public static final int ERROR_CODE_GL_PROCESSING_FAILED = 5002; + + // Audio editing errors (6xxx). + + /** Caused by an audio processor initialization failure. */ + public static final int ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED = 6001; + + private static final ImmutableBiMap NAME_TO_ERROR_CODE = + new ImmutableBiMap.Builder() + .put("ERROR_CODE_FAILED_RUNTIME_CHECK", ERROR_CODE_FAILED_RUNTIME_CHECK) + .put("ERROR_CODE_IO_UNSPECIFIED", ERROR_CODE_IO_UNSPECIFIED) + .put("ERROR_CODE_IO_NETWORK_CONNECTION_FAILED", ERROR_CODE_IO_NETWORK_CONNECTION_FAILED) + .put("ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT", ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT) + .put("ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE", ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE) + .put("ERROR_CODE_IO_BAD_HTTP_STATUS", ERROR_CODE_IO_BAD_HTTP_STATUS) + .put("ERROR_CODE_IO_FILE_NOT_FOUND", ERROR_CODE_IO_FILE_NOT_FOUND) + .put("ERROR_CODE_IO_NO_PERMISSION", ERROR_CODE_IO_NO_PERMISSION) + .put("ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED", ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED) + .put("ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE", ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) + .put("ERROR_CODE_DECODER_INIT_FAILED", ERROR_CODE_DECODER_INIT_FAILED) + .put("ERROR_CODE_DECODING_FAILED", ERROR_CODE_DECODING_FAILED) + .put("ERROR_CODE_DECODING_FORMAT_UNSUPPORTED", ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) + .put("ERROR_CODE_ENCODER_INIT_FAILED", ERROR_CODE_ENCODER_INIT_FAILED) + .put("ERROR_CODE_ENCODING_FAILED", ERROR_CODE_ENCODING_FAILED) + .put("ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED", ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED) + .put("ERROR_CODE_GL_INIT_FAILED", ERROR_CODE_GL_INIT_FAILED) + .put("ERROR_CODE_GL_PROCESSING_FAILED", ERROR_CODE_GL_PROCESSING_FAILED) + .put("ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED", ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED) + .buildOrThrow(); + + /** Returns the {@code errorCode} for a given name. */ + private static @ErrorCode int getErrorCodeForName(String errorCodeName) { + return NAME_TO_ERROR_CODE.getOrDefault(errorCodeName, ERROR_CODE_UNSPECIFIED); + } + + /** Returns the name of a given {@code errorCode}. */ + public static String getErrorCodeName(@ErrorCode int errorCode) { + return NAME_TO_ERROR_CODE.inverse().getOrDefault(errorCode, "invalid error code"); + } + + /** + * Equivalent to {@link TransformationException#getErrorCodeName(int) + * TransformationException.getErrorCodeName(this.errorCode)}. + */ + public final String getErrorCodeName() { + return getErrorCodeName(errorCode); + } + /** * Creates an instance for a decoder or encoder related exception. * @@ -70,8 +233,8 @@ 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}. + *

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. @@ -85,107 +248,17 @@ public final class TransformationException extends Exception { } /** - * Codes that identify causes of {@link Transformer} errors. + * Converts a {@link PlaybackException} to a {@code TransformationException}. * - *

This list of errors may be extended in future versions. + *

If no corresponding error code exists, the created instance will have {@link + * #ERROR_CODE_UNSPECIFIED}. */ - @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, - ERROR_CODE_AUDIO_PROCESSOR_INIT_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; - - // Video editing 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; - - // Audio editing errors (5xxx). - - /** Caused by an audio processor initialization failure. */ - public static final int ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED = 5001; - - /** 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"; - case ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED: - return "ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED"; - default: - return "invalid error code"; - } - } - - /** - * Equivalent to {@link TransformationException#getErrorCodeName(int) - * TransformationException.getErrorCodeName(this.errorCode)}. - */ - public final String getErrorCodeName() { - return getErrorCodeName(errorCode); + /* package */ static TransformationException createForPlaybackException( + PlaybackException exception) { + return new TransformationException( + exception.getMessage(), + exception.getCause(), + getErrorCodeForName(exception.getErrorCodeName())); } /** An error code which identifies the cause of the transformation failure. */ diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index b1688ee62b..b30299a359 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -845,7 +845,7 @@ public final class Transformer { handleTransformationEnded( cause instanceof TransformationException ? (TransformationException) cause - : TransformationException.createForUnexpected(error)); + : TransformationException.createForPlaybackException(error)); } private void handleTransformationEnded(@Nullable TransformationException exception) { diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java index 6bb8925bba..a72266330c 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerTest.java @@ -35,7 +35,6 @@ import android.view.Surface; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.FakeClock; @@ -295,15 +294,14 @@ public final class TransformerTest { } @Test - public void startTransformation_withPlayerError_completesWithError() throws Exception { + public void startTransformation_withIoError_completesWithError() throws Exception { Transformer transformer = new Transformer.Builder(context).setClock(clock).build(); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); transformer.startTransformation(mediaItem, outputPath); TransformationException exception = TransformerTestRunner.runUntilError(transformer); - assertThat(exception).hasCauseThat().isInstanceOf(ExoPlaybackException.class); - assertThat(exception).hasCauseThat().hasCauseThat().isInstanceOf(IOException.class); + assertThat(exception).hasCauseThat().isInstanceOf(IOException.class); } @Test