public final class

ExoPlaybackException

extends PlaybackException

 java.lang.Object

↳java.lang.Throwable

↳java.lang.Exception

androidx.media3.common.PlaybackException

↳androidx.media3.exoplayer.ExoPlaybackException

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-exoplayer:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Thrown when a non locally recoverable playback failure occurs.

Summary

Fields
public final MediaSource.MediaPeriodIdmediaPeriodId

The MediaSource.MediaPeriodId of the media associated with this error, or null if undetermined.

public final FormatrendererFormat

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the Format the renderer was using at the time of the exception, or null if the renderer wasn't using a Format.

public final intrendererFormatSupport

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the level of C.FormatSupport of the renderer for ExoPlaybackException.rendererFormat.

public final intrendererIndex

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the index of the renderer.

public final java.lang.StringrendererName

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the name of the renderer.

public final inttype

The ExoPlaybackException.Type of the playback failure.

public static final intTYPE_REMOTE

The error occurred in a remote component.

public static final intTYPE_RENDERER

The error occurred in a Renderer.

public static final intTYPE_SOURCE

The error occurred loading data from a MediaSource.

public static final intTYPE_UNEXPECTED

The error was an unexpected java.lang.RuntimeException.

from PlaybackExceptionCUSTOM_ERROR_CODE_BASE, ERROR_CODE_AUDIO_TRACK_INIT_FAILED, ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_BAD_VALUE, ERROR_CODE_BEHIND_LIVE_WINDOW, ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_CONTENT_ALREADY_PLAYING, ERROR_CODE_DECODER_INIT_FAILED, ERROR_CODE_DECODER_QUERY_FAILED, ERROR_CODE_DECODING_FAILED, ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, ERROR_CODE_DECODING_RESOURCES_RECLAIMED, ERROR_CODE_DISCONNECTED, ERROR_CODE_DRM_CONTENT_ERROR, ERROR_CODE_DRM_DEVICE_REVOKED, ERROR_CODE_DRM_DISALLOWED_OPERATION, ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, ERROR_CODE_DRM_LICENSE_EXPIRED, ERROR_CODE_DRM_PROVISIONING_FAILED, ERROR_CODE_DRM_SCHEME_UNSUPPORTED, ERROR_CODE_DRM_SYSTEM_ERROR, ERROR_CODE_DRM_UNSPECIFIED, ERROR_CODE_END_OF_PLAYLIST, ERROR_CODE_FAILED_RUNTIME_CHECK, ERROR_CODE_INVALID_STATE, ERROR_CODE_IO_BAD_HTTP_STATUS, ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, ERROR_CODE_IO_FILE_NOT_FOUND, ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, ERROR_CODE_IO_NO_PERMISSION, ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, ERROR_CODE_IO_UNSPECIFIED, ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_NOT_SUPPORTED, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, ERROR_CODE_PARSING_CONTAINER_MALFORMED, ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, ERROR_CODE_PARSING_MANIFEST_MALFORMED, ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, ERROR_CODE_PERMISSION_DENIED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, ERROR_CODE_REMOTE_ERROR, ERROR_CODE_SETUP_REQUIRED, ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_TIMEOUT, ERROR_CODE_UNSPECIFIED, ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED, ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED, errorCode, extras, FIELD_CUSTOM_ID_BASE, timestampMs
Methods
public static ExoPlaybackExceptioncreateForRemote(java.lang.String message)

Creates an instance of type ExoPlaybackException.TYPE_REMOTE.

