public abstract class

SessionPlayer

extends java.lang.Object

implements java.io.Closeable

 java.lang.Object

↳androidx.media2.common.SessionPlayer

Subclasses:

RemoteSessionPlayer, MediaPlayer

Gradle dependencies

compile group: 'androidx.media2', name: 'media2-common', version: '1.2.1'

  • groupId: androidx.media2
  • artifactId: media2-common
  • version: 1.2.1

Artifact androidx.media2:media2-common:1.2.1 it located at Google repository (https://maven.google.com/)

Overview

Base interface for all media players that want media session.

APIs that return should be the asynchronous calls and shouldn't block the calling thread. This guarantees the APIs are safe to be called on the main thread.

Topics covered here are:

  1. Best practices
  2. Player states
  3. Invalid method calls

Best practices

Here are best practices when implementing/using SessionPlayer:
  • When updating UI, you should respond to SessionPlayer.PlayerCallback invocations instead of SessionPlayer.PlayerResult objects since the player can be controlled by others.
  • When a SessionPlayer object is no longer being used, call SessionPlayer.close() as soon as possible to release the resources used by the internal player engine associated with the SessionPlayer. For example, if a player uses hardware decoder, other player instances may fallback to software decoders or fail to play. You cannot use SessionPlayer instance after you call SessionPlayer.close(). There is no way to reuse the instance.
  • The current playback position can be retrieved with a call to SessionPlayer.getCurrentPosition(), which is helpful for applications such as a music player that need to keep track of the playback progress.
  • The playback position can be adjusted with a call to SessionPlayer.seekTo(long). Although the asynchronous SessionPlayer.seekTo(long) call returns right away, the actual seek operation may take a while to finish, especially for audio/video being streamed.
  • You can call SessionPlayer.seekTo(long) from the SessionPlayer.PLAYER_STATE_PAUSED. In these cases, if you are playing a video stream and the requested position is valid, one video frame may be displayed.

Player states

The playback control of audio/video files is managed as a state machine. The SessionPlayer defines four states:
  1. SessionPlayer.PLAYER_STATE_IDLE: Initial state after the instantiation.

    While in this state, you should call SessionPlayer.setMediaItem(MediaItem) or SessionPlayer.setPlaylist(List, MediaMetadata). Check returned for potential error.

    Calling SessionPlayer.prepare() transfers this object to SessionPlayer.PLAYER_STATE_PAUSED.

  2. SessionPlayer.PLAYER_STATE_PAUSED: State when the audio/video playback is paused.

    Call SessionPlayer.play() to resume or start playback from the position where it paused.

  3. SessionPlayer.PLAYER_STATE_PLAYING: State when the player plays the media item.

    In this state, SessionPlayer.PlayerCallback.onBufferingStateChanged(SessionPlayer, MediaItem, int) will be called regularly to tell the buffering status.

    Playback state would remain SessionPlayer.PLAYER_STATE_PLAYING when the currently playing media item is changed.

    When the playback reaches the end of stream, the behavior depends on repeat mode, set by SessionPlayer.setRepeatMode(int). If the repeat mode was set to SessionPlayer.REPEAT_MODE_NONE, the player will transfer to the SessionPlayer.PLAYER_STATE_PAUSED. Otherwise, the SessionPlayer object remains in the SessionPlayer.PLAYER_STATE_PLAYING and playback will be ongoing.

  4. SessionPlayer.PLAYER_STATE_ERROR: State when the playback failed and player cannot be recovered by itself.

    In general, playback might fail due to various reasons such as unsupported audio/video format, poorly interleaved audio/video, resolution too high, streaming timeout, and others. In addition, due to programming errors, a playback control operation might be performed from an invalid state. In these cases the player may transition to this state.

Subclasses may have extra methods to reset the player state to SessionPlayer.PLAYER_STATE_IDLE from other states. Take a look at documentations of specific subclass that you're interested in.

Invalid method calls

The only method you safely call from the SessionPlayer.PLAYER_STATE_ERROR is SessionPlayer.close(). Any other methods might return meaningless data.

Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error state and/or provide a method to recover from the error state. Take a look at documentations of specific subclass that you're interested in.

Most methods can be called from any non-Error state. In case they're called in invalid state, the implementation should ignore and would return SessionPlayer.PlayerResult with BaseResult.RESULT_ERROR_INVALID_STATE. The following table lists the methods that aren't guaranteed to successfully running if they're called from the associated invalid states.

Method Name Invalid States
setAudioAttributes {Paused, Playing}
prepare {Paused, Playing}
play {Idle}
pause {Idle}
seekTo {Idle}

Summary

Fields
public static final intBUFFERING_STATE_BUFFERING_AND_PLAYABLE

Buffering state indicating the player is buffering but enough has been buffered for this player to be able to play the content.

public static final intBUFFERING_STATE_BUFFERING_AND_STARVED

Buffering state indicating the player is buffering, but the player is currently starved for data, and cannot play.

public static final intBUFFERING_STATE_COMPLETE

Buffering state indicating the player is done buffering, and the remainder of the content is available for playback.

public static final intBUFFERING_STATE_UNKNOWN

Buffering state is unknown.

public static final intINVALID_ITEM_INDEX

Media item index is invalid.

public static final intPLAYER_STATE_ERROR

State when the player is in error state and cannot be recovered self.

public static final intPLAYER_STATE_IDLE

State when the player is idle, and needs configuration to start playback.

public static final intPLAYER_STATE_PAUSED

State when the player's playback is paused

public static final intPLAYER_STATE_PLAYING

State when the player's playback is ongoing

public static final intREPEAT_MODE_ALL

Playing media list will be repeated.

public static final intREPEAT_MODE_GROUP

Playback of the playing media group will be repeated.

public static final intREPEAT_MODE_NONE

Playback will be stopped at the end of the playing media list.

public static final intREPEAT_MODE_ONE

Playback of the current playing media item will be repeated.

public static final intSHUFFLE_MODE_ALL

Media list will be played in shuffled order.

public static final intSHUFFLE_MODE_GROUP

Media group will be played in shuffled order.

public static final intSHUFFLE_MODE_NONE

Media list will be played in order.

public static final longUNKNOWN_TIME

Value indicating the time is unknown

Constructors
publicSessionPlayer()

Methods
public abstract <any>addPlaylistItem(int index, MediaItem item)

Adds the media item to the playlist at the index.

public voidclose()

Removes all existing references to callbacks and executors.

public <any>deselectTrack(SessionPlayer.TrackInfo trackInfo)

Deselects the SessionPlayer.TrackInfo for the current media item.

public abstract AudioAttributesCompatgetAudioAttributes()

Gets the AudioAttributesCompat that media player has.

public abstract longgetBufferedPosition()

Gets the position for how much has been buffered, or SessionPlayer.UNKNOWN_TIME if unknown.

public abstract intgetBufferingState()

Returns the current buffering state of the player.

protected final java.util.List<Pair>getCallbacks()

Gets the callbacks with executors for subclasses to notify player events.

public abstract MediaItemgetCurrentMediaItem()

Gets the current media item, which is currently playing or would be played with later SessionPlayer.play().

public abstract intgetCurrentMediaItemIndex()

Gets the index of current media item in playlist.

public abstract longgetCurrentPosition()

Gets the current playback position.

public abstract longgetDuration()

Gets the duration of the current media item, or SessionPlayer.UNKNOWN_TIME if unknown.

public abstract intgetNextMediaItemIndex()

Gets the next item index in the playlist.

public abstract floatgetPlaybackSpeed()

Gets the actual playback speed to be used by the player when playing.

public abstract intgetPlayerState()

Gets the current player state.

public abstract java.util.List<MediaItem>getPlaylist()

Gets the playlist.

public abstract MediaMetadatagetPlaylistMetadata()

Gets the playlist metadata.

public abstract intgetPreviousMediaItemIndex()

Gets the previous item index in the playlist.

public abstract intgetRepeatMode()

Gets the repeat mode.

public SessionPlayer.TrackInfogetSelectedTrack(int trackType)

Gets currently selected track's SessionPlayer.TrackInfo for the given track type.

public abstract intgetShuffleMode()

Gets the shuffle mode.

public java.util.List<SessionPlayer.TrackInfo>getTracks()

Gets the full list of selected and unselected tracks that the media contains.

public VideoSizegetVideoSize()

Gets the size of the video.

public <any>movePlaylistItem(int fromIndex, int toIndex)

Moves the media item at fromIdx to toIdx in the playlist.

public abstract <any>pause()

Pauses playback.

public abstract <any>play()

Starts or resumes playback.

public abstract <any>prepare()

Prepares the media items for playback.

public final voidregisterPlayerCallback(java.util.concurrent.Executor executor, SessionPlayer.PlayerCallback callback)

Register SessionPlayer.PlayerCallback to listen changes.

public abstract <any>removePlaylistItem(int index)

Removes the media item from the playlist

public abstract <any>replacePlaylistItem(int index, MediaItem item)

Replaces the media item at index in the playlist.

public abstract <any>seekTo(long position)

Seeks to the specified position.

public <any>selectTrack(SessionPlayer.TrackInfo trackInfo)

Selects the SessionPlayer.TrackInfo for the current media item.

public abstract <any>setAudioAttributes(AudioAttributesCompat attributes)

Sets the AudioAttributesCompat to be used during the playback of the media.

public abstract <any>setMediaItem(MediaItem item)

Sets a MediaItem for playback.

public abstract <any>setPlaybackSpeed(float playbackSpeed)

Sets the playback speed.

public abstract <any>setPlaylist(java.util.List<MediaItem> list, MediaMetadata metadata)

Sets a list of MediaItem with metadata.

public abstract <any>setRepeatMode(int repeatMode)

Sets the repeat mode.

public abstract <any>setShuffleMode(int shuffleMode)

Sets the shuffle mode.

public <any>setSurface(Surface surface)

Sets the to be used as the sink for the video portion of the media.

public abstract <any>skipToNextPlaylistItem()

Skips to the next item in the playlist.

public abstract <any>skipToPlaylistItem(int index)

Skips to the item in the playlist at the index.

public abstract <any>skipToPreviousPlaylistItem()

Skips to the previous item in the playlist.

public final voidunregisterPlayerCallback(SessionPlayer.PlayerCallback callback)

Unregister the previously registered SessionPlayer.PlayerCallback.

public abstract <any>updatePlaylistMetadata(MediaMetadata metadata)

Updates the playlist metadata while keeping the playlist as-is.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int PLAYER_STATE_IDLE

State when the player is idle, and needs configuration to start playback.

public static final int PLAYER_STATE_PAUSED

State when the player's playback is paused

public static final int PLAYER_STATE_PLAYING

State when the player's playback is ongoing

public static final int PLAYER_STATE_ERROR

State when the player is in error state and cannot be recovered self.

public static final int BUFFERING_STATE_UNKNOWN

Buffering state is unknown.

public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE

Buffering state indicating the player is buffering but enough has been buffered for this player to be able to play the content. See SessionPlayer.getBufferedPosition() for how far is buffered already.

public static final int BUFFERING_STATE_BUFFERING_AND_STARVED

Buffering state indicating the player is buffering, but the player is currently starved for data, and cannot play.

public static final int BUFFERING_STATE_COMPLETE

Buffering state indicating the player is done buffering, and the remainder of the content is available for playback.

public static final int REPEAT_MODE_NONE

Playback will be stopped at the end of the playing media list.

public static final int REPEAT_MODE_ONE

Playback of the current playing media item will be repeated.

public static final int REPEAT_MODE_ALL

Playing media list will be repeated.

public static final int REPEAT_MODE_GROUP

Playback of the playing media group will be repeated. A group is a logical block of media items which is specified in the section 5.7 of the Bluetooth AVRCP 1.6. An example of a group is the playlist.

public static final int SHUFFLE_MODE_NONE

Media list will be played in order.

public static final int SHUFFLE_MODE_ALL

Media list will be played in shuffled order.

public static final int SHUFFLE_MODE_GROUP

Media group will be played in shuffled order. A group is a logical block of media items which is specified in the section 5.7 of the Bluetooth AVRCP 1.6. An example of a group is the playlist.

public static final long UNKNOWN_TIME

Value indicating the time is unknown

public static final int INVALID_ITEM_INDEX

Media item index is invalid. This value will be returned when the corresponding media item does not exist.

Constructors

public SessionPlayer()

Methods

public abstract <any> play()

Starts or resumes playback.

On success, this transfers the player state to SessionPlayer.PLAYER_STATE_PLAYING and a SessionPlayer.PlayerResult should be returned with the current media item when the command was completed. If it is called in SessionPlayer.PLAYER_STATE_IDLE or SessionPlayer.PLAYER_STATE_ERROR, it should be ignored and a SessionPlayer.PlayerResult should be returned with BaseResult.RESULT_ERROR_INVALID_STATE.

Returns:

a representing the pending completion of the command

public abstract <any> pause()

Pauses playback.

On success, this transfers the player state to SessionPlayer.PLAYER_STATE_PAUSED and a SessionPlayer.PlayerResult should be returned with the current media item when the command was completed. If it is called in SessionPlayer.PLAYER_STATE_IDLE or SessionPlayer.PLAYER_STATE_ERROR, it should be ignored and a SessionPlayer.PlayerResult should be returned with BaseResult.RESULT_ERROR_INVALID_STATE.

Returns:

a representing the pending completion of the command

public abstract <any> prepare()

Prepares the media items for playback. During this time, the player may allocate resources required to play, such as audio and video decoders. Before calling this API, set media item(s) through either SessionPlayer.setMediaItem(MediaItem) or SessionPlayer.setPlaylist(List, MediaMetadata).

On success, this transfers the player state from SessionPlayer.PLAYER_STATE_IDLE to SessionPlayer.PLAYER_STATE_PAUSED and a SessionPlayer.PlayerResult should be returned with the prepared media item when the command completed. If it's not called in SessionPlayer.PLAYER_STATE_IDLE, it should be ignored and SessionPlayer.PlayerResult should be returned with BaseResult.RESULT_ERROR_INVALID_STATE.

Returns:

a representing the pending completion of the command

public abstract <any> seekTo(long position)

Seeks to the specified position.

The position is the relative position based on the MediaItem.getStartPosition(). So calling SessionPlayer.seekTo(long) with 0 means the seek to the start position.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed. If it's called in SessionPlayer.PLAYER_STATE_IDLE, it is ignored and a SessionPlayer.PlayerResult should be returned with BaseResult.RESULT_ERROR_INVALID_STATE.

Parameters:

position: the new playback position in ms. The value should be in the range of start and end positions defined in MediaItem.

Returns:

a representing the pending completion of the command

public abstract <any> setPlaybackSpeed(float playbackSpeed)

Sets the playback speed. The default playback speed is 1.0f, and negative values indicate reverse playback and 0.0f is not allowed.

The supported playback speed range depends on the underlying player implementation, so it is recommended to query the actual speed of the player via SessionPlayer.getPlaybackSpeed() after the operation completes. In particular, please note that player implementations may not support reverse playback.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

playbackSpeed: the requested playback speed

Returns:

a representing the pending completion of the command

See also: SessionPlayer.getPlaybackSpeed(), SessionPlayer.PlayerCallback.onPlaybackSpeedChanged(SessionPlayer, float)

public abstract <any> setAudioAttributes(AudioAttributesCompat attributes)

Sets the AudioAttributesCompat to be used during the playback of the media.

You must call this method in SessionPlayer.PLAYER_STATE_IDLE in order for the audio attributes to become effective thereafter. Otherwise, the call would be ignored and SessionPlayer.PlayerResult should be returned with BaseResult.RESULT_ERROR_INVALID_STATE.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

attributes: non-null AudioAttributes.

public abstract int getPlayerState()

Gets the current player state.

Returns:

the current player state

See also: SessionPlayer.PlayerCallback.onPlayerStateChanged(SessionPlayer, int), SessionPlayer.PLAYER_STATE_IDLE, SessionPlayer.PLAYER_STATE_PAUSED, SessionPlayer.PLAYER_STATE_PLAYING, SessionPlayer.PLAYER_STATE_ERROR

public abstract long getCurrentPosition()

Gets the current playback position.

The position is the relative position based on the MediaItem.getStartPosition(). So the position 0 means the start position of the MediaItem.

Returns:

the current playback position in ms, or SessionPlayer.UNKNOWN_TIME if unknown

public abstract long getDuration()

Gets the duration of the current media item, or SessionPlayer.UNKNOWN_TIME if unknown. If the current MediaItem has either start or end position, then duration would be adjusted accordingly instead of returning the whole size of the MediaItem.

Returns:

the duration in ms, or SessionPlayer.UNKNOWN_TIME if unknown

public abstract long getBufferedPosition()

Gets the position for how much has been buffered, or SessionPlayer.UNKNOWN_TIME if unknown.

The position is the relative position based on the MediaItem.getStartPosition(). So the position 0 means the start position of the MediaItem.

Returns:

the buffered position in ms, or SessionPlayer.UNKNOWN_TIME if unknown

public abstract int getBufferingState()

Returns the current buffering state of the player.

During the buffering, see SessionPlayer.getBufferedPosition() for the quantifying the amount already buffered.

Returns:

the buffering state, or SessionPlayer.BUFFERING_STATE_UNKNOWN if unknown

See also: SessionPlayer.getBufferedPosition()

public abstract float getPlaybackSpeed()

Gets the actual playback speed to be used by the player when playing. A value of 1.0f is the default playback value, and a negative value indicates reverse playback.

Note that it may differ from the speed set in SessionPlayer.setPlaybackSpeed(float).

Returns:

the actual playback speed

public VideoSize getVideoSize()

Gets the size of the video.

Returns:

the size of the video. The width and height of size could be 0 if there is no video or the size has not been determined yet.

See also: SessionPlayer.PlayerCallback.onVideoSizeChanged(SessionPlayer, VideoSize)

public <any> setSurface(Surface surface)

Sets the to be used as the sink for the video portion of the media.

A null surface will reset any Surface and result in only the audio track being played.

On success, a SessionPlayer.PlayerResult is returned with the current media item when the command completed.

Parameters:

surface: the to be used for the video portion of the media

Returns:

a which represents the pending completion of the command

public abstract <any> setPlaylist(java.util.List<MediaItem> list, MediaMetadata metadata)

Sets a list of MediaItem with metadata. Use this or SessionPlayer.setMediaItem(MediaItem) to specify which items to play.

This can be called multiple times in any states other than SessionPlayer.PLAYER_STATE_ERROR. This would override previous SessionPlayer.setMediaItem(MediaItem) or SessionPlayer.setPlaylist(List, MediaMetadata) calls.

Ensure uniqueness of each MediaItem in the playlist so the session can uniquely identity individual items. All MediaItems shouldn't be null as well.

It's recommended to fill MediaMetadata in each MediaItem especially for the duration information with the key MediaMetadata.METADATA_KEY_DURATION. Without the duration information in the metadata, session will do extra work to get the duration and send it to the controller.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) and SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's completed. The current media item would be the first item in the playlist.

The implementation must close the in the FileMediaItem when a media item in the playlist is a FileMediaItem.

On success, a SessionPlayer.PlayerResult should be returned with the first media item of the playlist when the command completed.

Parameters:

list: a list of MediaItem objects to set as a play list

Returns:

a which represents the pending completion of the command

See also: SessionPlayer.setMediaItem(MediaItem), SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata), SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)

