• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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