public static ExoPlaybackExceptioncreateForRenderer(java.lang.Throwable cause, java.lang.String rendererName, int rendererIndex, Format rendererFormat, int rendererFormatSupport, boolean isRecoverable, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_RENDERER.

public static ExoPlaybackExceptioncreateForSource(java.io.IOException cause, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_SOURCE.

public static ExoPlaybackExceptioncreateForUnexpected(java.lang.RuntimeException cause)

public static ExoPlaybackExceptioncreateForUnexpected(java.lang.RuntimeException cause, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_UNEXPECTED.

public booleanerrorInfoEquals(PlaybackException other)

Returns whether the error data associated to this exception equals the error data associated to other.

public static ExoPlaybackExceptionfromBundle(Bundle bundle)

Restores a ExoPlaybackException from a .

public java.lang.ExceptiongetRendererException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER.

public java.io.IOExceptiongetSourceException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_SOURCE.

public java.lang.RuntimeExceptiongetUnexpectedException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_UNEXPECTED.

public BundletoBundle()

from PlaybackExceptiongetErrorCodeName, getErrorCodeName
from java.lang.ThrowableaddSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final int TYPE_SOURCE

The error occurred loading data from a MediaSource.

Call ExoPlaybackException.getSourceException() to retrieve the underlying cause.

public static final int TYPE_RENDERER

The error occurred in a Renderer.

Call ExoPlaybackException.getRendererException() to retrieve the underlying cause.

public static final int TYPE_UNEXPECTED

The error was an unexpected java.lang.RuntimeException.

Call ExoPlaybackException.getUnexpectedException() to retrieve the underlying cause.

public static final int TYPE_REMOTE

The error occurred in a remote component.

Call getMessage to retrieve the message associated with the error.

public final int type

The ExoPlaybackException.Type of the playback failure.

public final java.lang.String rendererName

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the name of the renderer.

public final int rendererIndex

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the index of the renderer.

public final Format rendererFormat

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the Format the renderer was using at the time of the exception, or null if the renderer wasn't using a Format.

public final int rendererFormatSupport

If ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER, this is the level of C.FormatSupport of the renderer for ExoPlaybackException.rendererFormat. If ExoPlaybackException.rendererFormat is null, this is C.FORMAT_HANDLED.

public final MediaSource.MediaPeriodId mediaPeriodId

The MediaSource.MediaPeriodId of the media associated with this error, or null if undetermined.

Methods

public static ExoPlaybackException createForSource(java.io.IOException cause, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_SOURCE.

Parameters:

cause: The cause of the failure.
errorCode: See PlaybackException.errorCode.

Returns:

The created instance.

public static ExoPlaybackException createForRenderer(java.lang.Throwable cause, java.lang.String rendererName, int rendererIndex, Format rendererFormat, int rendererFormatSupport, boolean isRecoverable, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_RENDERER.

Parameters:

cause: The cause of the failure.
rendererName: The name of the renderer in which the failure occurred.
rendererIndex: The index of the renderer in which the failure occurred.
rendererFormat: The Format the renderer was using at the time of the exception, or null if the renderer wasn't using a Format.
rendererFormatSupport: The C.FormatSupport of the renderer for rendererFormat. Ignored if rendererFormat is null.
isRecoverable: If the failure can be recovered by disabling and re-enabling the renderer.
errorCode: See PlaybackException.errorCode.

Returns:

The created instance.

public static ExoPlaybackException createForUnexpected(java.lang.RuntimeException cause)

Deprecated: Use createForUnexpected(RuntimeException, ERROR_CODE_UNSPECIFIED) instead.

public static ExoPlaybackException createForUnexpected(java.lang.RuntimeException cause, int errorCode)

Creates an instance of type ExoPlaybackException.TYPE_UNEXPECTED.

Parameters:

cause: The cause of the failure.
errorCode: See PlaybackException.errorCode.

Returns:

The created instance.

public static ExoPlaybackException createForRemote(java.lang.String message)

Creates an instance of type ExoPlaybackException.TYPE_REMOTE.

Parameters:

message: The message associated with the error.

Returns:

The created instance.

public java.io.IOException getSourceException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_SOURCE.

public java.lang.Exception getRendererException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_RENDERER.

public java.lang.RuntimeException getUnexpectedException()

Retrieves the underlying error when ExoPlaybackException.type is ExoPlaybackException.TYPE_UNEXPECTED.

public boolean errorInfoEquals(PlaybackException other)

Returns whether the error data associated to this exception equals the error data associated to other.

Note that this method does not compare the exceptions' stacktraces.

public static ExoPlaybackException fromBundle(Bundle bundle)

Restores a ExoPlaybackException from a .

public Bundle toBundle()

It omits the ExoPlaybackException.mediaPeriodId field. The ExoPlaybackException.mediaPeriodId of an instance restored by ExoPlaybackException.fromBundle(Bundle) will always be null.

Source

/*
 * Copyright (C) 2016 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.exoplayer;

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.Bundle;
import android.os.SystemClock;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.C.FormatSupport;
import androidx.media3.common.Format;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import java.io.IOException;
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 playback failure occurs. */
public final class ExoPlaybackException extends PlaybackException {

  /**
   * The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER}
   * {@link #TYPE_UNEXPECTED} or {@link #TYPE_REMOTE}. Note that new types may be added in the
   * future and error handling should handle unknown type values.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @UnstableApi
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED, TYPE_REMOTE})
  public @interface Type {}

  /**
   * The error occurred loading data from a {@link MediaSource}.
   *
   * <p>Call {@link #getSourceException()} to retrieve the underlying cause.
   */
  @UnstableApi public static final int TYPE_SOURCE = 0;

  /**
   * The error occurred in a {@link Renderer}.
   *
   * <p>Call {@link #getRendererException()} to retrieve the underlying cause.
   */
  @UnstableApi public static final int TYPE_RENDERER = 1;

  /**
   * The error was an unexpected {@link RuntimeException}.
   *
   * <p>Call {@link #getUnexpectedException()} to retrieve the underlying cause.
   */
  @UnstableApi public static final int TYPE_UNEXPECTED = 2;

  /**
   * The error occurred in a remote component.
   *
   * <p>Call {@link #getMessage()} to retrieve the message associated with the error.
   */
  @UnstableApi public static final int TYPE_REMOTE = 3;

  /** The {@link Type} of the playback failure. */
  @UnstableApi public final @Type int type;

  /** If {@link #type} is {@link #TYPE_RENDERER}, this is the name of the renderer. */
  @UnstableApi @Nullable public final String rendererName;

  /** If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer. */
  @UnstableApi public final int rendererIndex;

  /**
   * If {@link #type} is {@link #TYPE_RENDERER}, this is the {@link Format} the renderer was using
   * at the time of the exception, or null if the renderer wasn't using a {@link Format}.
   */
  @UnstableApi @Nullable public final Format rendererFormat;

  /**
   * If {@link #type} is {@link #TYPE_RENDERER}, this is the level of {@link FormatSupport} of the
   * renderer for {@link #rendererFormat}. If {@link #rendererFormat} is null, this is {@link
   * C#FORMAT_HANDLED}.
   */
  @UnstableApi public final @FormatSupport int rendererFormatSupport;

  /** The {@link MediaPeriodId} of the media associated with this error, or null if undetermined. */
  @UnstableApi @Nullable public final MediaPeriodId mediaPeriodId;

  /**
   * If {@link #type} is {@link #TYPE_RENDERER}, this field indicates whether the error may be
   * recoverable by disabling and re-enabling (but <em>not</em> resetting) the renderers. For other
   * {@link Type types} this field will always be {@code false}.
   */
  /* package */ final boolean isRecoverable;

  /**
   * Creates an instance of type {@link #TYPE_SOURCE}.
   *
   * @param cause The cause of the failure.
   * @param errorCode See {@link #errorCode}.
   * @return The created instance.
   */
  @UnstableApi
  public static ExoPlaybackException createForSource(IOException cause, int errorCode) {
    return new ExoPlaybackException(TYPE_SOURCE, cause, errorCode);
  }

  /**
   * Creates an instance of type {@link #TYPE_RENDERER}.
   *
   * @param cause The cause of the failure.
   * @param rendererName The {@linkplain Renderer#getName() name} of the renderer in which the
   *     failure occurred.
   * @param rendererIndex The index of the renderer in which the failure occurred.
   * @param rendererFormat The {@link Format} the renderer was using at the time of the exception,
   *     or null if the renderer wasn't using a {@link Format}.
   * @param rendererFormatSupport The {@link FormatSupport} of the renderer for {@code
   *     rendererFormat}. Ignored if {@code rendererFormat} is null.
   * @param isRecoverable If the failure can be recovered by disabling and re-enabling the renderer.
   * @param errorCode See {@link #errorCode}.
   * @return The created instance.
   */
  @UnstableApi
  public static ExoPlaybackException createForRenderer(
      Throwable cause,
      String rendererName,
      int rendererIndex,
      @Nullable Format rendererFormat,
      @FormatSupport int rendererFormatSupport,
      boolean isRecoverable,
      @ErrorCode int errorCode) {

    return new ExoPlaybackException(
        TYPE_RENDERER,
        cause,
        /* customMessage= */ null,
        errorCode,
        rendererName,
        rendererIndex,
        rendererFormat,
        rendererFormat == null ? C.FORMAT_HANDLED : rendererFormatSupport,
        isRecoverable);
  }

  /**
   * @deprecated Use {@link #createForUnexpected(RuntimeException, int)
   *     createForUnexpected(RuntimeException, ERROR_CODE_UNSPECIFIED)} instead.
   */
  @UnstableApi
  @Deprecated
  public static ExoPlaybackException createForUnexpected(RuntimeException cause) {
    return createForUnexpected(cause, ERROR_CODE_UNSPECIFIED);
  }

  /**
   * Creates an instance of type {@link #TYPE_UNEXPECTED}.
   *
   * @param cause The cause of the failure.
   * @param errorCode See {@link #errorCode}.
   * @return The created instance.
   */
  @UnstableApi
  public static ExoPlaybackException createForUnexpected(
      RuntimeException cause, @ErrorCode int errorCode) {
    return new ExoPlaybackException(TYPE_UNEXPECTED, cause, errorCode);
  }

  /**
   * Creates an instance of type {@link #TYPE_REMOTE}.
   *
   * @param message The message associated with the error.
   * @return The created instance.
   */
  @UnstableApi
  public static ExoPlaybackException createForRemote(String message) {
    return new ExoPlaybackException(
        TYPE_REMOTE,
        /* cause= */ null,
        /* customMessage= */ message,
        ERROR_CODE_REMOTE_ERROR,
        /* rendererName= */ null,
        /* rendererIndex= */ C.INDEX_UNSET,
        /* rendererFormat= */ null,
        /* rendererFormatSupport= */ C.FORMAT_HANDLED,
        /* isRecoverable= */ false);
  }

  private ExoPlaybackException(@Type int type, Throwable cause, @ErrorCode int errorCode) {
    this(
        type,
        cause,
        /* customMessage= */ null,
        errorCode,
        /* rendererName= */ null,
        /* rendererIndex= */ C.INDEX_UNSET,
        /* rendererFormat= */ null,
        /* rendererFormatSupport= */ C.FORMAT_HANDLED,
        /* isRecoverable= */ false);
  }

  private ExoPlaybackException(
      @Type int type,
      @Nullable Throwable cause,
      @Nullable String customMessage,
      @ErrorCode int errorCode,
      @Nullable String rendererName,
      int rendererIndex,
      @Nullable Format rendererFormat,
      @FormatSupport int rendererFormatSupport,
      boolean isRecoverable) {
    this(
        deriveMessage(
            type,
            customMessage,
            rendererName,
            rendererIndex,
            rendererFormat,
            rendererFormatSupport),
        cause,
        errorCode,
        type,
        rendererName,
        rendererIndex,
        rendererFormat,
        rendererFormatSupport,
        /* mediaPeriodId= */ null,
        /* timestampMs= */ SystemClock.elapsedRealtime(),
        isRecoverable);
  }

  private ExoPlaybackException(Bundle bundle) {
    super(bundle);
    type = bundle.getInt(FIELD_TYPE, /* defaultValue= */ TYPE_UNEXPECTED);
    rendererName = bundle.getString(FIELD_RENDERER_NAME);
    rendererIndex = bundle.getInt(FIELD_RENDERER_INDEX, /* defaultValue= */ C.INDEX_UNSET);
    @Nullable Bundle rendererFormatBundle = bundle.getBundle(FIELD_RENDERER_FORMAT);
    rendererFormat = rendererFormatBundle == null ? null : Format.fromBundle(rendererFormatBundle);
    rendererFormatSupport =
        bundle.getInt(FIELD_RENDERER_FORMAT_SUPPORT, /* defaultValue= */ C.FORMAT_HANDLED);
    isRecoverable = bundle.getBoolean(FIELD_IS_RECOVERABLE, /* defaultValue= */ false);
    mediaPeriodId = null;
  }

  private ExoPlaybackException(
      String message,
      @Nullable Throwable cause,
      @ErrorCode int errorCode,
      @Type int type,
      @Nullable String rendererName,
      int rendererIndex,
      @Nullable Format rendererFormat,
      @FormatSupport int rendererFormatSupport,
      @Nullable MediaPeriodId mediaPeriodId,
      long timestampMs,
      boolean isRecoverable) {
    super(message, cause, errorCode, Bundle.EMPTY, timestampMs);
    Assertions.checkArgument(!isRecoverable || type == TYPE_RENDERER);
    Assertions.checkArgument(cause != null || type == TYPE_REMOTE);
    this.type = type;
    this.rendererName = rendererName;
    this.rendererIndex = rendererIndex;
    this.rendererFormat = rendererFormat;
    this.rendererFormatSupport = rendererFormatSupport;
    this.mediaPeriodId = mediaPeriodId;
    this.isRecoverable = isRecoverable;
  }

  /**
   * Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}.
   *
   * @throws IllegalStateException If {@link #type} is not {@link #TYPE_SOURCE}.
   */
  @UnstableApi
  public IOException getSourceException() {
    Assertions.checkState(type == TYPE_SOURCE);
    return (IOException) Assertions.checkNotNull(getCause());
  }

  /**
   * Retrieves the underlying error when {@link #type} is {@link #TYPE_RENDERER}.
   *
   * @throws IllegalStateException If {@link #type} is not {@link #TYPE_RENDERER}.
   */
  @UnstableApi
  public Exception getRendererException() {
    Assertions.checkState(type == TYPE_RENDERER);
    return (Exception) Assertions.checkNotNull(getCause());
  }

  /**
   * Retrieves the underlying error when {@link #type} is {@link #TYPE_UNEXPECTED}.
   *
   * @throws IllegalStateException If {@link #type} is not {@link #TYPE_UNEXPECTED}.
   */
  @UnstableApi
  public RuntimeException getUnexpectedException() {
    Assertions.checkState(type == TYPE_UNEXPECTED);
    return (RuntimeException) Assertions.checkNotNull(getCause());
  }

  @Override
  public boolean errorInfoEquals(@Nullable PlaybackException that) {
    if (!super.errorInfoEquals(that)) {
      return false;
    }
    // We know that is not null and is an ExoPlaybackException because of the super call returning
    // true.
    ExoPlaybackException other = (ExoPlaybackException) Util.castNonNull(that);
    return type == other.type
        && Util.areEqual(rendererName, other.rendererName)
        && rendererIndex == other.rendererIndex
        && Util.areEqual(rendererFormat, other.rendererFormat)
        && rendererFormatSupport == other.rendererFormatSupport
        && Util.areEqual(mediaPeriodId, other.mediaPeriodId)
        && isRecoverable == other.isRecoverable;
  }

  /**
   * Returns a copy of this exception with the provided {@link MediaPeriodId}.
   *
   * @param mediaPeriodId The {@link MediaPeriodId}.
   * @return The copied exception.
   */
  @CheckResult
  /* package */ ExoPlaybackException copyWithMediaPeriodId(@Nullable MediaPeriodId mediaPeriodId) {
    return new ExoPlaybackException(
        Util.castNonNull(getMessage()),
        getCause(),
        errorCode,
        type,
        rendererName,
        rendererIndex,
        rendererFormat,
        rendererFormatSupport,
        mediaPeriodId,
        timestampMs,
        isRecoverable);
  }

  private static String deriveMessage(
      @Type int type,
      @Nullable String customMessage,
      @Nullable String rendererName,
      int rendererIndex,
      @Nullable Format rendererFormat,
      @FormatSupport int rendererFormatSupport) {
    String message;
    switch (type) {
      case TYPE_SOURCE:
        message = "Source error";
        break;
      case TYPE_RENDERER:
        message =
            rendererName
                + " error"
                + ", index="
                + rendererIndex
                + ", format="
                + rendererFormat
                + ", format_supported="
                + Util.getFormatSupportString(rendererFormatSupport);
        break;
      case TYPE_REMOTE:
        message = "Remote error";
        break;
      case TYPE_UNEXPECTED:
      default:
        message = "Unexpected runtime error";
        break;
    }
    if (!TextUtils.isEmpty(customMessage)) {
      message += ": " + customMessage;
    }
    return message;
  }

  /** Restores a {@code ExoPlaybackException} from a {@link Bundle}. */
  @UnstableApi
  public static ExoPlaybackException fromBundle(Bundle bundle) {
    return new ExoPlaybackException(bundle);
  }

  private static final String FIELD_TYPE = Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 1);
  private static final String FIELD_RENDERER_NAME =
      Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 2);
  private static final String FIELD_RENDERER_INDEX =
      Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 3);
  private static final String FIELD_RENDERER_FORMAT =
      Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 4);
  private static final String FIELD_RENDERER_FORMAT_SUPPORT =
      Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 5);
  private static final String FIELD_IS_RECOVERABLE =
      Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 6);

  /**
   * {@inheritDoc}
   *
   * <p>It omits the {@link #mediaPeriodId} field. The {@link #mediaPeriodId} of an instance
   * restored by {@link #fromBundle} will always be {@code null}.
   */
  @UnstableApi
  @Override
  public Bundle toBundle() {
    Bundle bundle = super.toBundle();
    bundle.putInt(FIELD_TYPE, type);
    bundle.putString(FIELD_RENDERER_NAME, rendererName);
    bundle.putInt(FIELD_RENDERER_INDEX, rendererIndex);
    if (rendererFormat != null) {
      bundle.putBundle(
          FIELD_RENDERER_FORMAT, rendererFormat.toBundle(/* excludeMetadata= */ false));
    }
    bundle.putInt(FIELD_RENDERER_FORMAT_SUPPORT, rendererFormatSupport);
    bundle.putBoolean(FIELD_IS_RECOVERABLE, isRecoverable);
    return bundle;
  }
}