public abstract AudioAttributesCompat getAudioAttributes()

Gets the AudioAttributesCompat that media player has.

public abstract <any> setMediaItem(MediaItem item)

Sets a MediaItem for playback. Use this or SessionPlayer.setPlaylist(List, MediaMetadata) to specify which items to play. If you want to change current item in the playlist, use one of SessionPlayer.skipToPlaylistItem(int), SessionPlayer.skipToNextPlaylistItem(), or SessionPlayer.skipToPreviousPlaylistItem() instead of this method.

This can be called multiple times in any states other than SessionPlayer.PLAYER_STATE_ERROR. This would override previous SessionPlayer.setMediaItem(MediaItem) or SessionPlayer.setPlaylist(List, MediaMetadata) calls.

It's recommended to fill MediaMetadata in MediaItem especially for the duration information with the key MediaMetadata.METADATA_KEY_DURATION. Without the duration information in the metadata, session will do extra work to get the duration and send it to the controller.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) and SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's completed. The current item would be the item given here.

The implementation must close the in the FileMediaItem if the given media item is a FileMediaItem.

On success, a SessionPlayer.PlayerResult should be returned with item set.

Parameters:

item: the descriptor of media item you want to play

Returns:

a which represents the pending completion of the command

See also: SessionPlayer.setPlaylist(List, MediaMetadata), SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata), SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)

