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

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.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

/**
 * A media player interface defining traditional high-level functionality, such as the ability to
 * play, pause, seek and query properties of the currently playing media.
 *
 * <p>This interface includes some convenience methods that can be implemented by calling other
 * methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting
 * {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of
 * required methods can be implemented.
 *
 * <p>Some important properties of media players that implement this interface are:
 *
 * <ul>
 *   <li>They can provide a {@link Timeline} representing the structure of the media being played,
 *       which can be obtained by calling {@link #getCurrentTimeline()}.
 *   <li>They can provide a {@link Tracks} defining the currently available tracks and which are
 *       selected to be rendered, which can be obtained by calling {@link #getCurrentTracks()}.
 * </ul>
 */
public interface Player {

  /** A set of {@link Event events}. */
  final class Events {

    private final FlagSet flags;

    /**
     * Creates an instance.
     *
     * @param flags The {@link FlagSet} containing the {@link Event events}.
     */
    @UnstableApi
    public Events(FlagSet flags) {
      this.flags = flags;
    }

    /**
     * Returns whether the given {@link Event} occurred.
     *
     * @param event The {@link Event}.
     * @return Whether the {@link Event} occurred.
     */
    public boolean contains(@Event int event) {
      return flags.contains(event);
    }

    /**
     * Returns whether any of the given {@link Event events} occurred.
     *
     * @param events The {@link Event events}.
     * @return Whether any of the {@link Event events} occurred.
     */
    public boolean containsAny(@Event int... events) {
      return flags.containsAny(events);
    }

    /** Returns the number of events in the set. */
    public int size() {
      return flags.size();
    }

    /**
     * Returns the {@link Event} at the given index.
     *
     * <p>Although index-based access is possible, it doesn't imply a particular order of these
     * events.
     *
     * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive).
     * @return The {@link Event} at the given index.
     * @throws IndexOutOfBoundsException If index is outside the allowed range.
     */
    public @Event int get(int index) {
      return flags.get(index);
    }

