1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.transformer; 17 18 import static java.lang.annotation.ElementType.TYPE_USE; 19 20 import android.media.MediaCodec; 21 import android.media.MediaFormat; 22 import android.os.SystemClock; 23 import androidx.annotation.IntDef; 24 import androidx.annotation.Nullable; 25 import com.google.android.exoplayer2.Format; 26 import com.google.android.exoplayer2.PlaybackException; 27 import com.google.android.exoplayer2.audio.AudioProcessor; 28 import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat; 29 import com.google.android.exoplayer2.util.Clock; 30 import com.google.android.exoplayer2.util.Util; 31 import com.google.common.collect.ImmutableBiMap; 32 import java.lang.annotation.Documented; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.lang.annotation.Target; 36 37 /** Thrown when a non-locally recoverable transformation failure occurs. */ 38 public final class TransformationException extends Exception { 39 40 /** 41 * Codes that identify causes of {@link Transformer} errors. 42 * 43 * <p>This list of errors may be extended in future versions. The underlying values may also 44 * change, so it is best to avoid relying on them directly without using the constants. 45 */ 46 // TODO(b/209469847): Update the javadoc once the underlying values are fixed. 47 @Documented 48 @Retention(RetentionPolicy.SOURCE) 49 @Target(TYPE_USE) 50 @IntDef( 51 open = true, 52 value = { 53 ERROR_CODE_UNSPECIFIED, 54 ERROR_CODE_FAILED_RUNTIME_CHECK, 55 ERROR_CODE_IO_UNSPECIFIED, 56 ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, 57 ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, 58 ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, 59 ERROR_CODE_IO_BAD_HTTP_STATUS, 60 ERROR_CODE_IO_FILE_NOT_FOUND, 61 ERROR_CODE_IO_NO_PERMISSION, 62 ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, 63 ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, 64 ERROR_CODE_DECODER_INIT_FAILED, 65 ERROR_CODE_DECODING_FAILED, 66 ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, 67 ERROR_CODE_ENCODER_INIT_FAILED, 68 ERROR_CODE_ENCODING_FAILED, 69 ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED, 70 ERROR_CODE_GL_INIT_FAILED, 71 ERROR_CODE_GL_PROCESSING_FAILED, 72 ERROR_CODE_MUXING_FAILED, 73 }) 74 public @interface ErrorCode {} 75 76 // Miscellaneous errors (1xxx). 77 78 /** Caused by an error whose cause could not be identified. */ 79 public static final int ERROR_CODE_UNSPECIFIED = 1000; 80 /** 81 * Caused by a failed runtime check. 82 * 83 * <p>This can happen when transformer reaches an invalid state. 84 */ 85 public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1001; 86 87 // Input/Output errors (2xxx). 88 89 /** Caused by an Input/Output error which could not be identified. */ 90 public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; 91 /** 92 * Caused by a network connection failure. 93 * 94 * <p>The following is a non-exhaustive list of possible reasons: 95 * 96 * <ul> 97 * <li>There is no network connectivity. 98 * <li>The URL's domain is misspelled or does not exist. 99 * <li>The target host is unreachable. 100 * <li>The server unexpectedly closes the connection. 101 * </ul> 102 */ 103 public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2001; 104 /** Caused by a network timeout, meaning the server is taking too long to fulfill a request. */ 105 public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2002; 106 /** 107 * Caused by a server returning a resource with an invalid "Content-Type" HTTP header value. 108 * 109 * <p>For example, this can happen when the player is expecting a piece of media, but the server 110 * returns a paywall HTML page, with content type "text/html". 111 */ 112 public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2003; 113 /** Caused by an HTTP server returning an unexpected HTTP response status code. */ 114 public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; 115 /** Caused by a non-existent file. */ 116 public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; 117 /** 118 * Caused by lack of permission to perform an IO operation. For example, lack of permission to 119 * access internet or external storage. 120 */ 121 public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; 122 /** 123 * Caused by the player trying to access cleartext HTTP traffic (meaning http:// rather than 124 * https://) when the app's Network Security Configuration does not permit it. 125 */ 126 public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; 127 /** Caused by reading data out of the data bound. */ 128 public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008; 129 130 // Decoding errors (3xxx). 131 132 /** Caused by a decoder initialization failure. */ 133 public static final int ERROR_CODE_DECODER_INIT_FAILED = 3001; 134 /** Caused by a failure while trying to decode media samples. */ 135 public static final int ERROR_CODE_DECODING_FAILED = 3002; 136 /** Caused by trying to decode content whose format is not supported. */ 137 public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 3003; 138 139 // Encoding errors (4xxx). 140 141 /** Caused by an encoder initialization failure. */ 142 public static final int ERROR_CODE_ENCODER_INIT_FAILED = 4001; 143 /** Caused by a failure while trying to encode media samples. */ 144 public static final int ERROR_CODE_ENCODING_FAILED = 4002; 145 /** 146 * Caused by the output format for a track not being supported. 147 * 148 * <p>Supported output formats are limited by the muxer's capabilities and the {@linkplain 149 * Codec.DecoderFactory encoders} available. 150 */ 151 public static final int ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED = 4003; 152 153 // Video editing errors (5xxx). 154 155 /** Caused by a GL initialization failure. */ 156 public static final int ERROR_CODE_GL_INIT_FAILED = 5001; 157 /** Caused by a failure while using or releasing a GL program. */ 158 public static final int ERROR_CODE_GL_PROCESSING_FAILED = 5002; 159 160 // Muxing errors (6xxx). 161 /** Caused by a failure while muxing media samples. */ 162 public static final int ERROR_CODE_MUXING_FAILED = 6001; 163 164 private static final ImmutableBiMap<String, @ErrorCode Integer> NAME_TO_ERROR_CODE = 165 new ImmutableBiMap.Builder<String, @ErrorCode Integer>() 166 .put("ERROR_CODE_FAILED_RUNTIME_CHECK", ERROR_CODE_FAILED_RUNTIME_CHECK) 167 .put("ERROR_CODE_IO_UNSPECIFIED", ERROR_CODE_IO_UNSPECIFIED) 168 .put("ERROR_CODE_IO_NETWORK_CONNECTION_FAILED", ERROR_CODE_IO_NETWORK_CONNECTION_FAILED) 169 .put("ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT", ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT) 170 .put("ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE", ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE) 171 .put("ERROR_CODE_IO_BAD_HTTP_STATUS", ERROR_CODE_IO_BAD_HTTP_STATUS) 172 .put("ERROR_CODE_IO_FILE_NOT_FOUND", ERROR_CODE_IO_FILE_NOT_FOUND) 173 .put("ERROR_CODE_IO_NO_PERMISSION", ERROR_CODE_IO_NO_PERMISSION) 174 .put("ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED", ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED) 175 .put("ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE", ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) 176 .put("ERROR_CODE_DECODER_INIT_FAILED", ERROR_CODE_DECODER_INIT_FAILED) 177 .put("ERROR_CODE_DECODING_FAILED", ERROR_CODE_DECODING_FAILED) 178 .put("ERROR_CODE_DECODING_FORMAT_UNSUPPORTED", ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) 179 .put("ERROR_CODE_ENCODER_INIT_FAILED", ERROR_CODE_ENCODER_INIT_FAILED) 180 .put("ERROR_CODE_ENCODING_FAILED", ERROR_CODE_ENCODING_FAILED) 181 .put("ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED", ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED) 182 .put("ERROR_CODE_GL_INIT_FAILED", ERROR_CODE_GL_INIT_FAILED) 183 .put("ERROR_CODE_GL_PROCESSING_FAILED", ERROR_CODE_GL_PROCESSING_FAILED) 184 .put("ERROR_CODE_MUXING_FAILED", ERROR_CODE_MUXING_FAILED) 185 .buildOrThrow(); 186 187 /** Returns the {@code errorCode} for a given name. */ getErrorCodeForName(String errorCodeName)188 private static @ErrorCode int getErrorCodeForName(String errorCodeName) { 189 return NAME_TO_ERROR_CODE.getOrDefault(errorCodeName, ERROR_CODE_UNSPECIFIED); 190 } 191 192 /** Returns the name of a given {@code errorCode}. */ getErrorCodeName(@rrorCode int errorCode)193 public static String getErrorCodeName(@ErrorCode int errorCode) { 194 return NAME_TO_ERROR_CODE.inverse().getOrDefault(errorCode, "invalid error code"); 195 } 196 197 /** 198 * Equivalent to {@link TransformationException#getErrorCodeName(int) 199 * TransformationException.getErrorCodeName(this.errorCode)}. 200 */ getErrorCodeName()201 public final String getErrorCodeName() { 202 return getErrorCodeName(errorCode); 203 } 204 205 /** 206 * Creates an instance for a decoder or encoder related exception. 207 * 208 * <p>Use this method after the {@link MediaFormat} used to configure the {@link Codec} is known. 209 * 210 * @param cause The cause of the failure. 211 * @param isVideo Whether the decoder or encoder is configured for video. 212 * @param isDecoder Whether the exception is created for a decoder. 213 * @param mediaFormat The {@link MediaFormat} used for configuring the underlying {@link 214 * MediaCodec}. 215 * @param mediaCodecName The name of the {@link MediaCodec} used, if known. 216 * @param errorCode See {@link #errorCode}. 217 * @return The created instance. 218 */ createForCodec( Throwable cause, boolean isVideo, boolean isDecoder, MediaFormat mediaFormat, @Nullable String mediaCodecName, int errorCode)219 public static TransformationException createForCodec( 220 Throwable cause, 221 boolean isVideo, 222 boolean isDecoder, 223 MediaFormat mediaFormat, 224 @Nullable String mediaCodecName, 225 int errorCode) { 226 String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder"); 227 String errorMessage = 228 componentName + ", mediaFormat=" + mediaFormat + ", mediaCodecName=" + mediaCodecName; 229 return new TransformationException(errorMessage, cause, errorCode); 230 } 231 232 /** 233 * Creates an instance for a decoder or encoder related exception. 234 * 235 * <p>Use this method before configuring the {@link Codec}, or when the {@link Codec} is not 236 * configured with a {@link MediaFormat}. 237 * 238 * @param cause The cause of the failure. 239 * @param isVideo Whether the decoder or encoder is configured for video. 240 * @param isDecoder Whether the exception is created for a decoder. 241 * @param format The {@link Format} used for configuring the {@link Codec}. 242 * @param mediaCodecName The name of the {@link MediaCodec} used, if known. 243 * @param errorCode See {@link #errorCode}. 244 * @return The created instance. 245 */ createForCodec( Throwable cause, boolean isVideo, boolean isDecoder, Format format, @Nullable String mediaCodecName, int errorCode)246 public static TransformationException createForCodec( 247 Throwable cause, 248 boolean isVideo, 249 boolean isDecoder, 250 Format format, 251 @Nullable String mediaCodecName, 252 int errorCode) { 253 String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder"); 254 String errorMessage = 255 componentName + " error, format=" + format + ", mediaCodecName=" + mediaCodecName; 256 return new TransformationException(errorMessage, cause, errorCode); 257 } 258 259 /** 260 * Creates an instance for an {@link AudioProcessor} related exception. 261 * 262 * @param cause The cause of the failure. 263 * @param componentName The name of the {@link AudioProcessor} used. 264 * @param audioFormat The {@link AudioFormat} used. 265 * @param errorCode See {@link #errorCode}. 266 * @return The created instance. 267 */ createForAudioProcessor( Throwable cause, String componentName, AudioFormat audioFormat, int errorCode)268 public static TransformationException createForAudioProcessor( 269 Throwable cause, String componentName, AudioFormat audioFormat, int errorCode) { 270 return new TransformationException( 271 componentName + " error, audio_format = " + audioFormat, cause, errorCode); 272 } 273 274 /** 275 * Creates an instance for a {@link FrameProcessorChain} related exception. 276 * 277 * @param cause The cause of the failure. 278 * @param errorCode See {@link #errorCode}. 279 * @return The created instance. 280 */ createForFrameProcessorChain( Throwable cause, int errorCode)281 /* package */ static TransformationException createForFrameProcessorChain( 282 Throwable cause, int errorCode) { 283 return new TransformationException("FrameProcessorChain error", cause, errorCode); 284 } 285 286 /** 287 * Creates an instance for a muxer related exception. 288 * 289 * @param cause The cause of the failure. 290 * @param errorCode See {@link #errorCode}. 291 * @return The created instance. 292 */ createForMuxer(Throwable cause, int errorCode)293 /* package */ static TransformationException createForMuxer(Throwable cause, int errorCode) { 294 return new TransformationException("Muxer error", cause, errorCode); 295 } 296 297 /** 298 * Creates an instance for an unexpected exception. 299 * 300 * <p>If the exception is a runtime exception, error code {@link #ERROR_CODE_FAILED_RUNTIME_CHECK} 301 * is used. Otherwise, the created instance has error code {@link #ERROR_CODE_UNSPECIFIED}. 302 * 303 * @param cause The cause of the failure. 304 * @return The created instance. 305 */ createForUnexpected(Exception cause)306 public static TransformationException createForUnexpected(Exception cause) { 307 if (cause instanceof RuntimeException) { 308 return new TransformationException( 309 "Unexpected runtime error", cause, ERROR_CODE_FAILED_RUNTIME_CHECK); 310 } 311 return new TransformationException("Unexpected error", cause, ERROR_CODE_UNSPECIFIED); 312 } 313 314 /** 315 * Creates an instance for a {@link PlaybackException}. 316 * 317 * <p>If there is a corresponding {@link TransformationException.ErrorCode} for the {@link 318 * PlaybackException.ErrorCode}, this error code and the same message are used for the created 319 * instance. Otherwise, this is equivalent to {@link #createForUnexpected(Exception)}. 320 */ createForPlaybackException( PlaybackException exception)321 /* package */ static TransformationException createForPlaybackException( 322 PlaybackException exception) { 323 @ErrorCode int errorCode = getErrorCodeForName(exception.getErrorCodeName()); 324 return errorCode == ERROR_CODE_UNSPECIFIED 325 ? createForUnexpected(exception) 326 : new TransformationException(exception.getMessage(), exception, errorCode); 327 } 328 329 /** An error code which identifies the cause of the transformation failure. */ 330 public final @ErrorCode int errorCode; 331 332 /** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */ 333 public final long timestampMs; 334 335 /** 336 * Creates an instance. 337 * 338 * @param message See {@link #getMessage()}. 339 * @param cause See {@link #getCause()}. 340 * @param errorCode A number which identifies the cause of the error. May be one of the {@link 341 * ErrorCode ErrorCodes}. 342 */ TransformationException( @ullable String message, @Nullable Throwable cause, @ErrorCode int errorCode)343 private TransformationException( 344 @Nullable String message, @Nullable Throwable cause, @ErrorCode int errorCode) { 345 super(message, cause); 346 this.errorCode = errorCode; 347 this.timestampMs = Clock.DEFAULT.elapsedRealtime(); 348 } 349 350 /** 351 * Returns whether the error data associated to this exception equals the error data associated to 352 * {@code other}. 353 * 354 * <p>Note that this method does not compare the exceptions' stack traces. 355 */ errorInfoEquals(@ullable TransformationException other)356 public boolean errorInfoEquals(@Nullable TransformationException other) { 357 if (this == other) { 358 return true; 359 } 360 if (other == null || getClass() != other.getClass()) { 361 return false; 362 } 363 364 @Nullable Throwable thisCause = getCause(); 365 @Nullable Throwable thatCause = other.getCause(); 366 if (thisCause != null && thatCause != null) { 367 if (!Util.areEqual(thisCause.getMessage(), thatCause.getMessage())) { 368 return false; 369 } 370 if (!Util.areEqual(thisCause.getClass(), thatCause.getClass())) { 371 return false; 372 } 373 } else if (thisCause != null || thatCause != null) { 374 return false; 375 } 376 return errorCode == other.errorCode 377 && Util.areEqual(getMessage(), other.getMessage()) 378 && timestampMs == other.timestampMs; 379 } 380 } 381