public abstract <any> addPlaylistItem(int index, MediaItem item)

Adds the media item to the playlist at the index. Index equals to or greater than the current playlist size (e.g. MAX_VALUE) will add the item at the end of the playlist.

If index is less than or equal to the current index of the playlist, the current index of the playlist should be increased correspondingly.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's completed.

The implementation must close the in the FileMediaItem if the given media item is a FileMediaItem.

On success, a SessionPlayer.PlayerResult should be returned with item added.

Parameters:

index: the index of the item you want to add in the playlist
item: the media item you want to add

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)

public abstract <any> removePlaylistItem(int index)

Removes the media item from the playlist

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with item removed.

If the last item is removed, the player should be moved to SessionPlayer.PLAYER_STATE_IDLE.

Parameters:

index: the index of the item you want to remove in the playlist

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)

public abstract <any> replacePlaylistItem(int index, MediaItem item)

Replaces the media item at index in the playlist. This can be also used to update metadata of an item.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's completed.

The implementation must close the in the FileMediaItem if the given media item is a FileMediaItem.

On success, a SessionPlayer.PlayerResult should be returned with item set.

Parameters:

index: the index of the item to replace in the playlist
item: the new item

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)

public <any> movePlaylistItem(int fromIndex, int toIndex)

Moves the media item at fromIdx to toIdx in the playlist.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with item set.

Parameters:

fromIndex: the media item's initial index in the playlist
toIndex: the media item's target index in the playlist

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)

public abstract <any> skipToPreviousPlaylistItem()

Skips to the previous item in the playlist.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Returns:

a representing the pending completion of the command

See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)

public abstract <any> skipToNextPlaylistItem()

Skips to the next item in the playlist.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Returns:

a representing the pending completion of the command

See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)

public abstract <any> skipToPlaylistItem(int index)

Skips to the item in the playlist at the index.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

index: the index of the item you want to play in the playlist

See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)

public abstract <any> updatePlaylistMetadata(MediaMetadata metadata)

Updates the playlist metadata while keeping the playlist as-is.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

metadata: metadata of the playlist

See also: SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)

public abstract <any> setRepeatMode(int repeatMode)

Sets the repeat mode.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

repeatMode: repeat mode

See also: SessionPlayer.REPEAT_MODE_NONE, SessionPlayer.REPEAT_MODE_ONE, SessionPlayer.REPEAT_MODE_ALL, SessionPlayer.REPEAT_MODE_GROUP, SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int)

public abstract <any> setShuffleMode(int shuffleMode)

Sets the shuffle mode.

The implementation must notify registered callbacks with SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int) when it's completed.

On success, a SessionPlayer.PlayerResult should be returned with the current media item when the command completed.

Parameters:

shuffleMode: the shuffle mode

Returns:

a representing the pending completion of the command

See also: SessionPlayer.SHUFFLE_MODE_NONE, SessionPlayer.SHUFFLE_MODE_ALL, SessionPlayer.SHUFFLE_MODE_GROUP, SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int)

public abstract java.util.List<MediaItem> getPlaylist()

Gets the playlist. It can be null if the playlist hasn't been set or it's reset by SessionPlayer.setMediaItem(MediaItem).

Returns:

playlist, or null

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)

public abstract MediaMetadata getPlaylistMetadata()

Gets the playlist metadata.

Returns:

metadata metadata of the playlist, or null if none is set

See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata), SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)

public abstract int getRepeatMode()

Gets the repeat mode.

Returns:

repeat mode

See also: SessionPlayer.REPEAT_MODE_NONE, SessionPlayer.REPEAT_MODE_ONE, SessionPlayer.REPEAT_MODE_ALL, SessionPlayer.REPEAT_MODE_GROUP, SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int)

public abstract int getShuffleMode()

Gets the shuffle mode.

Returns:

the shuffle mode

See also: SessionPlayer.SHUFFLE_MODE_NONE, SessionPlayer.SHUFFLE_MODE_ALL, SessionPlayer.SHUFFLE_MODE_GROUP, SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int)

public abstract MediaItem getCurrentMediaItem()

Gets the current media item, which is currently playing or would be played with later SessionPlayer.play(). This value may be updated when SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.

Returns:

the current media item. Can be null only when the player is in SessionPlayer.PLAYER_STATE_IDLE and a media item or playlist hasn't been set.

See also: SessionPlayer.setMediaItem(MediaItem), SessionPlayer.setPlaylist(List, MediaMetadata)

public abstract int getCurrentMediaItemIndex()

Gets the index of current media item in playlist. This value should be updated when SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.

Returns:

the index of current media item. Can be SessionPlayer.INVALID_ITEM_INDEX when current media item is null or not in the playlist, and when the playlist hasn't been set.

public abstract int getPreviousMediaItemIndex()

Gets the previous item index in the playlist. This value should be updated when SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.

Returns:

the index of previous media item. Can be SessionPlayer.INVALID_ITEM_INDEX only when previous media item does not exist or playlist hasn't been set.

public abstract int getNextMediaItemIndex()

Gets the next item index in the playlist. This value should be updated when SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.

Returns:

the index of next media item. Can be SessionPlayer.INVALID_ITEM_INDEX only when next media item does not exist or playlist hasn't been set.

public final void registerPlayerCallback(java.util.concurrent.Executor executor, SessionPlayer.PlayerCallback callback)

Register SessionPlayer.PlayerCallback to listen changes.

Parameters:

executor: a callback Executor
callback: a PlayerCallback

public final void unregisterPlayerCallback(SessionPlayer.PlayerCallback callback)

Unregister the previously registered SessionPlayer.PlayerCallback.

Parameters:

callback: the callback to be removed

protected final java.util.List<Pair> getCallbacks()

Gets the callbacks with executors for subclasses to notify player events.

Returns:

map of callbacks and its executors

public java.util.List<SessionPlayer.TrackInfo> getTracks()

Gets the full list of selected and unselected tracks that the media contains. The order of the list is irrelevant as different players expose tracks in different ways, but the tracks will generally be ordered based on track type.

The types of tracks supported may vary based on player implementation.

Returns:

list of tracks. The total number of tracks is the size of the list. If empty, the implementation should return an empty list instead of null.

See also: SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA

public <any> selectTrack(SessionPlayer.TrackInfo trackInfo)

Selects the SessionPlayer.TrackInfo for the current media item.

Generally one track will be selected for each track type.

The types of tracks supported may vary based on player implementation.

Note: SessionPlayer.getTracks() returns the list of tracks that can be selected, but the list may be invalidated when SessionPlayer.PlayerCallback.onTracksChanged(SessionPlayer, List) is called.

Parameters:

trackInfo: track to be selected

Returns:

a representing the pending completion of the command

See also: SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA, SessionPlayer.PlayerCallback

public <any> deselectTrack(SessionPlayer.TrackInfo trackInfo)

Deselects the SessionPlayer.TrackInfo for the current media item.

Generally, a track should already be selected in order to be deselected, and audio and video tracks should not be deselected.