    @Override
    public int hashCode() {
      return flags.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof Events)) {
        return false;
      }
      Events other = (Events) obj;
      return flags.equals(other.flags);
    }
  }

  /** Position info describing a playback position involved in a discontinuity. */
  final class PositionInfo implements Bundleable {

    /**
     * The UID of the window, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}.
     */
    @Nullable public final Object windowUid;
    /**
     * @deprecated Use {@link #mediaItemIndex} instead.
     */
    @UnstableApi @Deprecated public final int windowIndex;
    /** The media item index. */
    public final int mediaItemIndex;
    /** The media item, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}. */
    @UnstableApi @Nullable public final MediaItem mediaItem;
    /**
     * The UID of the period, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}.
     */
    @Nullable public final Object periodUid;
    /** The period index. */
    public final int periodIndex;
    /** The playback position, in milliseconds. */
    public final long positionMs;
    /**
     * The content position, in milliseconds.
     *
     * <p>If {@link #adGroupIndex} is {@link C#INDEX_UNSET}, this is the same as {@link
     * #positionMs}.
     */
    public final long contentPositionMs;
    /**
     * The ad group index if the playback position is within an ad, {@link C#INDEX_UNSET} otherwise.
     */
    public final int adGroupIndex;
    /**
     * The index of the ad within the ad group if the playback position is within an ad, {@link
     * C#INDEX_UNSET} otherwise.
     */
    public final int adIndexInAdGroup;

    /**
     * @deprecated Use {@link #PositionInfo(Object, int, MediaItem, Object, int, long, long, int,
     *     int)} instead.
     */
    @Deprecated
    @UnstableApi
    public PositionInfo(
        @Nullable Object windowUid,
        int mediaItemIndex,
        @Nullable Object periodUid,
        int periodIndex,
        long positionMs,
        long contentPositionMs,
        int adGroupIndex,
        int adIndexInAdGroup) {
      this(
          windowUid,
          mediaItemIndex,
          MediaItem.EMPTY,
          periodUid,
          periodIndex,
          positionMs,
          contentPositionMs,
          adGroupIndex,
          adIndexInAdGroup);
    }

    /** Creates an instance. */
    @UnstableApi
    public PositionInfo(
        @Nullable Object windowUid,
        int mediaItemIndex,
        @Nullable MediaItem mediaItem,
        @Nullable Object periodUid,
        int periodIndex,
        long positionMs,
        long contentPositionMs,
        int adGroupIndex,
        int adIndexInAdGroup) {
      this.windowUid = windowUid;
      this.windowIndex = mediaItemIndex;
      this.mediaItemIndex = mediaItemIndex;
      this.mediaItem = mediaItem;
      this.periodUid = periodUid;
      this.periodIndex = periodIndex;
      this.positionMs = positionMs;
      this.contentPositionMs = contentPositionMs;
      this.adGroupIndex = adGroupIndex;
      this.adIndexInAdGroup = adIndexInAdGroup;
    }

    @Override
    public boolean equals(@Nullable Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      PositionInfo that = (PositionInfo) o;
      return mediaItemIndex == that.mediaItemIndex
          && periodIndex == that.periodIndex
          && positionMs == that.positionMs
          && contentPositionMs == that.contentPositionMs
          && adGroupIndex == that.adGroupIndex
          && adIndexInAdGroup == that.adIndexInAdGroup
          && Objects.equal(windowUid, that.windowUid)
          && Objects.equal(periodUid, that.periodUid)
          && Objects.equal(mediaItem, that.mediaItem);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(
          windowUid,
          mediaItemIndex,
          mediaItem,
          periodUid,
          periodIndex,
          positionMs,
          contentPositionMs,
          adGroupIndex,
          adIndexInAdGroup);
    }

    // Bundleable implementation.
    @Documented
    @Retention(RetentionPolicy.SOURCE)
    @Target(TYPE_USE)
    @IntDef({
      FIELD_MEDIA_ITEM_INDEX,
      FIELD_MEDIA_ITEM,
      FIELD_PERIOD_INDEX,
      FIELD_POSITION_MS,
      FIELD_CONTENT_POSITION_MS,
      FIELD_AD_GROUP_INDEX,
      FIELD_AD_INDEX_IN_AD_GROUP
    })
    private @interface FieldNumber {}

    private static final int FIELD_MEDIA_ITEM_INDEX = 0;
    private static final int FIELD_MEDIA_ITEM = 1;
    private static final int FIELD_PERIOD_INDEX = 2;
    private static final int FIELD_POSITION_MS = 3;
    private static final int FIELD_CONTENT_POSITION_MS = 4;
    private static final int FIELD_AD_GROUP_INDEX = 5;
    private static final int FIELD_AD_INDEX_IN_AD_GROUP = 6;

    /**
     * {@inheritDoc}
     *
     * <p>It omits the {@link #windowUid} and {@link #periodUid} fields. The {@link #windowUid} and
     * {@link #periodUid} of an instance restored by {@link #CREATOR} will always be {@code null}.
     */
    @UnstableApi
    @Override
    public Bundle toBundle() {
      Bundle bundle = new Bundle();
      bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex);
      if (mediaItem != null) {
        bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle());
      }
      bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex);
      bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs);
      bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
      bundle.putInt(keyForField(FIELD_AD_GROUP_INDEX), adGroupIndex);
      bundle.putInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), adIndexInAdGroup);
      return bundle;
    }

    /** Object that can restore {@link PositionInfo} from a {@link Bundle}. */
    @UnstableApi public static final Creator<PositionInfo> CREATOR = PositionInfo::fromBundle;

    private static PositionInfo fromBundle(Bundle bundle) {
      int mediaItemIndex =
          bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
      @Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
      @Nullable
      MediaItem mediaItem =
          mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle);
      int periodIndex =
          bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
      long positionMs =
          bundle.getLong(keyForField(FIELD_POSITION_MS), /* defaultValue= */ C.TIME_UNSET);
      long contentPositionMs =
          bundle.getLong(keyForField(FIELD_CONTENT_POSITION_MS), /* defaultValue= */ C.TIME_UNSET);
      int adGroupIndex =
          bundle.getInt(keyForField(FIELD_AD_GROUP_INDEX), /* defaultValue= */ C.INDEX_UNSET);
      int adIndexInAdGroup =
          bundle.getInt(keyForField(FIELD_AD_INDEX_IN_AD_GROUP), /* defaultValue= */ C.INDEX_UNSET);
      return new PositionInfo(
          /* windowUid= */ null,
          mediaItemIndex,
          mediaItem,
          /* periodUid= */ null,
          periodIndex,
          positionMs,
          contentPositionMs,
          adGroupIndex,
          adIndexInAdGroup);
    }

    private static String keyForField(@FieldNumber int field) {
      return Integer.toString(field, Character.MAX_RADIX);
    }
  }

  /**
   * A set of {@link Command commands}.
   *
   * <p>Instances are immutable.
   */
  final class Commands implements Bundleable {

    /** A builder for {@link Commands} instances. */
    @UnstableApi
    public static final class Builder {

      private static final @Command int[] SUPPORTED_COMMANDS = {
        COMMAND_PLAY_PAUSE,
        COMMAND_PREPARE,
        COMMAND_STOP,
        COMMAND_SEEK_TO_DEFAULT_POSITION,
        COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
        COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
        COMMAND_SEEK_TO_PREVIOUS,
        COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
        COMMAND_SEEK_TO_NEXT,
        COMMAND_SEEK_TO_MEDIA_ITEM,
        COMMAND_SEEK_BACK,
        COMMAND_SEEK_FORWARD,
        COMMAND_SET_SPEED_AND_PITCH,
        COMMAND_SET_SHUFFLE_MODE,
        COMMAND_SET_REPEAT_MODE,
        COMMAND_GET_CURRENT_MEDIA_ITEM,
        COMMAND_GET_TIMELINE,
        COMMAND_GET_MEDIA_ITEMS_METADATA,
        COMMAND_SET_MEDIA_ITEMS_METADATA,
        COMMAND_SET_MEDIA_ITEM,
        COMMAND_CHANGE_MEDIA_ITEMS,
        COMMAND_GET_AUDIO_ATTRIBUTES,
        COMMAND_GET_VOLUME,
        COMMAND_GET_DEVICE_VOLUME,
        COMMAND_SET_VOLUME,
        COMMAND_SET_DEVICE_VOLUME,
        COMMAND_ADJUST_DEVICE_VOLUME,
        COMMAND_SET_VIDEO_SURFACE,
        COMMAND_GET_TEXT,
        COMMAND_SET_TRACK_SELECTION_PARAMETERS,
        COMMAND_GET_TRACKS,
      };

      private final FlagSet.Builder flagsBuilder;

      /** Creates a builder. */
      public Builder() {
        flagsBuilder = new FlagSet.Builder();
      }

      private Builder(Commands commands) {
        flagsBuilder = new FlagSet.Builder();
        flagsBuilder.addAll(commands.flags);
      }

      /**
       * Adds a {@link Command}.
       *
       * @param command A {@link Command}.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder add(@Command int command) {
        flagsBuilder.add(command);
        return this;
      }

      /**
       * Adds a {@link Command} if the provided condition is true. Does nothing otherwise.
       *
       * @param command A {@link Command}.
       * @param condition A condition.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder addIf(@Command int command, boolean condition) {
        flagsBuilder.addIf(command, condition);
        return this;
      }

      /**
       * Adds {@link Command commands}.
       *
       * @param commands The {@link Command commands} to add.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder addAll(@Command int... commands) {
        flagsBuilder.addAll(commands);
        return this;
      }

      /**
       * Adds {@link Commands}.
       *
       * @param commands The set of {@link Command commands} to add.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder addAll(Commands commands) {
        flagsBuilder.addAll(commands.flags);
        return this;
      }

      /**
       * Adds all existing {@link Command commands}.
       *
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder addAllCommands() {
        flagsBuilder.addAll(SUPPORTED_COMMANDS);
        return this;
      }

      /**
       * Removes a {@link Command}.
       *
       * @param command A {@link Command}.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder remove(@Command int command) {
        flagsBuilder.remove(command);
        return this;
      }

      /**
       * Removes a {@link Command} if the provided condition is true. Does nothing otherwise.
       *
       * @param command A {@link Command}.
       * @param condition A condition.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder removeIf(@Command int command, boolean condition) {
        flagsBuilder.removeIf(command, condition);
        return this;
      }

      /**
       * Removes {@link Command commands}.
       *
       * @param commands The {@link Command commands} to remove.
       * @return This builder.
       * @throws IllegalStateException If {@link #build()} has already been called.
       */
      public Builder removeAll(@Command int... commands) {
        flagsBuilder.removeAll(commands);
        return this;
      }

      /**
       * Builds a {@link Commands} instance.
       *
       * @throws IllegalStateException If this method has already been called.
       */
      public Commands build() {
        return new Commands(flagsBuilder.build());
      }
    }

    /** An empty set of commands. */
    public static final Commands EMPTY = new Builder().build();

    private final FlagSet flags;

    private Commands(FlagSet flags) {
      this.flags = flags;
    }

    /** Returns a {@link Builder} initialized with the values of this instance. */
    @UnstableApi
    public Builder buildUpon() {
      return new Builder(this);
    }

    /** Returns whether the set of commands contains the specified {@link Command}. */
    public boolean contains(@Command int command) {
      return flags.contains(command);
    }

    /** Returns whether the set of commands contains at least one of the given {@code commands}. */
    public boolean containsAny(@Command int... commands) {
      return flags.containsAny(commands);
    }

    /** Returns the number of commands in this set. */
    public int size() {
      return flags.size();
    }

    /**
     * Returns the {@link Command} at the given index.
     *
     * @param index The index. Must be between 0 (inclusive) and {@link #size()} (exclusive).
     * @return The {@link Command} at the given index.
     * @throws IndexOutOfBoundsException If index is outside the allowed range.
     */
    public @Command int get(int index) {
      return flags.get(index);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof Commands)) {
        return false;
      }
      Commands commands = (Commands) obj;
      return flags.equals(commands.flags);
    }

    @Override
    public int hashCode() {
      return flags.hashCode();
    }

    // Bundleable implementation.

    @Documented
    @Retention(RetentionPolicy.SOURCE)
    @Target(TYPE_USE)
    @IntDef({FIELD_COMMANDS})
    private @interface FieldNumber {}

    private static final int FIELD_COMMANDS = 0;

    @UnstableApi
    @Override
    public Bundle toBundle() {
      Bundle bundle = new Bundle();
      ArrayList<Integer> commandsBundle = new ArrayList<>();
      for (int i = 0; i < flags.size(); i++) {
        commandsBundle.add(flags.get(i));
      }
      bundle.putIntegerArrayList(keyForField(FIELD_COMMANDS), commandsBundle);
      return bundle;
    }

    /** Object that can restore {@link Commands} from a {@link Bundle}. */
    @UnstableApi public static final Creator<Commands> CREATOR = Commands::fromBundle;

    private static Commands fromBundle(Bundle bundle) {
      @Nullable
      ArrayList<Integer> commands = bundle.getIntegerArrayList(keyForField(FIELD_COMMANDS));
      if (commands == null) {
        return Commands.EMPTY;
      }
      Builder builder = new Builder();
      for (int i = 0; i < commands.size(); i++) {
        builder.add(commands.get(i));
      }
      return builder.build();
    }

    private static String keyForField(@FieldNumber int field) {
      return Integer.toString(field, Character.MAX_RADIX);
    }
  }

  /**
   * Listener of all changes in the Player.
   *
   * <p>All methods have no-op default implementations to allow selective overrides.
   */
  interface Listener {

    /**
     * Called when one or more player states changed.
     *
     * <p>State changes and events that happen within one {@link Looper} message queue iteration are
     * reported together and only after all individual callbacks were triggered.
     *
     * <p>Only state changes represented by {@link Event events} are reported through this method.
     *
     * <p>Listeners should prefer this method over individual callbacks in the following cases:
     *
     * <ul>
     *   <li>They intend to trigger the same logic for multiple events (e.g. when updating a UI for
     *       both {@link #onPlaybackStateChanged(int)} and {@link #onPlayWhenReadyChanged(boolean,
     *       int)}).
     *   <li>They need access to the {@link Player} object to trigger further events (e.g. to call
     *       {@link Player#seekTo(long)} after a {@link #onMediaItemTransition(MediaItem, int)}).
     *   <li>They intend to use multiple state values together or in combination with {@link Player}
     *       getter methods. For example using {@link #getCurrentMediaItemIndex()} with the {@code
     *       timeline} provided in {@link #onTimelineChanged(Timeline, int)} is only safe from
     *       within this method.
     *   <li>They are interested in events that logically happened together (e.g {@link
     *       #onPlaybackStateChanged(int)} to {@link #STATE_BUFFERING} because of {@link
     *       #onMediaItemTransition(MediaItem, int)}).
     * </ul>
     *
     * @param player The {@link Player} whose state changed. Use the getters to obtain the latest
     *     states.
     * @param events The {@link Events} that happened in this iteration, indicating which player
     *     states changed.
     */
    default void onEvents(Player player, Events events) {}

    /**
     * Called when the timeline has been refreshed.
     *
     * <p>Note that the current {@link MediaItem} or playback position may change as a result of a
     * timeline change. If playback can't continue smoothly because of this timeline change, a
     * separate {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} callback will be
     * triggered.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param timeline The latest timeline. Never null, but may be empty.
     * @param reason The {@link TimelineChangeReason} responsible for this timeline change.
     */
    default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {}

    /**
     * Called when playback transitions to a media item or starts repeating a media item according
     * to the current {@link #getRepeatMode() repeat mode}.
     *
     * <p>Note that this callback is also called when the playlist becomes non-empty or empty as a
     * consequence of a playlist change.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param mediaItem The {@link MediaItem}. May be null if the playlist becomes empty.
     * @param reason The reason for the transition.
     */
    default void onMediaItemTransition(
        @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {}

    /**
     * Called when the tracks change.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param tracks The available tracks information. Never null, but may be of length zero.
     */
    default void onTracksChanged(Tracks tracks) {}

    /**
     * Called when the combined {@link MediaMetadata} changes.
     *
     * <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
     * MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
     * any timed metadata that has been parsed from the media and output via {@link
     * Listener#onMetadata(Metadata)}. If a field is populated in the {@link
     * MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
     * timed metadata.
     *
     * <p>This method may be called multiple times in quick succession.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param mediaMetadata The combined {@link MediaMetadata}.
     */
    default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {}

    /**
     * Called when the playlist {@link MediaMetadata} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     */
    default void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) {}

    /**
     * Called when the player starts or stops loading the source.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param isLoading Whether the source is currently being loaded.
     */
    default void onIsLoadingChanged(boolean isLoading) {}

    /**
     * @deprecated Use {@link #onIsLoadingChanged(boolean)} instead.
     */
    @Deprecated
    @UnstableApi
    default void onLoadingChanged(boolean isLoading) {}

    /**
     * Called when the value returned from {@link #isCommandAvailable(int)} changes for at least one
     * {@link Command}.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param availableCommands The available {@link Commands}.
     */
    default void onAvailableCommandsChanged(Commands availableCommands) {}

    /**
     * Called when the value returned from {@link #getTrackSelectionParameters()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param parameters The new {@link TrackSelectionParameters}.
     */
    default void onTrackSelectionParametersChanged(TrackSelectionParameters parameters) {}

    /**
     * @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link
     *     #onPlayWhenReadyChanged(boolean, int)} instead.
     */
    @Deprecated
    @UnstableApi
    default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {}

    /**
     * Called when the value returned from {@link #getPlaybackState()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param playbackState The new playback {@link State state}.
     */
    default void onPlaybackStateChanged(@State int playbackState) {}

    /**
     * Called when the value returned from {@link #getPlayWhenReady()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param playWhenReady Whether playback will proceed when ready.
     * @param reason The {@link PlayWhenReadyChangeReason reason} for the change.
     */
    default void onPlayWhenReadyChanged(
        boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {}

    /**
     * Called when the value returned from {@link #getPlaybackSuppressionReason()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param playbackSuppressionReason The current {@link PlaybackSuppressionReason}.
     */
    default void onPlaybackSuppressionReasonChanged(
        @PlaybackSuppressionReason int playbackSuppressionReason) {}

    /**
     * Called when the value of {@link #isPlaying()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param isPlaying Whether the player is playing.
     */
    default void onIsPlayingChanged(boolean isPlaying) {}

    /**
     * Called when the value of {@link #getRepeatMode()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param repeatMode The {@link RepeatMode} used for playback.
     */
    default void onRepeatModeChanged(@RepeatMode int repeatMode) {}

    /**
     * Called when the value of {@link #getShuffleModeEnabled()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param shuffleModeEnabled Whether shuffling of {@link MediaItem media items} is enabled.
     */
    default void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {}

    /**
     * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
     * immediately after this method is called. The player instance can still be used, and {@link
     * #release()} must still be called on the player should it no longer be required.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * <p>Implementations of Player may pass an instance of a subclass of {@link PlaybackException}
     * to this method in order to include more information about the error.
     *
     * @param error The error.
     */
    default void onPlayerError(PlaybackException error) {}

    /**
     * Called when the {@link PlaybackException} returned by {@link #getPlayerError()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * <p>Implementations of Player may pass an instance of a subclass of {@link PlaybackException}
     * to this method in order to include more information about the error.
     *
     * @param error The new error, or null if the error is being cleared.
     */
    default void onPlayerErrorChanged(@Nullable PlaybackException error) {}

    /**
     * @deprecated Use {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} instead.
     */
    @Deprecated
    @UnstableApi
    default void onPositionDiscontinuity(@DiscontinuityReason int reason) {}

    /**
     * Called when a position discontinuity occurs.
     *
     * <p>A position discontinuity occurs when the playing period changes, the playback position
     * jumps within the period currently being played, or when the playing period has been skipped
     * or removed.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param oldPosition The position before the discontinuity.
     * @param newPosition The position after the discontinuity.
     * @param reason The {@link DiscontinuityReason} responsible for the discontinuity.
     */
    default void onPositionDiscontinuity(
        PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {}

    /**
     * Called when the current playback parameters change. The playback parameters may change due to
     * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change
     * them (for example, if audio playback switches to passthrough or offload mode, where speed
     * adjustment is no longer possible).
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param playbackParameters The playback parameters.
     */
    default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}

    /**
     * Called when the value of {@link #getSeekBackIncrement()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds.
     */
    default void onSeekBackIncrementChanged(long seekBackIncrementMs) {}

    /**
     * Called when the value of {@link #getSeekForwardIncrement()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds.
     */
    default void onSeekForwardIncrementChanged(long seekForwardIncrementMs) {}

    /**
     * Called when the value of {@link #getMaxSeekToPreviousPosition()} changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()}
     *     seeks to the previous position, in milliseconds.
     */
    default void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) {}

    /**
     * @deprecated Seeks are processed without delay. Listen to {@link
     *     #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} with reason {@link
     *     #DISCONTINUITY_REASON_SEEK} instead.
     */
    @Deprecated
    @UnstableApi
    default void onSeekProcessed() {}

    /**
     * Called when the audio session ID changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param audioSessionId The audio session ID.
     */
    @UnstableApi
    default void onAudioSessionIdChanged(int audioSessionId) {}

    /**
     * Called when the audio attributes change.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param audioAttributes The audio attributes.
     */
    default void onAudioAttributesChanged(AudioAttributes audioAttributes) {}

    /**
     * Called when the volume changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param volume The new volume, with 0 being silence and 1 being unity gain.
     */
    default void onVolumeChanged(float volume) {}

    /**
     * Called when skipping silences is enabled or disabled in the audio stream.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled.
     */
    default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}

    /**
     * Called when the device information changes
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param deviceInfo The new {@link DeviceInfo}.
     */
    default void onDeviceInfoChanged(DeviceInfo deviceInfo) {}

    /**
     * Called when the device volume or mute state changes.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param volume The new device volume, with 0 being silence and 1 being unity gain.
     * @param muted Whether the device is muted.
     */
    default void onDeviceVolumeChanged(int volume, boolean muted) {}

    /**
     * Called each time there's a change in the size of the video being rendered.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param videoSize The new size of the video.
     */
    default void onVideoSizeChanged(VideoSize videoSize) {}

    /**
     * Called each time there's a change in the size of the surface onto which the video is being
     * rendered.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param width The surface width in pixels. May be {@link C#LENGTH_UNSET} if unknown, or 0 if
     *     the video is not rendered onto a surface.
     * @param height The surface height in pixels. May be {@link C#LENGTH_UNSET} if unknown, or 0 if
     *     the video is not rendered onto a surface.
     */
    default void onSurfaceSizeChanged(int width, int height) {}

    /**
     * Called when a frame is rendered for the first time since setting the surface, or since the
     * renderer was reset, or since the stream being rendered was changed.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     */
    default void onRenderedFirstFrame() {}

    /**
     * Called when there is a change in the {@link Cue Cues}.
     *
     * <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
     * in the cues. You should only implement one or the other.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @deprecated Use {@link #onCues(CueGroup)} instead.
     */
    @Deprecated
    @UnstableApi
    default void onCues(List<Cue> cues) {}

    /**
     * Called when there is a change in the {@link CueGroup}.
     *
     * <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
     * in the cues. You should only implement one or the other.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     */
    default void onCues(CueGroup cueGroup) {}

    /**
     * Called when there is metadata associated with the current playback time.
     *
     * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
     * other events that happen in the same {@link Looper} message queue iteration.
     *
     * @param metadata The metadata.
     */
    @UnstableApi
    default void onMetadata(Metadata metadata) {}
  }

  /**
   * Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
   * {@link #STATE_ENDED}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
  @interface State {}
  /**
   * The player is idle, meaning it holds only limited resources. The player must be {@link
   * #prepare() prepared} before it will play the media.
   */
  int STATE_IDLE = 1;
  /**
   * The player is not able to immediately play the media, but is doing work toward being able to do
   * so. This state typically occurs when the player needs to buffer more data before playback can
   * start.
   */
  int STATE_BUFFERING = 2;
  /**
   * The player is able to immediately play from its current position. The player will be playing if
   * {@link #getPlayWhenReady()} is true, and paused otherwise.
   */
  int STATE_READY = 3;
  /** The player has finished playing the media. */
  int STATE_ENDED = 4;

  /**
   * Reasons for {@link #getPlayWhenReady() playWhenReady} changes. One of {@link
   * #PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST}, {@link
   * #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS}, {@link
   * #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY}, {@link
   * #PLAY_WHEN_READY_CHANGE_REASON_REMOTE} or {@link
   * #PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
    PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
    PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY,
    PLAY_WHEN_READY_CHANGE_REASON_REMOTE,
    PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM
  })
  @interface PlayWhenReadyChangeReason {}
  /** Playback has been started or paused by a call to {@link #setPlayWhenReady(boolean)}. */
  int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1;
  /** Playback has been paused because of a loss of audio focus. */
  int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2;
  /** Playback has been paused to avoid becoming noisy. */
  int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3;
  /** Playback has been started or paused because of a remote change. */
  int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4;
  /** Playback has been paused at the end of a media item. */
  int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5;

  /**
   * Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
   * of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link
   * #PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    PLAYBACK_SUPPRESSION_REASON_NONE,
    PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
  })
  @interface PlaybackSuppressionReason {}
  /** Playback is not suppressed. */
  int PLAYBACK_SUPPRESSION_REASON_NONE = 0;
  /** Playback is suppressed due to transient audio focus loss. */
  int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1;

  /**
   * Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
   * #REPEAT_MODE_ALL}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
  @interface RepeatMode {}
  /**
   * Normal playback without repetition. "Previous" and "Next" actions move to the previous and next
   * {@link MediaItem} respectively, and do nothing when there is no previous or next {@link
   * MediaItem} to move to.
   */
  int REPEAT_MODE_OFF = 0;
  /**
   * Repeats the currently playing {@link MediaItem} infinitely during ongoing playback. "Previous"
   * and "Next" actions behave as they do in {@link #REPEAT_MODE_OFF}, moving to the previous and
   * next {@link MediaItem} respectively, and doing nothing when there is no previous or next {@link
   * MediaItem} to move to.
   */
  int REPEAT_MODE_ONE = 1;
  /**
   * Repeats the entire timeline infinitely. "Previous" and "Next" actions behave as they do in
   * {@link #REPEAT_MODE_OFF}, but with looping at the ends so that "Previous" when playing the
   * first {@link MediaItem} will move to the last {@link MediaItem}, and "Next" when playing the
   * last {@link MediaItem} will move to the first {@link MediaItem}.
   */
  int REPEAT_MODE_ALL = 2;

  /**
   * Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_AUTO_TRANSITION},
   * {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link
   * #DISCONTINUITY_REASON_SKIP}, {@link #DISCONTINUITY_REASON_REMOVE} or {@link
   * #DISCONTINUITY_REASON_INTERNAL}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    DISCONTINUITY_REASON_AUTO_TRANSITION,
    DISCONTINUITY_REASON_SEEK,
    DISCONTINUITY_REASON_SEEK_ADJUSTMENT,
    DISCONTINUITY_REASON_SKIP,
    DISCONTINUITY_REASON_REMOVE,
    DISCONTINUITY_REASON_INTERNAL
  })
  @interface DiscontinuityReason {}
  /**
   * Automatic playback transition from one period in the timeline to the next. The period index may
   * be the same as it was before the discontinuity in case the current period is repeated.
   *
   * <p>This reason also indicates an automatic transition from the content period to an inserted ad
   * period or vice versa. Or a transition caused by another player (e.g. multiple controllers can
   * control the same playback on a remote device).
   */
  int DISCONTINUITY_REASON_AUTO_TRANSITION = 0;
  /** Seek within the current period or to another period. */
  int DISCONTINUITY_REASON_SEEK = 1;
  /**
   * Seek adjustment due to being unable to seek to the requested position or because the seek was
   * permitted to be inexact.
   */
  int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2;
  /** Discontinuity introduced by a skipped period (for instance a skipped ad). */
  int DISCONTINUITY_REASON_SKIP = 3;
  /** Discontinuity caused by the removal of the current period from the {@link Timeline}. */
  int DISCONTINUITY_REASON_REMOVE = 4;
  /** Discontinuity introduced internally (e.g. by the source). */
  int DISCONTINUITY_REASON_INTERNAL = 5;

  /**
   * Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED} or {@link
   * #TIMELINE_CHANGE_REASON_SOURCE_UPDATE}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, TIMELINE_CHANGE_REASON_SOURCE_UPDATE})
  @interface TimelineChangeReason {}
  /** Timeline changed as a result of a change of the playlist items or the order of the items. */
  int TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED = 0;
  /**
   * Timeline changed as a result of a source update (e.g. result of a dynamic update by the played
   * media).
   *
   * <p>This reason also indicates a change caused by another player (e.g. multiple controllers can
   * control the same playback on the remote device).
   */
  int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1;

  /**
   * Reasons for media item transitions. One of {@link #MEDIA_ITEM_TRANSITION_REASON_REPEAT}, {@link
   * #MEDIA_ITEM_TRANSITION_REASON_AUTO}, {@link #MEDIA_ITEM_TRANSITION_REASON_SEEK} or {@link
   * #MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    MEDIA_ITEM_TRANSITION_REASON_REPEAT,
    MEDIA_ITEM_TRANSITION_REASON_AUTO,
    MEDIA_ITEM_TRANSITION_REASON_SEEK,
    MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED
  })
  @interface MediaItemTransitionReason {}
  /** The media item has been repeated. */
  int MEDIA_ITEM_TRANSITION_REASON_REPEAT = 0;
  /**
   * Playback has automatically transitioned to the next media item.
   *
   * <p>This reason also indicates a transition caused by another player (e.g. multiple controllers
   * can control the same playback on a remote device).
   */
  int MEDIA_ITEM_TRANSITION_REASON_AUTO = 1;
  /** A seek to another media item has occurred. */
  int MEDIA_ITEM_TRANSITION_REASON_SEEK = 2;
  /**
   * The current media item has changed because of a change in the playlist. This can either be if
   * the media item previously being played has been removed, or when the playlist becomes non-empty
   * after being empty.
   */
  int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3;

  /**
   * Events that can be reported via {@link Listener#onEvents(Player, Events)}.
   *
   * <p>One of the {@link Player}{@code .EVENT_*} values.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    EVENT_TIMELINE_CHANGED,
    EVENT_MEDIA_ITEM_TRANSITION,
    EVENT_TRACKS_CHANGED,
    EVENT_IS_LOADING_CHANGED,
    EVENT_PLAYBACK_STATE_CHANGED,
    EVENT_PLAY_WHEN_READY_CHANGED,
    EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
    EVENT_IS_PLAYING_CHANGED,
    EVENT_REPEAT_MODE_CHANGED,
    EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
    EVENT_PLAYER_ERROR,
    EVENT_POSITION_DISCONTINUITY,
    EVENT_PLAYBACK_PARAMETERS_CHANGED,
    EVENT_AVAILABLE_COMMANDS_CHANGED,
    EVENT_MEDIA_METADATA_CHANGED,
    EVENT_PLAYLIST_METADATA_CHANGED,
    EVENT_SEEK_BACK_INCREMENT_CHANGED,
    EVENT_SEEK_FORWARD_INCREMENT_CHANGED,
    EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED,
    EVENT_TRACK_SELECTION_PARAMETERS_CHANGED,
    EVENT_AUDIO_ATTRIBUTES_CHANGED,
    EVENT_AUDIO_SESSION_ID,
    EVENT_VOLUME_CHANGED,
    EVENT_SKIP_SILENCE_ENABLED_CHANGED,
    EVENT_SURFACE_SIZE_CHANGED,
    EVENT_VIDEO_SIZE_CHANGED,
    EVENT_RENDERED_FIRST_FRAME,
    EVENT_CUES,
    EVENT_METADATA,
    EVENT_DEVICE_INFO_CHANGED,
    EVENT_DEVICE_VOLUME_CHANGED
  })
  @interface Event {}
  /** {@link #getCurrentTimeline()} changed. */
  int EVENT_TIMELINE_CHANGED = 0;
  /** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
  int EVENT_MEDIA_ITEM_TRANSITION = 1;
  /** {@link #getCurrentTracks()} changed. */
  int EVENT_TRACKS_CHANGED = 2;
  /** {@link #isLoading()} ()} changed. */
  int EVENT_IS_LOADING_CHANGED = 3;
  /** {@link #getPlaybackState()} changed. */
  int EVENT_PLAYBACK_STATE_CHANGED = 4;
  /** {@link #getPlayWhenReady()} changed. */
  int EVENT_PLAY_WHEN_READY_CHANGED = 5;
  /** {@link #getPlaybackSuppressionReason()} changed. */
  int EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED = 6;
  /** {@link #isPlaying()} changed. */
  int EVENT_IS_PLAYING_CHANGED = 7;
  /** {@link #getRepeatMode()} changed. */
  int EVENT_REPEAT_MODE_CHANGED = 8;
  /** {@link #getShuffleModeEnabled()} changed. */
  int EVENT_SHUFFLE_MODE_ENABLED_CHANGED = 9;
  /** {@link #getPlayerError()} changed. */
  int EVENT_PLAYER_ERROR = 10;
  /**
   * A position discontinuity occurred. See {@link Listener#onPositionDiscontinuity(PositionInfo,
   * PositionInfo, int)}.
   */
  int EVENT_POSITION_DISCONTINUITY = 11;
  /** {@link #getPlaybackParameters()} changed. */
  int EVENT_PLAYBACK_PARAMETERS_CHANGED = 12;
  /** {@link #isCommandAvailable(int)} changed for at least one {@link Command}. */
  int EVENT_AVAILABLE_COMMANDS_CHANGED = 13;
  /** {@link #getMediaMetadata()} changed. */
  int EVENT_MEDIA_METADATA_CHANGED = 14;
  /** {@link #getPlaylistMetadata()} changed. */
  int EVENT_PLAYLIST_METADATA_CHANGED = 15;
  /** {@link #getSeekBackIncrement()} changed. */
  int EVENT_SEEK_BACK_INCREMENT_CHANGED = 16;
  /** {@link #getSeekForwardIncrement()} changed. */
  int EVENT_SEEK_FORWARD_INCREMENT_CHANGED = 17;
  /** {@link #getMaxSeekToPreviousPosition()} changed. */
  int EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED = 18;
  /** {@link #getTrackSelectionParameters()} changed. */
  int EVENT_TRACK_SELECTION_PARAMETERS_CHANGED = 19;
  /** {@link #getAudioAttributes()} changed. */
  int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20;
  /** The audio session id was set. */
  int EVENT_AUDIO_SESSION_ID = 21;
  /** {@link #getVolume()} changed. */
  int EVENT_VOLUME_CHANGED = 22;
  /** Skipping silences in the audio stream is enabled or disabled. */
  int EVENT_SKIP_SILENCE_ENABLED_CHANGED = 23;
  /** The size of the surface onto which the video is being rendered changed. */
  int EVENT_SURFACE_SIZE_CHANGED = 24;
  /** {@link #getVideoSize()} changed. */
  int EVENT_VIDEO_SIZE_CHANGED = 25;
  /**
   * A frame is rendered for the first time since setting the surface, or since the renderer was
   * reset, or since the stream being rendered was changed.
   */
  int EVENT_RENDERED_FIRST_FRAME = 26;
  /** {@link #getCurrentCues()} changed. */
  int EVENT_CUES = 27;
  /** Metadata associated with the current playback time changed. */
  int EVENT_METADATA = 28;
  /** {@link #getDeviceInfo()} changed. */
  int EVENT_DEVICE_INFO_CHANGED = 29;
  /** {@link #getDeviceVolume()} changed. */
  int EVENT_DEVICE_VOLUME_CHANGED = 30;

  /**
   * Commands that can be executed on a {@code Player}. One of {@link #COMMAND_PLAY_PAUSE}, {@link
   * #COMMAND_PREPARE}, {@link #COMMAND_STOP}, {@link #COMMAND_SEEK_TO_DEFAULT_POSITION}, {@link
   * #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM}, {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM}, {@link
   * #COMMAND_SEEK_TO_PREVIOUS}, {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}, {@link
   * #COMMAND_SEEK_TO_NEXT}, {@link #COMMAND_SEEK_TO_MEDIA_ITEM}, {@link #COMMAND_SEEK_BACK}, {@link
   * #COMMAND_SEEK_FORWARD}, {@link #COMMAND_SET_SPEED_AND_PITCH}, {@link
   * #COMMAND_SET_SHUFFLE_MODE}, {@link #COMMAND_SET_REPEAT_MODE}, {@link
   * #COMMAND_GET_CURRENT_MEDIA_ITEM}, {@link #COMMAND_GET_TIMELINE}, {@link
   * #COMMAND_GET_MEDIA_ITEMS_METADATA}, {@link #COMMAND_SET_MEDIA_ITEMS_METADATA}, {@link
   * #COMMAND_CHANGE_MEDIA_ITEMS}, {@link #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link
   * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link
   * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link
   * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link
   * #COMMAND_SET_TRACK_SELECTION_PARAMETERS}, {@link #COMMAND_GET_TRACKS} or {@link
   * #COMMAND_SET_MEDIA_ITEM}.
   */
  // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
  // with Kotlin usages from before TYPE_USE was added.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
  @IntDef({
    COMMAND_INVALID,
    COMMAND_PLAY_PAUSE,
    COMMAND_PREPARE,
    COMMAND_STOP,
    COMMAND_SEEK_TO_DEFAULT_POSITION,
    COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
    COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
    COMMAND_SEEK_TO_PREVIOUS,
    COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
    COMMAND_SEEK_TO_NEXT,
    COMMAND_SEEK_TO_MEDIA_ITEM,
    COMMAND_SEEK_BACK,
    COMMAND_SEEK_FORWARD,
    COMMAND_SET_SPEED_AND_PITCH,
    COMMAND_SET_SHUFFLE_MODE,
    COMMAND_SET_REPEAT_MODE,
    COMMAND_GET_CURRENT_MEDIA_ITEM,
    COMMAND_GET_TIMELINE,
    COMMAND_GET_MEDIA_ITEMS_METADATA,
    COMMAND_SET_MEDIA_ITEMS_METADATA,
    COMMAND_SET_MEDIA_ITEM,
    COMMAND_CHANGE_MEDIA_ITEMS,
    COMMAND_GET_AUDIO_ATTRIBUTES,
    COMMAND_GET_VOLUME,
    COMMAND_GET_DEVICE_VOLUME,
    COMMAND_SET_VOLUME,
    COMMAND_SET_DEVICE_VOLUME,
    COMMAND_ADJUST_DEVICE_VOLUME,
    COMMAND_SET_VIDEO_SURFACE,
    COMMAND_GET_TEXT,
    COMMAND_SET_TRACK_SELECTION_PARAMETERS,
    COMMAND_GET_TRACKS,
  })
  @interface Command {}
  /** Command to start, pause or resume playback. */
  int COMMAND_PLAY_PAUSE = 1;
  /** Command to prepare the player. */
  int COMMAND_PREPARE = 2;
  /** Command to stop playback or release the player. */
  int COMMAND_STOP = 3;
  /** Command to seek to the default position of the current {@link MediaItem}. */
  int COMMAND_SEEK_TO_DEFAULT_POSITION = 4;
  /** Command to seek anywhere into the current {@link MediaItem}. */
  int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5;
  /**
   * @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead.
   */
  @UnstableApi @Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
  /** Command to seek to the default position of the previous {@link MediaItem}. */
  int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6;
  /**
   * @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead.
   */
  @UnstableApi @Deprecated
  int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
  /** Command to seek to an earlier position in the current or previous {@link MediaItem}. */
  int COMMAND_SEEK_TO_PREVIOUS = 7;
  /** Command to seek to the default position of the next {@link MediaItem}. */
  int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8;
  /**
   * @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead.
   */
  @UnstableApi @Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
  /** Command to seek to a later position in the current or next {@link MediaItem}. */
  int COMMAND_SEEK_TO_NEXT = 9;
  /** Command to seek anywhere in any {@link MediaItem}. */
  int COMMAND_SEEK_TO_MEDIA_ITEM = 10;
  /**
   * @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead.
   */
  @UnstableApi @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM;
  /** Command to seek back by a fixed increment into the current {@link MediaItem}. */
  int COMMAND_SEEK_BACK = 11;
  /** Command to seek forward by a fixed increment into the current {@link MediaItem}. */
  int COMMAND_SEEK_FORWARD = 12;
  /** Command to set the playback speed and pitch. */
  int COMMAND_SET_SPEED_AND_PITCH = 13;
  /** Command to enable shuffling. */
  int COMMAND_SET_SHUFFLE_MODE = 14;
  /** Command to set the repeat mode. */
  int COMMAND_SET_REPEAT_MODE = 15;
  /** Command to get the currently playing {@link MediaItem}. */
  int COMMAND_GET_CURRENT_MEDIA_ITEM = 16;
  /** Command to get the information about the current timeline. */
  int COMMAND_GET_TIMELINE = 17;
  /** Command to get the {@link MediaItem MediaItems} metadata. */
  int COMMAND_GET_MEDIA_ITEMS_METADATA = 18;
  /** Command to set the {@link MediaItem MediaItems} metadata. */
  int COMMAND_SET_MEDIA_ITEMS_METADATA = 19;
  /** Command to set a {@link MediaItem MediaItem}. */
  int COMMAND_SET_MEDIA_ITEM = 31;
  /** Command to change the {@link MediaItem MediaItems} in the playlist. */
  int COMMAND_CHANGE_MEDIA_ITEMS = 20;
  /** Command to get the player current {@link AudioAttributes}. */
  int COMMAND_GET_AUDIO_ATTRIBUTES = 21;
  /** Command to get the player volume. */
  int COMMAND_GET_VOLUME = 22;
  /** Command to get the device volume and whether it is muted. */
  int COMMAND_GET_DEVICE_VOLUME = 23;
  /** Command to set the player volume. */
  int COMMAND_SET_VOLUME = 24;
  /** Command to set the device volume and mute it. */
  int COMMAND_SET_DEVICE_VOLUME = 25;
  /** Command to increase and decrease the device volume and mute it. */
  int COMMAND_ADJUST_DEVICE_VOLUME = 26;
  /** Command to set and clear the surface on which to render the video. */
  int COMMAND_SET_VIDEO_SURFACE = 27;
  /** Command to get the text that should currently be displayed by the player. */
  int COMMAND_GET_TEXT = 28;
  /** Command to set the player's track selection parameters. */
  int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
  /** Command to get details of the current track selection. */
  int COMMAND_GET_TRACKS = 30;

  /** Represents an invalid {@link Command}. */
  int COMMAND_INVALID = -1;

  /**
   * Returns the {@link Looper} associated with the application thread that's used to access the
   * player and on which player events are received.
   */
  Looper getApplicationLooper();

  /**
   * Registers a listener to receive all events from the player.
   *
   * <p>The listener's methods will be called on the thread associated with {@link
   * #getApplicationLooper()}.
   *
   * @param listener The listener to register.
   */
  void addListener(Listener listener);

  /**
   * Unregister a listener registered through {@link #addListener(Listener)}. The listener will no
   * longer receive events.
   *
   * @param listener The listener to unregister.
   */
  void removeListener(Listener listener);

  /**
   * Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to
   * the default position.
   *
   * @param mediaItems The new {@link MediaItem MediaItems}.
   */
  void setMediaItems(List<MediaItem> mediaItems);

  /**
   * Clears the playlist and adds the specified {@link MediaItem MediaItems}.
   *
   * @param mediaItems The new {@link MediaItem MediaItems}.
   * @param resetPosition Whether the playback position should be reset to the default position in
   *     the first {@link Timeline.Window}. If false, playback will start from the position defined
   *     by {@link #getCurrentMediaItemIndex()} and {@link #getCurrentPosition()}.
   */
  void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition);

  /**
   * Clears the playlist and adds the specified {@link MediaItem MediaItems}.
   *
   * @param mediaItems The new {@link MediaItem MediaItems}.
   * @param startIndex The {@link MediaItem} index to start playback from. If {@link C#INDEX_UNSET}
   *     is passed, the current position is not reset.
   * @param startPositionMs The position in milliseconds to start playback from. If {@link
   *     C#TIME_UNSET} is passed, the default position of the given {@link MediaItem} is used. In
   *     any case, if {@code startIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored
   *     and the position is not reset at all.
   * @throws IllegalSeekPositionException If the provided {@code startIndex} is not within the
   *     bounds of the list of media items.
   */
  void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs);

  /**
   * Clears the playlist, adds the specified {@link MediaItem} and resets the position to the
   * default position.
   *
   * @param mediaItem The new {@link MediaItem}.
   */
  void setMediaItem(MediaItem mediaItem);

  /**
   * Clears the playlist and adds the specified {@link MediaItem}.
   *
   * @param mediaItem The new {@link MediaItem}.
   * @param startPositionMs The position in milliseconds to start playback from.
   */
  void setMediaItem(MediaItem mediaItem, long startPositionMs);

  /**
   * Clears the playlist and adds the specified {@link MediaItem}.
   *
   * @param mediaItem The new {@link MediaItem}.
   * @param resetPosition Whether the playback position should be reset to the default position. If
   *     false, playback will start from the position defined by {@link #getCurrentMediaItemIndex()}
   *     and {@link #getCurrentPosition()}.
   */
  void setMediaItem(MediaItem mediaItem, boolean resetPosition);

  /**
   * Adds a media item to the end of the playlist.
   *
   * @param mediaItem The {@link MediaItem} to add.
   */
  void addMediaItem(MediaItem mediaItem);

  /**
   * Adds a media item at the given index of the playlist.
   *
   * @param index The index at which to add the media item. If the index is larger than the size of
   *     the playlist, the media item is added to the end of the playlist.
   * @param mediaItem The {@link MediaItem} to add.
   */
  void addMediaItem(int index, MediaItem mediaItem);

  /**
   * Adds a list of media items to the end of the playlist.
   *
   * @param mediaItems The {@link MediaItem MediaItems} to add.
   */
  void addMediaItems(List<MediaItem> mediaItems);

  /**
   * Adds a list of media items at the given index of the playlist.
   *
   * @param index The index at which to add the media items. If the index is larger than the size of
   *     the playlist, the media items are added to the end of the playlist.
   * @param mediaItems The {@link MediaItem MediaItems} to add.
   */
  void addMediaItems(int index, List<MediaItem> mediaItems);

  /**
   * Moves the media item at the current index to the new index.
   *
   * @param currentIndex The current index of the media item to move.
   * @param newIndex The new index of the media item. If the new index is larger than the size of
   *     the playlist the item is moved to the end of the playlist.
   */
  void moveMediaItem(int currentIndex, int newIndex);

  /**
   * Moves the media item range to the new index.
   *
   * @param fromIndex The start of the range to move.
   * @param toIndex The first item not to be included in the range (exclusive).
   * @param newIndex The new index of the first media item of the range. If the new index is larger
   *     than the size of the remaining playlist after removing the range, the range is moved to the
   *     end of the playlist.
   */
  void moveMediaItems(int fromIndex, int toIndex, int newIndex);

  /**
   * Removes the media item at the given index of the playlist.
   *
   * @param index The index at which to remove the media item.
   */
  void removeMediaItem(int index);

  /**
   * Removes a range of media items from the playlist.
   *
   * @param fromIndex The index at which to start removing media items.
   * @param toIndex The index of the first item to be kept (exclusive). If the index is larger than
   *     the size of the playlist, media items to the end of the playlist are removed.
   */
  void removeMediaItems(int fromIndex, int toIndex);

  /** Clears the playlist. */
  void clearMediaItems();

  /**
   * Returns whether the provided {@link Command} is available.
   *
   * <p>This method does not execute the command.
   *
   * <p>Executing a command that is not available (for example, calling {@link
   * #seekToNextMediaItem()} if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will
   * neither throw an exception nor generate a {@link #getPlayerError()} player error}.
   *
   * <p>{@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}
   * are unavailable if there is no such {@link MediaItem}.
   *
   * @param command A {@link Command}.
   * @return Whether the {@link Command} is available.
   * @see Listener#onAvailableCommandsChanged(Commands)
   */
  boolean isCommandAvailable(@Command int command);

  /** Returns whether the player can be used to advertise a media session. */
  boolean canAdvertiseSession();

  /**
   * Returns the player's currently available {@link Commands}.
   *
   * <p>The returned {@link Commands} are not updated when available commands change. Use {@link
   * Listener#onAvailableCommandsChanged(Commands)} to get an update when the available commands
   * change.
   *
   * <p>Executing a command that is not available (for example, calling {@link
   * #seekToNextMediaItem()} if {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} is unavailable) will
   * neither throw an exception nor generate a {@link #getPlayerError()} player error}.
   *
   * <p>{@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} and {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM}
   * are unavailable if there is no such {@link MediaItem}.
   *
   * @return The currently available {@link Commands}.
   * @see Listener#onAvailableCommandsChanged
   */
  Commands getAvailableCommands();

  /**
   * Prepares the player.
   *
   * <p>This will move the player out of {@link #STATE_IDLE idle state} and the player will start
   * loading media and acquire resources needed for playback.
   */
  void prepare();

  /**
   * Returns the current {@link State playback state} of the player.
   *
   * @return The current {@link State playback state}.
   * @see Listener#onPlaybackStateChanged(int)
   */
  @State
  int getPlaybackState();

  /**
   * Returns the reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code
   * true}, or {@link #PLAYBACK_SUPPRESSION_REASON_NONE} if playback is not suppressed.
   *
   * @return The current {@link PlaybackSuppressionReason playback suppression reason}.
   * @see Listener#onPlaybackSuppressionReasonChanged(int)
   */
  @PlaybackSuppressionReason
  int getPlaybackSuppressionReason();

  /**
   * Returns whether the player is playing, i.e. {@link #getCurrentPosition()} is advancing.
   *
   * <p>If {@code false}, then at least one of the following is true:
   *
   * <ul>
   *   <li>The {@link #getPlaybackState() playback state} is not {@link #STATE_READY ready}.
   *   <li>There is no {@link #getPlayWhenReady() intention to play}.
   *   <li>Playback is {@link #getPlaybackSuppressionReason() suppressed for other reasons}.
   * </ul>
   *
   * @return Whether the player is playing.
   * @see Listener#onIsPlayingChanged(boolean)
   */
  boolean isPlaying();

  /**
   * Returns the error that caused playback to fail. This is the same error that will have been
   * reported via {@link Listener#onPlayerError(PlaybackException)} at the time of failure. It can
   * be queried using this method until the player is re-prepared.
   *
   * <p>Note that this method will always return {@code null} if {@link #getPlaybackState()} is not
   * {@link #STATE_IDLE}.
   *
   * @return The error, or {@code null}.
   * @see Listener#onPlayerError(PlaybackException)
   */
  @Nullable
  PlaybackException getPlayerError();

  /**
   * Resumes playback as soon as {@link #getPlaybackState()} == {@link #STATE_READY}. Equivalent to
   * {@code setPlayWhenReady(true)}.
   */
  void play();

  /** Pauses playback. Equivalent to {@code setPlayWhenReady(false)}. */
  void pause();

  /**
   * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
   *
   * <p>If the player is already in the ready state then this method pauses and resumes playback.
   *
   * @param playWhenReady Whether playback should proceed when ready.
   */
  void setPlayWhenReady(boolean playWhenReady);

  /**
   * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
   *
   * @return Whether playback will proceed when ready.
   * @see Listener#onPlayWhenReadyChanged(boolean, int)
   */
  boolean getPlayWhenReady();

  /**
   * Sets the {@link RepeatMode} to be used for playback.
   *
   * @param repeatMode The repeat mode.
   */
  void setRepeatMode(@RepeatMode int repeatMode);

  /**
   * Returns the current {@link RepeatMode} used for playback.
   *
   * @return The current repeat mode.
   * @see Listener#onRepeatModeChanged(int)
   */
  @RepeatMode
  int getRepeatMode();

  /**
   * Sets whether shuffling of media items is enabled.
   *
   * @param shuffleModeEnabled Whether shuffling is enabled.
   */
  void setShuffleModeEnabled(boolean shuffleModeEnabled);

  /**
   * Returns whether shuffling of media items is enabled.
   *
   * @see Listener#onShuffleModeEnabledChanged(boolean)
   */
  boolean getShuffleModeEnabled();

  /**
   * Whether the player is currently loading the source.
   *
   * @return Whether the player is currently loading the source.
   * @see Listener#onIsLoadingChanged(boolean)
   */
  boolean isLoading();

  /**
   * Seeks to the default position associated with the current {@link MediaItem}. The position can
   * depend on the type of media being played. For live streams it will typically be the live edge.
   * For other streams it will typically be the start.
   */
  void seekToDefaultPosition();

  /**
   * Seeks to the default position associated with the specified {@link MediaItem}. The position can
   * depend on the type of media being played. For live streams it will typically be the live edge.
   * For other streams it will typically be the start.
   *
   * @param mediaItemIndex The index of the {@link MediaItem} whose associated default position
   *     should be seeked to.
   * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided
   *     {@code mediaItemIndex} is not within the bounds of the current timeline.
   */
  void seekToDefaultPosition(int mediaItemIndex);

  /**
   * Seeks to a position specified in milliseconds in the current {@link MediaItem}.
   *
   * @param positionMs The seek position in the current {@link MediaItem}, or {@link C#TIME_UNSET}
   *     to seek to the media item's default position.
   */
  void seekTo(long positionMs);

  /**
   * Seeks to a position specified in milliseconds in the specified {@link MediaItem}.
   *
   * @param mediaItemIndex The index of the {@link MediaItem}.
   * @param positionMs The seek position in the specified {@link MediaItem}, or {@link C#TIME_UNSET}
   *     to seek to the media item's default position.
   * @throws IllegalSeekPositionException If the player has a non-empty timeline and the provided
   *     {@code mediaItemIndex} is not within the bounds of the current timeline.
   */
  void seekTo(int mediaItemIndex, long positionMs);

  /**
   * Returns the {@link #seekBack()} increment.
   *
   * @return The seek back increment, in milliseconds.
   * @see Listener#onSeekBackIncrementChanged(long)
   */
  long getSeekBackIncrement();

  /**
   * Seeks back in the current {@link MediaItem} by {@link #getSeekBackIncrement()} milliseconds.
   */
  void seekBack();

  /**
   * Returns the {@link #seekForward()} increment.
   *
   * @return The seek forward increment, in milliseconds.
   * @see Listener#onSeekForwardIncrementChanged(long)
   */
  long getSeekForwardIncrement();

  /**
   * Seeks forward in the current {@link MediaItem} by {@link #getSeekForwardIncrement()}
   * milliseconds.
   */
  void seekForward();

  /**
   * @deprecated Use {@link #hasPreviousMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean hasPrevious();

  /**
   * @deprecated Use {@link #hasPreviousMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean hasPreviousWindow();

  /**
   * Returns whether a previous media item exists, which may depend on the current repeat mode and
   * whether shuffle mode is enabled.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  boolean hasPreviousMediaItem();

  /**
   * @deprecated Use {@link #seekToPreviousMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  void previous();

  /**
   * @deprecated Use {@link #seekToPreviousMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  void seekToPreviousWindow();

  /**
   * Seeks to the default position of the previous {@link MediaItem}, which may depend on the
   * current repeat mode and whether shuffle mode is enabled. Does nothing if {@link
   * #hasPreviousMediaItem()} is {@code false}.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  void seekToPreviousMediaItem();

  /**
   * Returns the maximum position for which {@link #seekToPrevious()} seeks to the previous {@link
   * MediaItem}, in milliseconds.
   *
   * @return The maximum seek to previous position, in milliseconds.
   * @see Listener#onMaxSeekToPreviousPositionChanged(long)
   */
  long getMaxSeekToPreviousPosition();

  /**
   * Seeks to an earlier position in the current or previous {@link MediaItem} (if available). More
   * precisely:
   *
   * <ul>
   *   <li>If the timeline is empty or seeking is not possible, does nothing.
   *   <li>Otherwise, if the current {@link MediaItem} is {@link #isCurrentMediaItemLive()} live}
   *       and {@link #isCurrentMediaItemSeekable() unseekable}, then:
   *       <ul>
   *         <li>If {@link #hasPreviousMediaItem() a previous media item exists}, seeks to the
   *             default position of the previous media item.
   *         <li>Otherwise, does nothing.
   *       </ul>
   *   <li>Otherwise, if {@link #hasPreviousMediaItem() a previous media item exists} and the {@link
   *       #getCurrentPosition() current position} is less than {@link
   *       #getMaxSeekToPreviousPosition()}, seeks to the default position of the previous {@link
   *       MediaItem}.
   *   <li>Otherwise, seeks to 0 in the current {@link MediaItem}.
   * </ul>
   */
  void seekToPrevious();

  /**
   * @deprecated Use {@link #hasNextMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean hasNext();

  /**
   * @deprecated Use {@link #hasNextMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean hasNextWindow();

  /**
   * Returns whether a next {@link MediaItem} exists, which may depend on the current repeat mode
   * and whether shuffle mode is enabled.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  boolean hasNextMediaItem();

  /**
   * @deprecated Use {@link #seekToNextMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  void next();

  /**
   * @deprecated Use {@link #seekToNextMediaItem()} instead.
   */
  @UnstableApi
  @Deprecated
  void seekToNextWindow();

  /**
   * Seeks to the default position of the next {@link MediaItem}, which may depend on the current
   * repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasNextMediaItem()} is
   * {@code false}.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  void seekToNextMediaItem();

  /**
   * Seeks to a later position in the current or next {@link MediaItem} (if available). More
   * precisely:
   *
   * <ul>
   *   <li>If the timeline is empty or seeking is not possible, does nothing.
   *   <li>Otherwise, if {@link #hasNextMediaItem() a next media item exists}, seeks to the default
   *       position of the next {@link MediaItem}.
   *   <li>Otherwise, if the current {@link MediaItem} is {@link #isCurrentMediaItemLive() live} and
   *       has not ended, seeks to the live edge of the current {@link MediaItem}.
   *   <li>Otherwise, does nothing.
   * </ul>
   */
  void seekToNext();

  /**
   * Attempts to set the playback parameters. Passing {@link PlaybackParameters#DEFAULT} resets the
   * player to the default, which means there is no speed or pitch adjustment.
   *
   * <p>Playback parameters changes may cause the player to buffer. {@link
   * Listener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the currently
   * active playback parameters change.
   *
   * @param playbackParameters The playback parameters.
   */
  void setPlaybackParameters(PlaybackParameters playbackParameters);

  /**
   * Changes the rate at which playback occurs. The pitch is not changed.
   *
   * <p>This is equivalent to {@code
   * setPlaybackParameters(getPlaybackParameters().withSpeed(speed))}.
   *
   * @param speed The linear factor by which playback will be sped up. Must be higher than 0. 1 is
   *     normal speed, 2 is twice as fast, 0.5 is half normal speed...
   */
  void setPlaybackSpeed(@FloatRange(from = 0, fromInclusive = false) float speed);

  /**
   * Returns the currently active playback parameters.
   *
   * @see Listener#onPlaybackParametersChanged(PlaybackParameters)
   */
  PlaybackParameters getPlaybackParameters();

  /**
   * Stops playback without resetting the playlist. Use {@link #pause()} rather than this method if
   * the intention is to pause playback.
   *
   * <p>Calling this method will cause the playback state to transition to {@link #STATE_IDLE} and
   * the player will release the loaded media and resources required for playback. The player
   * instance can still be used by calling {@link #prepare()} again, and {@link #release()} must
   * still be called on the player if it's no longer required.
   *
   * <p>Calling this method does not clear the playlist, reset the playback position or the playback
   * error.
   */
  void stop();

  /**
   * @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
   *     just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
   *     {@link #prepare() re-preparing} the player.
   */
  @UnstableApi
  @Deprecated
  void stop(boolean reset);

  /**
   * Releases the player. This method must be called when the player is no longer required. The
   * player must not be used after calling this method.
   */
  void release();

  /**
   * Returns the current tracks.
   *
   * @see Listener#onTracksChanged(Tracks)
   */
  Tracks getCurrentTracks();

  /**
   * Returns the parameters constraining the track selection.
   *
   * @see Listener#onTrackSelectionParametersChanged}
   */
  TrackSelectionParameters getTrackSelectionParameters();

  /**
   * Sets the parameters constraining the track selection.
   *
   * <p>Unsupported parameters will be silently ignored.
   *
   * <p>Use {@link #getTrackSelectionParameters()} to retrieve the current parameters. For example,
   * the following snippet restricts video to SD whilst keep other track selection parameters
   * unchanged:
   *
   * <pre>{@code
   * player.setTrackSelectionParameters(
   *   player.getTrackSelectionParameters()
   *         .buildUpon()
   *         .setMaxVideoSizeSd()
   *         .build())
   * }</pre>
   */
  void setTrackSelectionParameters(TrackSelectionParameters parameters);

  /**
   * Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
   * supported.
   *
   * <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
   * metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
   * metadata that has been parsed from the media and output via {@link
   * Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
   * it will be prioritised above the same field coming from static or timed metadata.
   */
  MediaMetadata getMediaMetadata();

  /**
   * Returns the playlist {@link MediaMetadata}, as set by {@link
   * #setPlaylistMetadata(MediaMetadata)}, or {@link MediaMetadata#EMPTY} if not supported.
   */
  MediaMetadata getPlaylistMetadata();

  /** Sets the playlist {@link MediaMetadata}. */
  void setPlaylistMetadata(MediaMetadata mediaMetadata);

  /**
   * Returns the current manifest. The type depends on the type of media being played. May be null.
   */
  @UnstableApi
  @Nullable
  Object getCurrentManifest();

  /**
   * Returns the current {@link Timeline}. Never null, but may be empty.
   *
   * @see Listener#onTimelineChanged(Timeline, int)
   */
  Timeline getCurrentTimeline();

  /** Returns the index of the period currently being played. */
  int getCurrentPeriodIndex();

  /**
   * @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
   */
  @UnstableApi
  @Deprecated
  int getCurrentWindowIndex();

  /**
   * Returns the index of the current {@link MediaItem} in the {@link #getCurrentTimeline()
   * timeline}, or the prospective index if the {@link #getCurrentTimeline() current timeline} is
   * empty.
   */
  int getCurrentMediaItemIndex();

  /**
   * @deprecated Use {@link #getNextMediaItemIndex()} instead.
   */
  @UnstableApi
  @Deprecated
  int getNextWindowIndex();

  /**
   * Returns the index of the {@link MediaItem} that will be played if {@link
   * #seekToNextMediaItem()} is called, which may depend on the current repeat mode and whether
   * shuffle mode is enabled. Returns {@link C#INDEX_UNSET} if {@link #hasNextMediaItem()} is {@code
   * false}.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  int getNextMediaItemIndex();

  /**
   * @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
   */
  @UnstableApi
  @Deprecated
  int getPreviousWindowIndex();

  /**
   * Returns the index of the {@link MediaItem} that will be played if {@link
   * #seekToPreviousMediaItem()} is called, which may depend on the current repeat mode and whether
   * shuffle mode is enabled. Returns {@link C#INDEX_UNSET} if {@link #hasPreviousMediaItem()} is
   * {@code false}.
   *
   * <p>Note: When the repeat mode is {@link #REPEAT_MODE_ONE}, this method behaves the same as when
   * the current repeat mode is {@link #REPEAT_MODE_OFF}. See {@link #REPEAT_MODE_ONE} for more
   * details.
   */
  int getPreviousMediaItemIndex();

  /**
   * Returns the currently playing {@link MediaItem}. May be null if the timeline is empty.
   *
   * @see Listener#onMediaItemTransition(MediaItem, int)
   */
  @Nullable
  MediaItem getCurrentMediaItem();

  /** Returns the number of {@link MediaItem media items} in the playlist. */
  int getMediaItemCount();

  /** Returns the {@link MediaItem} at the given index. */
  MediaItem getMediaItemAt(int index);

  /**
   * Returns the duration of the current content or ad in milliseconds, or {@link C#TIME_UNSET} if
   * the duration is not known.
   */
  long getDuration();

  /**
   * Returns the playback position in the current content or ad, in milliseconds, or the prospective
   * position in milliseconds if the {@link #getCurrentTimeline() current timeline} is empty.
   */
  long getCurrentPosition();

  /**
   * Returns an estimate of the position in the current content or ad up to which data is buffered,
   * in milliseconds.
   */
  long getBufferedPosition();

  /**
   * Returns an estimate of the percentage in the current content or ad up to which data is
   * buffered, or 0 if no estimate is available.
   */
  @IntRange(from = 0, to = 100)
  int getBufferedPercentage();

  /**
   * Returns an estimate of the total buffered duration from the current position, in milliseconds.
   * This includes pre-buffered data for subsequent ads and {@link MediaItem media items}.
   */
  long getTotalBufferedDuration();

  /**
   * @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean isCurrentWindowDynamic();

  /**
   * Returns whether the current {@link MediaItem} is dynamic (may change when the {@link Timeline}
   * is updated), or {@code false} if the {@link Timeline} is empty.
   *
   * @see Timeline.Window#isDynamic
   */
  boolean isCurrentMediaItemDynamic();

  /**
   * @deprecated Use {@link #isCurrentMediaItemLive()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean isCurrentWindowLive();

  /**
   * Returns whether the current {@link MediaItem} is live, or {@code false} if the {@link Timeline}
   * is empty.
   *
   * @see Timeline.Window#isLive()
   */
  boolean isCurrentMediaItemLive();

  /**
   * Returns the offset of the current playback position from the live edge in milliseconds, or
   * {@link C#TIME_UNSET} if the current {@link MediaItem} {@link #isCurrentMediaItemLive()} isn't
   * live} or the offset is unknown.
   *
   * <p>The offset is calculated as {@code currentTime - playbackPosition}, so should usually be
   * positive.
   *
   * <p>Note that this offset may rely on an accurate local time, so this method may return an
   * incorrect value if the difference between system clock and server clock is unknown.
   */
  long getCurrentLiveOffset();

  /**
   * @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
   */
  @UnstableApi
  @Deprecated
  boolean isCurrentWindowSeekable();

  /**
   * Returns whether the current {@link MediaItem} is seekable, or {@code false} if the {@link
   * Timeline} is empty.
   *
   * @see Timeline.Window#isSeekable
   */
  boolean isCurrentMediaItemSeekable();

  /** Returns whether the player is currently playing an ad. */
  boolean isPlayingAd();

  /**
   * If {@link #isPlayingAd()} returns true, returns the index of the ad group in the period
   * currently being played. Returns {@link C#INDEX_UNSET} otherwise.
   */
  int getCurrentAdGroupIndex();

  /**
   * If {@link #isPlayingAd()} returns true, returns the index of the ad in its ad group. Returns
   * {@link C#INDEX_UNSET} otherwise.
   */
  int getCurrentAdIndexInAdGroup();

  /**
   * If {@link #isPlayingAd()} returns {@code true}, returns the duration of the current content in
   * milliseconds, or {@link C#TIME_UNSET} if the duration is not known. If there is no ad playing,
   * the returned duration is the same as that returned by {@link #getDuration()}.
   */
  long getContentDuration();

  /**
   * If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be
   * played once all ads in the ad group have finished playing, in milliseconds. If there is no ad
   * playing, the returned position is the same as that returned by {@link #getCurrentPosition()}.
   */
  long getContentPosition();

  /**
   * If {@link #isPlayingAd()} returns {@code true}, returns an estimate of the content position in
   * the current content up to which data is buffered, in milliseconds. If there is no ad playing,
   * the returned position is the same as that returned by {@link #getBufferedPosition()}.
   */
  long getContentBufferedPosition();

  /** Returns the attributes for audio playback. */
  AudioAttributes getAudioAttributes();

  /**
   * Sets the audio volume, valid values are between 0 (silence) and 1 (unity gain, signal
   * unchanged), inclusive.
   *
   * @param volume Linear output gain to apply to all audio channels.
   */
  void setVolume(@FloatRange(from = 0, to = 1.0) float volume);

  /**
   * Returns the audio volume, with 0 being silence and 1 being unity gain (signal unchanged).
   *
   * @return The linear gain applied to all audio channels.
   */
  @FloatRange(from = 0, to = 1.0)
  float getVolume();

  /**
   * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
   * currently set on the player.
   */
  void clearVideoSurface();

  /**
   * Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
   * Else does nothing.
   *
   * @param surface The surface to clear.
   */
  void clearVideoSurface(@Nullable Surface surface);

  /**
   * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for
   * tracking the lifecycle of the surface, and must clear the surface by calling {@code
   * setVideoSurface(null)} if the surface is destroyed.
   *
   * <p>If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link
   * SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link
   * #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather than
   * this method, since passing the holder allows the player to track the lifecycle of the surface
   * automatically.
   *
   * @param surface The {@link Surface}.
   */
  void setVideoSurface(@Nullable Surface surface);

  /**
   * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
   * rendered. The player will track the lifecycle of the surface automatically.
   *
   * <p>The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread
   * associated with {@link #getApplicationLooper()}.
   *
   * @param surfaceHolder The surface holder.
   */
  void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);

  /**
   * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
   * rendered if it matches the one passed. Else does nothing.
   *
   * @param surfaceHolder The surface holder to clear.
   */
  void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);

  /**
   * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
   * lifecycle of the surface automatically.
   *
   * <p>The thread that calls the {@link SurfaceHolder.Callback} methods must be the thread
   * associated with {@link #getApplicationLooper()}.
   *
   * @param surfaceView The surface view.
   */
  void setVideoSurfaceView(@Nullable SurfaceView surfaceView);

  /**
   * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed.
   * Else does nothing.
   *
   * @param surfaceView The texture view to clear.
   */
  void clearVideoSurfaceView(@Nullable SurfaceView surfaceView);

  /**
   * Sets the {@link TextureView} onto which video will be rendered. The player will track the
   * lifecycle of the surface automatically.
   *
   * <p>The thread that calls the {@link TextureView.SurfaceTextureListener} methods must be the
   * thread associated with {@link #getApplicationLooper()}.
   *
   * @param textureView The texture view.
   */
  void setVideoTextureView(@Nullable TextureView textureView);

  /**
   * Clears the {@link TextureView} onto which video is being rendered if it matches the one passed.
   * Else does nothing.
   *
   * @param textureView The texture view to clear.
   */
  void clearVideoTextureView(@Nullable TextureView textureView);

  /**
   * Gets the size of the video.
   *
   * <p>The video's width and height are {@code 0} if there is no video or its size has not been
   * determined yet.
   *
   * @see Listener#onVideoSizeChanged(VideoSize)
   */
  VideoSize getVideoSize();

  /** Returns the current {@link CueGroup}. */
  CueGroup getCurrentCues();

  /** Gets the device information. */
  DeviceInfo getDeviceInfo();

  /**
   * Gets the current volume of the device.
   *
   * <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume returned
   * by this method varies according to the current {@link C.StreamType stream type}. The stream
   * type is determined by {@link AudioAttributes#usage} which can be converted to stream type with
   * {@link Util#getStreamTypeForAudioUsage(int)}.
   *
   * <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the
   * remote device is returned.
   */
  @IntRange(from = 0)
  int getDeviceVolume();

  /** Gets whether the device is muted or not. */
  boolean isDeviceMuted();

  /**
   * Sets the volume of the device.
   *
   * @param volume The volume to set.
   */
  void setDeviceVolume(@IntRange(from = 0) int volume);

  /** Increases the volume of the device. */
  void increaseDeviceVolume();

  /** Decreases the volume of the device. */
  void decreaseDeviceVolume();

  /** Sets the mute state of the device. */
  void setDeviceMuted(boolean muted);
}