The types of tracks supported may vary based on player implementation.

Note: SessionPlayer.getSelectedTrack(int) returns the currently selected track per track type that can be deselected, but the list may be invalidated when SessionPlayer.PlayerCallback.onTracksChanged(SessionPlayer, List) is called.

Parameters:

trackInfo: track to be deselected

Returns:

a which represents the pending completion of the command

See also: SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA, SessionPlayer.PlayerCallback

public SessionPlayer.TrackInfo getSelectedTrack(int trackType)

Gets currently selected track's SessionPlayer.TrackInfo for the given track type.

The returned value can be outdated after SessionPlayer.PlayerCallback.onTracksChanged(SessionPlayer, List), SessionPlayer.PlayerCallback, or SessionPlayer.PlayerCallback is called.

Parameters:

trackType: type of selected track

Returns:

selected track info

See also: SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA

public void close()

Removes all existing references to callbacks and executors. Note: Sub classes of SessionPlayer that override this API should call this super method.

Source

/*
 * Copyright 2019 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.media2.common;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import android.media.MediaFormat;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;

import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.util.Pair;
import androidx.media.AudioAttributesCompat;
import androidx.versionedparcelable.CustomVersionedParcelable;
import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelize;

import com.google.common.util.concurrent.ListenableFuture;

import java.io.Closeable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;

/**
 * Base interface for all media players that want media session.
 * <p>
 * APIs that return {@link ListenableFuture} should be the asynchronous calls and shouldn't block
 * the calling thread. This guarantees the APIs are safe to be called on the main thread.
 *
 * <p>Topics covered here are:
 * <ol>
 * <li><a href="#BestPractices">Best practices</a>
 * <li><a href="#PlayerStates">Player states</a>
 * <li><a href="#InvalidStates">Invalid method calls</a>
 * </ol>
 *
 * <h3 id="BestPractices">Best practices</h3>
 *
 * Here are best practices when implementing/using SessionPlayer:
 *
 * <ul>
 * <li>When updating UI, you should respond to {@link PlayerCallback} invocations instead of
 * {@link PlayerResult} objects since the player can be controlled by others.
 * <li>When a SessionPlayer object is no longer being used, call {@link #close()} as soon as
 * possible to release the resources used by the internal player engine associated with the
 * SessionPlayer. For example, if a player uses hardware decoder, other player instances may
 * fallback to software decoders or fail to play. You cannot use SessionPlayer instance after
 * you call {@link #close()}. There is no way to reuse the instance.
 * <li>The current playback position can be retrieved with a call to {@link #getCurrentPosition()},
 * which is helpful for applications such as a music player that need to keep track of the playback
 * progress.
 * <li>The playback position can be adjusted with a call to {@link #seekTo(long)}. Although the
 * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
 * while to finish, especially for audio/video being streamed.
 * <li>You can call {@link #seekTo(long)} from the {@link #PLAYER_STATE_PAUSED}. In these cases, if
 * you are playing a video stream and the requested position is valid, one video frame may be
 * displayed.
 * </ul>
 *
 * <h3 id="PlayerStates">Player states</h3>
 * The playback control of audio/video files is managed as a state machine. The SessionPlayer
 * defines four states:
 * <ol>
 *     <li>{@link #PLAYER_STATE_IDLE}: Initial state after the instantiation.
 *         <p>
 *         While in this state, you should call {@link #setMediaItem(MediaItem)} or
 *         {@link #setPlaylist(List, MediaMetadata)}. Check returned {@link ListenableFuture} for
 *         potential error.
 *         <p>
 *         Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}.
 *
 *     <li>{@link #PLAYER_STATE_PAUSED}: State when the audio/video playback is paused.
 *         <p>
 *         Call {@link #play()} to resume or start playback from the position where it paused.
 *
 *     <li>{@link #PLAYER_STATE_PLAYING}: State when the player plays the media item.
 *         <p>
 *         In this state, {@link PlayerCallback#onBufferingStateChanged(
 *         SessionPlayer, MediaItem, int)} will be called regularly to tell the buffering status.
 *         <p>
 *         Playback state would remain {@link #PLAYER_STATE_PLAYING} when the currently playing
 *         media item is changed.
 *         <p>
 *         When the playback reaches the end of stream, the behavior depends on repeat mode, set by
 *         {@link #setRepeatMode(int)}. If the repeat mode was set to {@link #REPEAT_MODE_NONE},
 *         the player will transfer to the {@link #PLAYER_STATE_PAUSED}. Otherwise, the
 *         SessionPlayer object remains in the {@link #PLAYER_STATE_PLAYING} and playback will be
 *         ongoing.
 *
 *     <li>{@link #PLAYER_STATE_ERROR}: State when the playback failed and player cannot be
 *         recovered by itself.
 *         <p>
 *         In general, playback might fail due to various reasons such as unsupported audio/video
 *         format, poorly interleaved audio/video, resolution too high, streaming timeout, and
 *         others. In addition, due to programming errors, a playback control operation might be
 *         performed from an <a href="#InvalidStates">invalid state</a>. In these cases the player
 *         may transition to this state.
 * </ol>
 * Subclasses may have extra methods to reset the player state to {@link #PLAYER_STATE_IDLE} from
 * other states. Take a look at documentations of specific subclass that you're interested in.
 * <p>
 *
 * <h3 id="InvalidStates">Invalid method calls</h3>
 * The only method you safely call from the {@link #PLAYER_STATE_ERROR} is {@link #close()}.
 * Any other methods might return meaningless data.
 * <p>
 * Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error
 * state and/or provide a method to recover from the error state. Take a look at documentations of
 * specific subclass that you're interested in.
 * <p>
 * Most methods can be called from any non-Error state. In case they're called in invalid state,
 * the implementation should ignore and would return {@link PlayerResult} with
 * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}. The following table lists the methods that
 * aren't guaranteed to successfully running if they're called from the associated invalid states.
 * <p>
 * <table>
 * <tr><th>Method Name</th> <th>Invalid States</th></tr>
 * <tr><td>setAudioAttributes</td> <td>{Paused, Playing}</td></tr>
 * <tr><td>prepare</td> <td>{Paused, Playing}</td></tr>
 * <tr><td>play</td> <td>{Idle}</td></tr>
 * <tr><td>pause</td> <td>{Idle}</td></tr>
 * <tr><td>seekTo</td> <td>{Idle}</td></tr>
 * </table>
 */
// Previously MediaSessionCompat.Callback.
// Players can extend this directly (e.g. MediaPlayer) or create wrapper and control underlying
// player.
// Preferably it can be interface, but API guideline requires to use abstract class.
public abstract class SessionPlayer implements Closeable {
    private static final String TAG = "SessionPlayer";

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    @IntDef({
            PLAYER_STATE_IDLE,
            PLAYER_STATE_PAUSED,
            PLAYER_STATE_PLAYING,
            PLAYER_STATE_ERROR})
    @Retention(RetentionPolicy.SOURCE)
    public @interface PlayerState {
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    @IntDef({
            BUFFERING_STATE_UNKNOWN,
            BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
            BUFFERING_STATE_BUFFERING_AND_STARVED,
            BUFFERING_STATE_COMPLETE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BuffState {
    }

    /**
     * State when the player is idle, and needs configuration to start playback.
     */
    public static final int PLAYER_STATE_IDLE = 0;

    /**
     * State when the player's playback is paused
     */
    public static final int PLAYER_STATE_PAUSED = 1;

    /**
     * State when the player's playback is ongoing
     */
    public static final int PLAYER_STATE_PLAYING = 2;

    /**
     * State when the player is in error state and cannot be recovered self.
     */
    public static final int PLAYER_STATE_ERROR = 3;

    /**
     * Buffering state is unknown.
     */
    public static final int BUFFERING_STATE_UNKNOWN = 0;

    /**
     * Buffering state indicating the player is buffering but enough has been buffered
     * for this player to be able to play the content.
     * See {@link #getBufferedPosition()} for how far is buffered already.
     */
    public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1;

    /**
     * Buffering state indicating the player is buffering, but the player is currently starved
     * for data, and cannot play.
     */
    public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2;

    /**
     * Buffering state indicating the player is done buffering, and the remainder of the content is
     * available for playback.
     */
    public static final int BUFFERING_STATE_COMPLETE = 3;

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
            REPEAT_MODE_GROUP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface RepeatMode {
    }

    /**
     * Playback will be stopped at the end of the playing media list.
     */
    public static final int REPEAT_MODE_NONE = 0;

    /**
     * Playback of the current playing media item will be repeated.
     */
    public static final int REPEAT_MODE_ONE = 1;

    /**
     * Playing media list will be repeated.
     */
    public static final int REPEAT_MODE_ALL = 2;

    /**
     * Playback of the playing media group will be repeated.
     * A group is a logical block of media items which is specified in the section 5.7 of the
     * Bluetooth AVRCP 1.6. An example of a group is the playlist.
     */
    public static final int REPEAT_MODE_GROUP = 3;

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ShuffleMode {
    }

    /**
     * Media list will be played in order.
     */
    public static final int SHUFFLE_MODE_NONE = 0;

    /**
     * Media list will be played in shuffled order.
     */
    public static final int SHUFFLE_MODE_ALL = 1;

    /**
     * Media group will be played in shuffled order.
     * A group is a logical block of media items which is specified in the section 5.7 of the
     * Bluetooth AVRCP 1.6. An example of a group is the playlist.
     */
    public static final int SHUFFLE_MODE_GROUP = 2;

    /**
     * Value indicating the time is unknown
     */
    public static final long UNKNOWN_TIME = Long.MIN_VALUE;

    /**
     * Media item index is invalid. This value will be returned when the corresponding media item
     * does not exist.
     */
    public static final int INVALID_ITEM_INDEX = -1;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private final List<Pair<PlayerCallback, Executor>> mCallbacks = new ArrayList<>();

    /**
     * Starts or resumes playback.
     * <p>
     * On success, this transfers the player state to {@link #PLAYER_STATE_PLAYING} and
     * a {@link PlayerResult} should be returned with the current media item when the command
     * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
     * it should be ignored and a {@link PlayerResult} should be returned with
     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> play();

    /**
     * Pauses playback.
     * <p>
     * On success, this transfers the player state to {@link #PLAYER_STATE_PAUSED} and
     * a {@link PlayerResult} should be returned with the current media item when the command
     * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
     * it should be ignored and a {@link PlayerResult} should be returned with
     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> pause();

    /**
     * Prepares the media items for playback. During this time, the player may allocate resources
     * required to play, such as audio and video decoders. Before calling this API, set media
     * item(s) through either {@link #setMediaItem} or {@link #setPlaylist}.
     * <p>
     * On success, this transfers the player state from {@link #PLAYER_STATE_IDLE} to
     * {@link #PLAYER_STATE_PAUSED} and a {@link PlayerResult} should be returned with the prepared
     * media item when the command completed. If it's not called in {@link #PLAYER_STATE_IDLE},
     * it should be ignored and {@link PlayerResult} should be returned with
     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> prepare();

    /**
     * Seeks to the specified position.
     * <p>
     * The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
     * calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored and
     * a {@link PlayerResult} should be returned with
     * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @param position the new playback position in ms. The value should be in the range of start
     * and end positions defined in {@link MediaItem}.
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> seekTo(long position);

    /**
     * Sets the playback speed. The default playback speed is {@code 1.0f}, and negative values
     * indicate reverse playback and {@code 0.0f} is not allowed.
     * <p>
     * The supported playback speed range depends on the underlying player implementation, so it is
     * recommended to query the actual speed of the player via {@link #getPlaybackSpeed()} after the
     * operation completes. In particular, please note that player implementations may not support
     * reverse playback.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param playbackSpeed the requested playback speed
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @see #getPlaybackSpeed()
     * @see PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed);

    /**
     * Sets the {@link AudioAttributesCompat} to be used during the playback of the media.
     * <p>
     * You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
     * become effective thereafter. Otherwise, the call would be ignored and {@link PlayerResult}
     * should be returned with {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param attributes non-null <code>AudioAttributes</code>.
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setAudioAttributes(
            @NonNull AudioAttributesCompat attributes);

    /**
     * Gets the current player state.
     *
     * @return the current player state
     * @see PlayerCallback#onPlayerStateChanged(SessionPlayer, int)
     * @see #PLAYER_STATE_IDLE
     * @see #PLAYER_STATE_PAUSED
     * @see #PLAYER_STATE_PLAYING
     * @see #PLAYER_STATE_ERROR
     */
    @PlayerState
    public abstract int getPlayerState();

    /**
     * Gets the current playback position.
     * <p>
     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
     * So the position {@code 0} means the start position of the {@link MediaItem}.
     *
     * @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown
     */
    public abstract long getCurrentPosition();

    /**
     * Gets the duration of the current media item, or {@link #UNKNOWN_TIME} if unknown. If the
     * current {@link MediaItem} has either start or end position, then duration would be adjusted
     * accordingly instead of returning the whole size of the {@link MediaItem}.
     *
     * @return the duration in ms, or {@link #UNKNOWN_TIME} if unknown
     */
    public abstract long getDuration();

    /**
     * Gets the position for how much has been buffered, or {@link #UNKNOWN_TIME} if unknown.
     * <p>
     * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
     * So the position {@code 0} means the start position of the {@link MediaItem}.
     *
     * @return the buffered position in ms, or {@link #UNKNOWN_TIME} if unknown
     */
    public abstract long getBufferedPosition();

    /**
     * Returns the current buffering state of the player.
     * <p>
     * During the buffering, see {@link #getBufferedPosition()} for the quantifying the amount
     * already buffered.
     *
     * @return the buffering state, or {@link #BUFFERING_STATE_UNKNOWN} if unknown
     * @see #getBufferedPosition()
     */
    @BuffState
    public abstract int getBufferingState();

    /**
     * Gets the actual playback speed to be used by the player when playing. A value of {@code 1.0f}
     * is the default playback value, and a negative value indicates reverse playback.
     * <p>
     * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
     *
     * @return the actual playback speed
     */
    public abstract float getPlaybackSpeed();

    /**
     * Gets the size of the video.
     *
     * @return the size of the video. The width and height of size could be 0 if there is no video
     *         or the size has not been determined yet.
     * @see PlayerCallback#onVideoSizeChanged(SessionPlayer, VideoSize)
     */
    @NonNull
    public VideoSize getVideoSize() {
        throw new UnsupportedOperationException("getVideoSize is not implemented");
    }

    /**
     * Sets the {@link Surface} to be used as the sink for the video portion of the media.
     * <p>
     * A null surface will reset any Surface and result in only the audio track being played.
     * <p>
     * On success, a {@link SessionPlayer.PlayerResult} is returned with
     * the current media item when the command completed.
     *
     * @param surface the {@link Surface} to be used for the video portion of the media
     * @return a {@link ListenableFuture} which represents the pending completion of the command
     */
    @NonNull
    public ListenableFuture<PlayerResult> setSurface(@Nullable Surface surface) {
        throw new UnsupportedOperationException("setSurface is not implemented");
    }

    /**
     * Sets a list of {@link MediaItem} with metadata. Use this or {@link #setMediaItem} to specify
     * which items to play.
     * <p>
     * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
     * <p>
     * Ensure uniqueness of each {@link MediaItem} in the playlist so the session can uniquely
     * identity individual items. All {@link MediaItem}s shouldn't be {@code null} as well.
     * <p>
     * It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
     * duration information in the metadata, session will do extra work to get the duration and send
     * it to the controller.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
     * when it's completed. The current media item would be the first item in the playlist.
     * <p>
     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
     * when a media item in the playlist is a {@link FileMediaItem}.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the first media item of the
     * playlist when the command completed.
     *
     * @param list a list of {@link MediaItem} objects to set as a play list
     * @throws IllegalArgumentException if the given list is {@code null} or empty, or has
     *         duplicated media items.
     * @return a {@link ListenableFuture} which represents the pending completion of the command
     * @see #setMediaItem
     * @see PlayerCallback#onPlaylistChanged
     * @see PlayerCallback#onCurrentMediaItemChanged
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setPlaylist(
            @NonNull List<MediaItem> list, @Nullable MediaMetadata metadata);

    /**
     * Gets the {@link AudioAttributesCompat} that media player has.
     */
    @Nullable
    public abstract AudioAttributesCompat getAudioAttributes();

    /**
     * Sets a {@link MediaItem} for playback. Use this or {@link #setPlaylist} to specify which
     * items to play. If you want to change current item in the playlist, use one of
     * {@link #skipToPlaylistItem}, {@link #skipToNextPlaylistItem}, or
     * {@link #skipToPreviousPlaylistItem} instead of this method.
     * <p>
     * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
     * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
     * <p>
     * It's recommended to fill {@link MediaMetadata} in {@link MediaItem} especially for the
     * duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
     * duration information in the metadata, session will do extra work to get the duration and send
     * it to the controller.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
     * when it's completed. The current item would be the item given here.
     * <p>
     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
     * if the given media item is a {@link FileMediaItem}.
     * <p>
     * On success, a {@link PlayerResult} should be returned with {@code item} set.
     *
     * @param item the descriptor of media item you want to play
     * @return a {@link ListenableFuture} which represents the pending completion of the command
     * @see #setPlaylist
     * @see PlayerCallback#onPlaylistChanged
     * @see PlayerCallback#onCurrentMediaItemChanged
     * @throws IllegalArgumentException if the given item is {@code null}.
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setMediaItem(
            @NonNull MediaItem item);

    /**
     * Adds the media item to the playlist at the index. Index equals to or greater than
     * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
     * the playlist.
     * <p>
     * If index is less than or equal to the current index of the playlist,
     * the current index of the playlist should be increased correspondingly.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
     * completed.
     * <p>
     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
     * if the given media item is a {@link FileMediaItem}.
     * <p>
     * On success, a {@link PlayerResult} should be returned with {@code item} added.
     *
     * @param index the index of the item you want to add in the playlist
     * @param item the media item you want to add
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> addPlaylistItem(int index,
            @NonNull MediaItem item);

    /**
     * Removes the media item from the playlist
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with {@code item} removed.
     * <p>
     * If the last item is removed, the player should be moved to {@link #PLAYER_STATE_IDLE}.
     *
     * @param index the index of the item you want to remove in the playlist
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> removePlaylistItem(
            @IntRange(from = 0) int index);

    /**
     * Replaces the media item at index in the playlist. This can be also used to update metadata of
     * an item.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
     * completed.
     * <p>
     * The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
     * if the given media item is a {@link FileMediaItem}.
     * <p>
     * On success, a {@link PlayerResult} should be returned with {@code item} set.
     *
     * @param index the index of the item to replace in the playlist
     * @param item the new item
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> replacePlaylistItem(int index,
            @NonNull MediaItem item);

    /**
     * Moves the media item at {@code fromIdx} to {@code toIdx} in the playlist.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with {@code item} set.
     *
     * @param fromIndex the media item's initial index in the playlist
     * @param toIndex the media item's target index in the playlist
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     */
    @NonNull
    public ListenableFuture<PlayerResult> movePlaylistItem(
            @IntRange(from = 0) int fromIndex, @IntRange(from = 0) int toIndex) {
        throw new UnsupportedOperationException("movePlaylistItem is not implemented");
    }

    /**
     * Skips to the previous item in the playlist.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> skipToPreviousPlaylistItem();

    /**
     * Skips to the next item in the playlist.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> skipToNextPlaylistItem();

    /**
     * Skips to the item in the playlist at the index.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param index the index of the item you want to play in the playlist
     * @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> skipToPlaylistItem(
            @IntRange(from = 0) int index);

    /**
     * Updates the playlist metadata while keeping the playlist as-is.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)} when it's
     * completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param metadata metadata of the playlist
     * @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> updatePlaylistMetadata(
            @Nullable MediaMetadata metadata);

    /**
     * Sets the repeat mode.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onRepeatModeChanged(SessionPlayer, int)} when it's completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param repeatMode repeat mode
     * @see #REPEAT_MODE_NONE
     * @see #REPEAT_MODE_ONE
     * @see #REPEAT_MODE_ALL
     * @see #REPEAT_MODE_GROUP
     * @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setRepeatMode(
            @RepeatMode int repeatMode);

    /**
     * Sets the shuffle mode.
     * <p>
     * The implementation must notify registered callbacks with
     * {@link PlayerCallback#onShuffleModeChanged(SessionPlayer, int)} when it's completed.
     * <p>
     * On success, a {@link PlayerResult} should be returned with the current media item when the
     * command completed.
     *
     * @param shuffleMode the shuffle mode
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @see #SHUFFLE_MODE_NONE
     * @see #SHUFFLE_MODE_ALL
     * @see #SHUFFLE_MODE_GROUP
     * @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
     */
    @NonNull
    public abstract ListenableFuture<PlayerResult> setShuffleMode(
            @ShuffleMode int shuffleMode);

    /**
     * Gets the playlist. It can be {@code null} if the playlist hasn't been set or it's reset by
     * {@link #setMediaItem}.
     *
     * @return playlist, or {@code null}
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     */
    @Nullable
    public abstract List<MediaItem> getPlaylist();

    /**
     * Gets the playlist metadata.
     *
     * @return metadata metadata of the playlist, or null if none is set
     * @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
     * @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
     */
    @Nullable
    public abstract MediaMetadata getPlaylistMetadata();

    /**
     * Gets the repeat mode.
     *
     * @return repeat mode
     * @see #REPEAT_MODE_NONE
     * @see #REPEAT_MODE_ONE
     * @see #REPEAT_MODE_ALL
     * @see #REPEAT_MODE_GROUP
     * @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
     */
    @RepeatMode
    public abstract int getRepeatMode();

    /**
     * Gets the shuffle mode.
     *
     * @return the shuffle mode
     * @see #SHUFFLE_MODE_NONE
     * @see #SHUFFLE_MODE_ALL
     * @see #SHUFFLE_MODE_GROUP
     * @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
     */
    @ShuffleMode
    public abstract int getShuffleMode();

    /**
     * Gets the current media item, which is currently playing or would be played with later
     * {@link #play}. This value may be updated when
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is
     * called.
     *
     * @return the current media item. Can be {@code null} only when the player is in
     * {@link #PLAYER_STATE_IDLE} and a media item or playlist hasn't been set.
     * @see #setMediaItem
     * @see #setPlaylist
     */
    @Nullable
    public abstract MediaItem getCurrentMediaItem();

    /**
     * Gets the index of current media item in playlist. This value should be updated when
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
     *
     * @return the index of current media item. Can be {@link #INVALID_ITEM_INDEX} when current
     *         media item is null or not in the playlist, and when the playlist hasn't been set.
     */
    @IntRange(from = INVALID_ITEM_INDEX)
    public abstract int getCurrentMediaItemIndex();

    /**
     * Gets the previous item index in the playlist. This value should be updated when
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
     *
     * @return the index of previous media item. Can be {@link #INVALID_ITEM_INDEX} only when
     *         previous media item does not exist or playlist hasn't been set.
     */
    @IntRange(from = INVALID_ITEM_INDEX)
    public abstract int getPreviousMediaItemIndex();

    /**
     * Gets the next item index in the playlist. This value should be updated when
     * {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
     * {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
     *
     * @return the index of next media item. Can be {@link #INVALID_ITEM_INDEX} only when next media
     *         item does not exist or playlist hasn't been set.
     */
    @IntRange(from = INVALID_ITEM_INDEX)
    public abstract int getNextMediaItemIndex();

    // Listeners / Callback related
    // Intentionally final not to allow developers to change the behavior
    /**
     * Register {@link PlayerCallback} to listen changes.
     *
     * @param executor a callback Executor
     * @param callback a PlayerCallback
     * @throws IllegalArgumentException if executor or callback is {@code null}.
     */
    public final void registerPlayerCallback(
            @NonNull /*@CallbackExecutor*/ Executor executor,
            @NonNull PlayerCallback callback) {
        if (executor == null) {
            throw new NullPointerException("executor shouldn't be null");
        }
        if (callback == null) {
            throw new NullPointerException("callback shouldn't be null");
        }

        synchronized (mLock) {
            for (Pair<PlayerCallback, Executor> pair : mCallbacks) {
                if (pair.first == callback && pair.second != null) {
                    Log.w(TAG, "callback is already added. Ignoring.");
                    return;
                }
            }
            mCallbacks.add(new Pair<>(callback, executor));
        }
    }

    /**
     * Unregister the previously registered {@link PlayerCallback}.
     *
     * @param callback the callback to be removed
     * @throws IllegalArgumentException if the callback is {@code null}.
     */
    public final void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
        if (callback == null) {
            throw new NullPointerException("callback shouldn't be null");
        }
        synchronized (mLock) {
            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                if (mCallbacks.get(i).first == callback) {
                    mCallbacks.remove(i);
                }
            }
        }
    }

    /**
     * Gets the callbacks with executors for subclasses to notify player events.
     *
     * @return map of callbacks and its executors
     */
    @NonNull
    protected final List<Pair<PlayerCallback, Executor>> getCallbacks() {
        List<Pair<PlayerCallback, Executor>> list = new ArrayList<>();
        synchronized (mLock) {
            list.addAll(mCallbacks);
        }
        return list;
    }

    /**
     * Gets the full list of selected and unselected tracks that the media contains. The order of
     * the list is irrelevant as different players expose tracks in different ways, but the tracks
     * will generally be ordered based on track type.
     * <p>
     * The types of tracks supported may vary based on player implementation.
     *
     * @return list of tracks. The total number of tracks is the size of the list. If empty,
     *         the implementation should return an empty list instead of {@code null}.
     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
     */
    @NonNull
    public List<TrackInfo> getTracks() {
        throw new UnsupportedOperationException("getTracks is not implemented");
    }

    /**
     * Selects the {@link TrackInfo} for the current media item.
     * <p>
     * Generally one track will be selected for each track type.
     * <p>
     * The types of tracks supported may vary based on player implementation.
     * <p>
     * Note: {@link #getTracks()} returns the list of tracks that can be selected, but the
     * list may be invalidated when {@link PlayerCallback#onTracksChanged(SessionPlayer, List)}
     * is called.
     *
     * @param trackInfo track to be selected
     * @return a {@link ListenableFuture} representing the pending completion of the command
     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
     * @see PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)
     */
    @NonNull
    public ListenableFuture<PlayerResult> selectTrack(@NonNull TrackInfo trackInfo) {
        throw new UnsupportedOperationException("selectTrack is not implemented");
    }

    /**
     * Deselects the {@link TrackInfo} for the current media item.
     * <p>
     * Generally, a track should already be selected in order to be deselected, and audio and video
     * tracks should not be deselected.
     * <p>
     * The types of tracks supported may vary based on player implementation.
     * <p>
     * Note: {@link #getSelectedTrack(int)} returns the currently selected track per track type that
     * can be deselected, but the list may be invalidated when
     * {@link PlayerCallback#onTracksChanged(SessionPlayer, List)} is called.
     *
     * @param trackInfo track to be deselected
     * @return a {@link ListenableFuture} which represents the pending completion of the command
     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
     * @see PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)
     */
    @NonNull
    public ListenableFuture<PlayerResult> deselectTrack(@NonNull TrackInfo trackInfo) {
        throw new UnsupportedOperationException("deselectTrack is not implemented");
    }

    /**
     * Gets currently selected track's {@link TrackInfo} for the given track type.
     * <p>
     * The returned value can be outdated after
     * {@link PlayerCallback#onTracksChanged(SessionPlayer, List)},
     * {@link PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)},
     * or {@link PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)} is called.
     *
     * @param trackType type of selected track
     * @return selected track info
     * @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
     * @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
     * @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
     * @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
     */
    @Nullable
    public TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
        throw new UnsupportedOperationException(
                "getSelectedTrack is not implemented");
    }

    /**
     * Removes all existing references to callbacks and executors.
     *
     * Note: Sub classes of {@link SessionPlayer} that override this API should call this super
     * method.
     */
    @CallSuper
    @Override
    public void close() {
        synchronized (mLock) {
            mCallbacks.clear();
        }
    }

    /**
     * Class for the player to return each audio/video/subtitle track's metadata.
     *
     * Note: TrackInfo holds a MediaFormat instance, but only the following key-values will be
     * supported when sending it over different processes:
     * <ul>
     * <li>{@link MediaFormat#KEY_LANGUAGE}</li>
     * <li>{@link MediaFormat#KEY_MIME}</li>
     * <li>{@link MediaFormat#KEY_IS_FORCED_SUBTITLE}</li>
     * <li>{@link MediaFormat#KEY_IS_AUTOSELECT}</li>
     * <li>{@link MediaFormat#KEY_IS_DEFAULT}</li>
     * </ul>
     *
     * @see #getTracks
     */
    @VersionedParcelize(isCustom = true)
    public static class TrackInfo extends CustomVersionedParcelable {
        public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
        public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
        public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
        public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
        public static final int MEDIA_TRACK_TYPE_METADATA = 5;

        private static final String KEY_IS_FORMAT_NULL =
                "androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_FORMAT_NULL";
        private static final String KEY_IS_SELECTABLE =
                "androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_SELECTABLE";

        /**
         * @hide
         */
        @IntDef(flag = false, /*prefix = "MEDIA_TRACK_TYPE",*/ value = {
                MEDIA_TRACK_TYPE_UNKNOWN,
                MEDIA_TRACK_TYPE_VIDEO,
                MEDIA_TRACK_TYPE_AUDIO,
                MEDIA_TRACK_TYPE_SUBTITLE,
                MEDIA_TRACK_TYPE_METADATA,
        })
        @Retention(RetentionPolicy.SOURCE)
        @RestrictTo(LIBRARY_GROUP)
        public @interface MediaTrackType {}

        @ParcelField(1)
        int mId;

        // Removed @ParcelField(2)

        @ParcelField(3)
        int mTrackType;

        // Parceled via mParcelableExtras.
        @NonParcelField
        @Nullable
        MediaFormat mFormat;
        // Parceled via mParcelableExtras.
        @NonParcelField
        boolean mIsSelectable;
        // For extra information containing MediaFormat and isSelectable data. Should only be used
        // by onPreParceling() and onPostParceling().
        @ParcelField(4)
        Bundle mParcelableExtras;
        @NonParcelField
        private final Object mLock = new Object();

        // WARNING: Adding a new ParcelField may break old library users (b/152830728)

        /**
         * Used for VersionedParcelable
         */
        TrackInfo() {
            // no-op
        }

        /**
         * Constructor to create a TrackInfo instance.
         *
         * Note: The default value for {@link #isSelectable()} is false.
         *
         * @param id id of track unique across {@link MediaItem}s
         * @param type type of track. Can be video, audio or subtitle
         * @param format format of track
         */
        public TrackInfo(int id, int type, @Nullable MediaFormat format) {
            this(id, type, format, /* isSelectable= */ false);
        }

        /**
         * Constructor to create a TrackInfo instance.
         *
         * @param id id of track unique across {@link MediaItem}s
         * @param type type of track. Can be video, audio or subtitle
         * @param format format of track
         * @param isSelectable whether track can be selected via
         * {@link SessionPlayer#selectTrack(TrackInfo)}.
         */
        public TrackInfo(int id, int type, @Nullable MediaFormat format, boolean isSelectable) {
            mId = id;
            mTrackType = type;
            mFormat = format;
            mIsSelectable = isSelectable;
        }

        /**
         * Gets the track type.
         * @return MediaTrackType which indicates if the track is video, audio or subtitle
         */
        @MediaTrackType
        public int getTrackType() {
            return mTrackType;
        }

        /**
         * Gets the language code of the track.
         * @return {@link Locale} which includes the language information
         */
        @NonNull
        public Locale getLanguage() {
            String language = mFormat != null ? mFormat.getString(MediaFormat.KEY_LANGUAGE) : null;
            if (language == null) {
                language = "und";
            }
            return new Locale(language);
        }

        /**
         * Gets the {@link MediaFormat} of the track.  If the format is
         * unknown or could not be determined, null is returned.
         */
        @Nullable
        public MediaFormat getFormat() {
            return mFormat;
        }

        /**
         * Gets the id of the track.
         * The id is used by {@link #selectTrack(TrackInfo)} and {@link #deselectTrack(TrackInfo)}
         * to identify the track to be (de)selected.
         * So, it's highly recommended to ensure that the id of each track is unique across
         * {@link MediaItem}s to avoid potential mis-selection when a stale {@link TrackInfo} is
         * used.
         *
         * @return id of the track
         */
        public int getId() {
            return mId;
        }

        /**
         * Whether the current track can be selected via {@link #selectTrack(TrackInfo)} or not.
         *
         * @return true if the current track can be selected; false if otherwise.
         */
        public boolean isSelectable() {
            return mIsSelectable;
        }

        @Override
        @NonNull
        public String toString() {
            StringBuilder out = new StringBuilder(128);
            out.append(getClass().getName());
            out.append('#').append(mId);
            out.append('{');
            switch (mTrackType) {
                case MEDIA_TRACK_TYPE_VIDEO:
                    out.append("VIDEO");
                    break;
                case MEDIA_TRACK_TYPE_AUDIO:
                    out.append("AUDIO");
                    break;
                case MEDIA_TRACK_TYPE_SUBTITLE:
                    out.append("SUBTITLE");
                    break;
                case MEDIA_TRACK_TYPE_METADATA:
                    out.append("METADATA");
                    break;
                default:
                    out.append("UNKNOWN");
                    break;
            }
            out.append(", ").append(mFormat);
            out.append(", isSelectable=").append(mIsSelectable);
            out.append("}");
            return out.toString();
        }

        @Override
        public int hashCode() {
            return mId;
        }

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

        /**
         * @hide
         * @param isStream
         */
        @RestrictTo(LIBRARY)
        @Override
        public void onPreParceling(boolean isStream) {
            synchronized (mLock) {
                mParcelableExtras = new Bundle();

                mParcelableExtras.putBoolean(KEY_IS_FORMAT_NULL, mFormat == null);
                if (mFormat != null) {
                    putStringValueToBundle(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
                    putStringValueToBundle(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
                    putIntValueToBundle(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
                            mParcelableExtras);
                    putIntValueToBundle(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
                    putIntValueToBundle(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
                }

                mParcelableExtras.putBoolean(KEY_IS_SELECTABLE, mIsSelectable);
            }
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        @Override
        public void onPostParceling() {
            if (mParcelableExtras != null && !mParcelableExtras.getBoolean(KEY_IS_FORMAT_NULL)) {
                mFormat = new MediaFormat();
                setStringValueToMediaFormat(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
                setStringValueToMediaFormat(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
                setIntValueToMediaFormat(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
                        mParcelableExtras);
                setIntValueToMediaFormat(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
                setIntValueToMediaFormat(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
            }

            if (mParcelableExtras == null || !mParcelableExtras.containsKey(KEY_IS_SELECTABLE)) {
                mIsSelectable = mTrackType != MEDIA_TRACK_TYPE_VIDEO;
            } else {
                mIsSelectable = mParcelableExtras.getBoolean(KEY_IS_SELECTABLE);
            }
        }

        private static void putIntValueToBundle(
                String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
            if (mediaFormat.containsKey(intValueKey)) {
                bundle.putInt(intValueKey, mediaFormat.getInteger(intValueKey));
            }
        }

        private static void putStringValueToBundle(
                String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
            if (mediaFormat.containsKey(stringValueKey)) {
                bundle.putString(stringValueKey, mediaFormat.getString(stringValueKey));
            }
        }

        private static void setIntValueToMediaFormat(
                String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
            if (bundle.containsKey(intValueKey)) {
                mediaFormat.setInteger(intValueKey, bundle.getInt(intValueKey));
            }
        }

        private static void setStringValueToMediaFormat(
                String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
            if (bundle.containsKey(stringValueKey)) {
                mediaFormat.setString(stringValueKey, bundle.getString(stringValueKey));
            }
        }
    }

    /**
     * A callback class to receive notifications for events on the session player. See
     * {@link #registerPlayerCallback(Executor, PlayerCallback)} to register this callback.
     */
    public abstract static class PlayerCallback {
        /**
         * Called when the state of the player has changed.
         *
         * @param player the player whose state has changed
         * @param playerState the new state of the player
         * @see #getPlayerState()
         */
        public void onPlayerStateChanged(@NonNull SessionPlayer player,
                @PlayerState int playerState) {
        }

        /**
         * Called when a buffering events for a media item happened.
         *
         * @param player the player that is buffering
         * @param item the media item for which buffering is happening
         * @param buffState the new buffering state
         * @see #getBufferingState()
         */
        public void onBufferingStateChanged(@NonNull SessionPlayer player,
                @Nullable MediaItem item, @BuffState int buffState) {
        }

        /**
         * Called when the playback speed has changed.
         *
         * @param player the player that has changed the playback speed
         * @param playbackSpeed the new playback speed
         * @see #getPlaybackSpeed()
         */
        public void onPlaybackSpeedChanged(@NonNull SessionPlayer player,
                float playbackSpeed) {
        }

        /**
         * Called when {@link #seekTo(long)} is completed.
         *
         * @param player the player that has completed seeking
         * @param position the previous seeking request
         * @see #getCurrentPosition()
         */
        public void onSeekCompleted(@NonNull SessionPlayer player, long position) {
        }

        /**
         * Called when a playlist is changed. It's also called after {@link #setPlaylist} or
         * {@link #setMediaItem}.
         *
         * @param player the player that has changed the playlist and playlist metadata
         * @param list new playlist
         * @param metadata new metadata
         * @see #getPlaylist()
         * @see #getPlaylistMetadata()
         */
        public void onPlaylistChanged(@NonNull SessionPlayer player,
                @Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
        }

        /**
         * Called when a playlist metadata is changed.
         *
         * @param player the player that has changed the playlist metadata
         * @param metadata new metadata
         * @see #getPlaylistMetadata()
         */
        public void onPlaylistMetadataChanged(@NonNull SessionPlayer player,
                @Nullable MediaMetadata metadata) {
        }

        /**
         * Called when the shuffle mode is changed.
         * <p>
         * {@link SessionPlayer#getPreviousMediaItemIndex()} and
         * {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
         * is called if the current media item is the first or last item in the playlist.
         *
         * @param player playlist agent for this event
         * @param shuffleMode shuffle mode
         * @see #SHUFFLE_MODE_NONE
         * @see #SHUFFLE_MODE_ALL
         * @see #SHUFFLE_MODE_GROUP
         * @see #getShuffleMode()
         */
        public void onShuffleModeChanged(@NonNull SessionPlayer player,
                @ShuffleMode int shuffleMode) {
        }

        /**
         * Called when the repeat mode is changed.
         * <p>
         * {@link SessionPlayer#getPreviousMediaItemIndex()} and
         * {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
         * is called if the current media item is the first or last item in the playlist.
         *
         * @param player player for this event
         * @param repeatMode repeat mode
         * @see #REPEAT_MODE_NONE
         * @see #REPEAT_MODE_ONE
         * @see #REPEAT_MODE_ALL
         * @see #REPEAT_MODE_GROUP
         * @see #getRepeatMode()
         */
        public void onRepeatModeChanged(@NonNull SessionPlayer player,
                @RepeatMode int repeatMode) {
        }

        /**
         * Called when the player's current media item has changed. Generally called after a new
         * media item is set through {@link #setPlaylist} or {@link #setMediaItem}, or after
         * skipping to a different item in a given playlist.
         *
         * @param player the player whose media item changed
         * @param item the new current media item. This can be {@code null} when the state of
         * the player becomes {@link #PLAYER_STATE_IDLE}.
         * @see #getCurrentMediaItem()
         */
        public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
                @Nullable MediaItem item) {
        }

        /**
         * Called when the player finished playing. Playback state would be also set
         * {@link #PLAYER_STATE_PAUSED} with it.
         * <p>
         * This will be called only when the repeat mode is set to {@link #REPEAT_MODE_NONE}.
         *
         * @param player the player whose playback is completed
         * @see #REPEAT_MODE_NONE
         */
        public void onPlaybackCompleted(@NonNull SessionPlayer player) {
        }

        /**
         * Called when the player's current audio attributes are changed.
         *
         * @param player the player whose audio attributes are changed
         * @param attributes the new current audio attributes
         * @see #getAudioAttributes()
         */
        public void onAudioAttributesChanged(@NonNull SessionPlayer player,
                @Nullable AudioAttributesCompat attributes) {
        }

        /**
         * Called to indicate the video size
         * <p>
         * The video size (width and height) could be 0 if there was no video,
         * no display surface was set, or the value was not determined yet.
         * <p>
         * This callback is generally called when player updates video size, but will also be
         * called when {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)}
         * is called.
         *
         * @param player the player associated with this callback
         * @param size the size of the video
         * @see #getVideoSize()
         */
        public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
        }

        /**
         * Called when the player's subtitle track has new subtitle data available.
         * @param player the player that reports the new subtitle data
         * @param item the MediaItem of this media item
         * @param track the track that has the subtitle data
         * @param data the subtitle data
         */
        public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
                @NonNull TrackInfo track, @NonNull SubtitleData data) {
        }

        /**
         * Called when the tracks of the current media item is changed such as
         * 1) when tracks of a media item become available,
         * 2) when new tracks are found during playback, or
         * 3) when the current media item is changed.
         * <p>
         * When it's called, you should invalidate previous track information and use the new
         * tracks to call {@link #selectTrack(TrackInfo)} or
         * {@link #deselectTrack(TrackInfo)}.
         *
         * @param player the player associated with this callback
         * @param tracks the list of tracks. It can be empty
         * @see #getTracks()
         */
        public void onTracksChanged(@NonNull SessionPlayer player,
                @NonNull List<TrackInfo> tracks) {
        }

        /**
         * Called when a track is selected.
         *
         * @param player the player associated with this callback
         * @param trackInfo the selected track
         * @see #selectTrack(TrackInfo)
         */
        public void onTrackSelected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
        }

        /**
         * Called when a track is deselected.
         * <p>
         * This callback will generally be called only after calling
         * {@link #deselectTrack(TrackInfo)}.
         *
         * @param player the player associated with this callback
         * @param trackInfo the deselected track
         * @see #deselectTrack(TrackInfo)
         */
        public void onTrackDeselected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
        }
    }

    /**
     * Result class of the asynchronous APIs.
     * <p>
     * Subclass may extend this class for providing more result and/or custom result code. For the
     * custom result code, follow the convention below to avoid potential code duplication.
     * <p>
     * <ul>
     * <li>Predefined error code: Negative integers greater than -100. (i.e. -100 < code < 0)
     * <li>Custom error code: Negative integers equal to or less than -1000. (i.e. code < -1000)
     * <li>Predefined info code: Positive integers less than 100. (i.e. 0 < code < 100)
     * <li>Custom Info code: Positive integers equal to or greater than 1000. (i.e. code > +1000)
     * </ul>
     */
    @SuppressWarnings("HiddenSuperclass")
    public static class PlayerResult implements BaseResult {
        /**
         * @hide
         */
        @IntDef(flag = false, /*prefix = "RESULT",*/ value = {
                RESULT_SUCCESS,
                RESULT_ERROR_UNKNOWN,
                RESULT_ERROR_INVALID_STATE,
                RESULT_ERROR_BAD_VALUE,
                RESULT_ERROR_PERMISSION_DENIED,
                RESULT_ERROR_IO,
                RESULT_ERROR_NOT_SUPPORTED,
                RESULT_INFO_SKIPPED})
        @Retention(RetentionPolicy.SOURCE)
        @RestrictTo(LIBRARY)
        public @interface ResultCode {}

        private final int mResultCode;
        private final long mCompletionTime;
        private final MediaItem mItem;

        /**
         * Constructor that uses the current system clock as the completion time.
         *
         * @param resultCode result code. Recommends to use the standard code defined here.
         * @param item media item when the command completed
         */
        // Note: resultCode is intentionally not annotated for subclass to return extra error codes.
        public PlayerResult(int resultCode, @Nullable MediaItem item) {
            this(resultCode, item, SystemClock.elapsedRealtime());
        }

        // Note: resultCode is intentionally not annotated for subclass to return extra error codes.
        private PlayerResult(int resultCode, @Nullable MediaItem item, long completionTime) {
            mResultCode = resultCode;
            mItem = item;
            mCompletionTime = completionTime;
        }


        /**
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP)
        @NonNull
        public static ListenableFuture<PlayerResult> createFuture(int resultCode) {
            ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
            result.set(new PlayerResult(resultCode, null));
            return result;
        }

        /**
         * Gets the result code.
         * <p>
         * Subclass of the {@link SessionPlayer} may have defined customized extra code other than
         * codes defined here. Check the documentation of the class that you're interested in.
         *
         * @return result code
         * @see #RESULT_ERROR_UNKNOWN
         * @see #RESULT_ERROR_INVALID_STATE
         * @see #RESULT_ERROR_BAD_VALUE
         * @see #RESULT_ERROR_PERMISSION_DENIED
         * @see #RESULT_ERROR_IO
         * @see #RESULT_ERROR_NOT_SUPPORTED
         * @see #RESULT_INFO_SKIPPED
         */
        @Override
        @ResultCode
        public int getResultCode() {
            return mResultCode;
        }

        /**
         * Gets the completion time of the command. Being more specific, it's the same as
         * {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
         *
         * @return completion time of the command
         */
        @Override
        public long getCompletionTime() {
            return mCompletionTime;
        }

        /**
         * Gets the {@link MediaItem} for which the command was executed. In other words, this is
         * the item sent as an argument of the command if any, otherwise the current media item when
         * the command completed.
         *
         * @return media item when the command completed. Can be {@code null} for an error, or
         *         the current media item was {@code null}
         */
        @Override
        @Nullable
        public MediaItem getMediaItem() {
            return mItem;
        }
    }
}