public final class

ExoPlayerMediaPlayer2Impl

extends MediaPlayer2

implements androidx.media2.exoplayer.ExoPlayerWrapper.Listener

 java.lang.Object

androidx.media2.MediaPlayer2

↳androidx.media2.exoplayer.ExoPlayerMediaPlayer2Impl

Gradle dependencies

compile group: 'androidx.media2', name: 'media2', version: '1.0.0-alpha04'

  • groupId: androidx.media2
  • artifactId: media2
  • version: 1.0.0-alpha04

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

Androidx artifact mapping:

androidx.media2:media2 com.android.support:media2

Overview

An implementation of MediaPlayer2 based on a repackaged version of ExoPlayer.

Summary

Fields
from MediaPlayer2CALL_COMPLETED_ATTACH_AUX_EFFECT, CALL_COMPLETED_DESELECT_TRACK, CALL_COMPLETED_LOOP_CURRENT, CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, CALL_COMPLETED_PAUSE, CALL_COMPLETED_PLAY, CALL_COMPLETED_PREPARE, CALL_COMPLETED_PREPARE_DRM, CALL_COMPLETED_SEEK_TO, CALL_COMPLETED_SELECT_TRACK, CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, CALL_COMPLETED_SET_AUDIO_SESSION_ID, CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, CALL_COMPLETED_SET_DATA_SOURCE, CALL_COMPLETED_SET_NEXT_DATA_SOURCE, CALL_COMPLETED_SET_NEXT_DATA_SOURCES, CALL_COMPLETED_SET_PLAYBACK_PARAMS, CALL_COMPLETED_SET_PLAYER_VOLUME, CALL_COMPLETED_SET_SURFACE, CALL_COMPLETED_SKIP_TO_NEXT, CALL_STATUS_BAD_VALUE, CALL_STATUS_ERROR_IO, CALL_STATUS_ERROR_UNKNOWN, CALL_STATUS_INVALID_OPERATION, CALL_STATUS_NO_ERROR, CALL_STATUS_PERMISSION_DENIED, CALL_STATUS_SKIPPED, MEDIA_ERROR_IO, MEDIA_ERROR_MALFORMED, MEDIA_ERROR_SYSTEM, MEDIA_ERROR_TIMED_OUT, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, MEDIA_INFO_AUDIO_NOT_PLAYING, MEDIA_INFO_AUDIO_RENDERING_START, MEDIA_INFO_BAD_INTERLEAVING, MEDIA_INFO_BUFFERING_END, MEDIA_INFO_BUFFERING_START, MEDIA_INFO_BUFFERING_UPDATE, MEDIA_INFO_DATA_SOURCE_END, MEDIA_INFO_DATA_SOURCE_LIST_END, MEDIA_INFO_DATA_SOURCE_REPEAT, MEDIA_INFO_DATA_SOURCE_START, MEDIA_INFO_EXTERNAL_METADATA_UPDATE, MEDIA_INFO_METADATA_UPDATE, MEDIA_INFO_NETWORK_BANDWIDTH, MEDIA_INFO_NOT_SEEKABLE, MEDIA_INFO_PREPARED, MEDIA_INFO_SUBTITLE_TIMED_OUT, MEDIA_INFO_TIMED_TEXT_ERROR, MEDIA_INFO_UNKNOWN, MEDIA_INFO_UNSUPPORTED_SUBTITLE, MEDIA_INFO_VIDEO_NOT_PLAYING, MEDIA_INFO_VIDEO_RENDERING_START, MEDIA_INFO_VIDEO_TRACK_LAGGING, PLAYER_STATE_ERROR, PLAYER_STATE_IDLE, PLAYER_STATE_PAUSED, PLAYER_STATE_PLAYING, PLAYER_STATE_PREPARED, PREPARE_DRM_STATUS_PREPARATION_ERROR, PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, PREPARE_DRM_STATUS_RESOURCE_BUSY, PREPARE_DRM_STATUS_SUCCESS, PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME, SEEK_CLOSEST, SEEK_CLOSEST_SYNC, SEEK_NEXT_SYNC, SEEK_PREVIOUS_SYNC, SEPARATE_CALL_COMPLETE_CALLBACK_START, VIDEO_SCALING_MODE_SCALE_TO_FIT
Constructors
publicExoPlayerMediaPlayer2Impl(Context context)

Creates a new ExoPlayer wrapper using the specified context.

Methods
public abstract java.lang.ObjectattachAuxEffect(int effectId)

Attaches an auxiliary effect to the player.

public abstract booleancancel(java.lang.Object token)

Cancels the asynchronous call previously submitted.

public abstract voidclearDrmEventCallback()

Clears the MediaPlayer2.DrmEventCallback.

public abstract voidclearEventCallback()

Clears the MediaPlayer2.EventCallback.

public abstract voidclearPendingCommands()

Discards all pending commands.

public abstract voidclose()

Releases the resources held by this MediaPlayer2 object.

public abstract java.lang.ObjectdeselectTrack(int index)

Deselects a track.

public abstract AudioAttributesCompatgetAudioAttributes()

Gets the audio attributes for this MediaPlayer2.

public abstract intgetAudioSessionId()

Returns the audio session ID.

public abstract longgetBufferedPosition()

Gets the current buffered media source position received through progressive downloading.

public abstract MediaItemgetCurrentMediaItem()

Gets the current media item as described by a MediaItem.

public abstract longgetCurrentPosition()

Gets the current playback position.

public abstract MediaPlayer2.DrmInfogetDrmInfo()

Retrieves the DRM Info associated with the current source

public abstract MediaDrm.KeyRequestgetDrmKeyRequest(byte[] keySetId[], byte[] initData[], java.lang.String mimeType, int keyType, java.util.Map<java.lang.String, java.lang.String> optionalParameters)

A key request/response exchange occurs between the app and a license server to obtain or release keys used to decrypt encrypted content.

public abstract java.lang.StringgetDrmPropertyString(java.lang.String propertyName)

Read a DRM engine plugin String property value, given the property name string.

public abstract longgetDuration()

Gets the duration of the file.

public abstract PersistableBundlegetMetrics()

Return Metrics data about the current player.

public abstract PlaybackParamsgetPlaybackParams()

Gets the playback params, containing the current playback rate.

public abstract floatgetPlayerVolume()

Returns the current volume of this player to this player.

public abstract intgetSelectedTrack(int trackType)

Returns the index of the audio, video, or subtitle track currently selected for playback, The return value is an index into the array returned by MediaPlayer2.getTrackInfo(), and can be used in calls to MediaPlayer2.selectTrack(int) or MediaPlayer2.deselectTrack(int).

public abstract intgetState()

Gets the current MediaPlayer2 state.

public abstract MediaTimestampgetTimestamp()

Gets current playback position as a MediaTimestamp.

public abstract java.util.List<MediaPlayer2.TrackInfo>getTrackInfo()

Returns a List of track information.

public abstract intgetVideoHeight()

Returns the height of the video.

public abstract intgetVideoWidth()

Returns the width of the video.

public abstract java.lang.ObjectloopCurrent(boolean loop)

Configures the player to loop on the current media item.

public abstract java.lang.ObjectnotifyWhenCommandLabelReached(java.lang.Object label)

Insert a task in the command queue to help the client to identify whether a batch of commands has been finished.

public voidonBandwidthSample(MediaItem mediaItem, int bitrateKbps)

public voidonBufferingEnded(MediaItem mediaItem)

public voidonBufferingStarted(MediaItem mediaItem)

public voidonError(MediaItem mediaItem, int what)

public voidonLoop(MediaItem mediaItem)

public voidonMediaItemEnded(MediaItem mediaItem)

public voidonMediaItemStartedAsNext(MediaItem mediaItem)

public voidonMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp)

public voidonMetadataChanged(MediaItem mediaItem)

public voidonPlaybackEnded(MediaItem mediaItem)

public voidonPrepared(MediaItem mediaItem)

public voidonSeekCompleted(long positionMs)

public voidonSubtitleData(MediaItem mediaItem, SubtitleData subtitleData)

public voidonTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData)

public voidonVideoRenderingStart(MediaItem mediaItem)

public voidonVideoSizeChanged(MediaItem mediaItem, int width, int height)

public abstract java.lang.Objectpause()

Pauses playback.

public abstract java.lang.Objectplay()

Starts or resumes playback.

public abstract java.lang.Objectprepare()

Prepares the player for playback, asynchronously.

public abstract java.lang.ObjectprepareDrm(java.util.UUID uuid)

Prepares the DRM for the current source

public abstract byte[]provideDrmKeyResponse(byte[] keySetId[], byte[] response[])

A key response is received from the license server by the app, then it is provided to the DRM engine plugin using provideDrmKeyResponse.

public abstract voidreleaseDrm()

Releases the DRM session

public abstract voidreset()

Resets the MediaPlayer2 to its uninitialized state.

public abstract voidrestoreDrmKeys(byte[] keySetId[])

Restore persisted offline keys into a new session.

public abstract java.lang.ObjectseekTo(long msec, int mode)

Moves the media to specified time position by considering the given mode.

public abstract java.lang.ObjectselectTrack(int index)

Selects a track.

public abstract java.lang.ObjectsetAudioAttributes(AudioAttributesCompat attributes)

Sets the audio attributes for this MediaPlayer2.

public abstract java.lang.ObjectsetAudioSessionId(int sessionId)

Sets the audio session ID.

public abstract java.lang.ObjectsetAuxEffectSendLevel(float level)

Sets the send level of the player to the attached auxiliary effect.

public abstract voidsetDrmEventCallback(java.util.concurrent.Executor executor, MediaPlayer2.DrmEventCallback eventCallback)

Sets the callback to be invoked when the media source is ready for playback.

public abstract voidsetDrmPropertyString(java.lang.String propertyName, java.lang.String value)

Set a DRM engine plugin String property value.

public abstract voidsetEventCallback(java.util.concurrent.Executor executor, MediaPlayer2.EventCallback eventCallback)

Sets the callback to be invoked when the media source is ready for playback.

public abstract java.lang.ObjectsetMediaItem(MediaItem item)

Sets the media item as described by a MediaItem.

public abstract java.lang.ObjectsetNextMediaItem(MediaItem item)

Sets a single media item as described by a MediaItem which will be played after current media item is finished.

public abstract java.lang.ObjectsetNextMediaItems(java.util.List<MediaItem> items)

Sets a list of media items to be played sequentially after current media item is done.

public abstract voidsetOnDrmConfigHelper(MediaPlayer2.OnDrmConfigHelper listener)

Register a callback to be invoked for configuration of the DRM object before the session is created.

public abstract java.lang.ObjectsetPlaybackParams(PlaybackParams params)

Sets playback rate using PlaybackParams.

public abstract java.lang.ObjectsetPlayerVolume(float volume)

Sets the volume of the audio of the media to play, expressed as a linear multiplier on the audio samples.

public abstract java.lang.ObjectsetSurface(Surface surface)

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

public abstract java.lang.ObjectskipToNext()

Tries to play next media item if applicable.

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

Constructors

public ExoPlayerMediaPlayer2Impl(Context context)

Creates a new ExoPlayer wrapper using the specified context.

Methods

public abstract java.lang.Object notifyWhenCommandLabelReached(java.lang.Object label)

Insert a task in the command queue to help the client to identify whether a batch of commands has been finished. When this command is processed, a notification MediaPlayer2.EventCallback.onCommandLabelReached(MediaPlayer2, Object) will be fired with the given label.

Parameters:

label: An application specific Object used to help to identify the completeness of a batch of commands.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

See also: MediaPlayer2.EventCallback.onCommandLabelReached(MediaPlayer2, Object)

public abstract void clearPendingCommands()

Discards all pending commands.

public abstract boolean cancel(java.lang.Object token)

Cancels the asynchronous call previously submitted.

Parameters:

token: the token which is returned from the asynchronous call.

Returns:

false if the task could not be cancelled; true otherwise.

public abstract void setEventCallback(java.util.concurrent.Executor executor, MediaPlayer2.EventCallback eventCallback)

Sets the callback to be invoked when the media source is ready for playback.

Parameters:

eventCallback: the callback that will be run
executor: the executor through which the callback should be invoked

public abstract void clearEventCallback()

Clears the MediaPlayer2.EventCallback.

public abstract void setDrmEventCallback(java.util.concurrent.Executor executor, MediaPlayer2.DrmEventCallback eventCallback)

Sets the callback to be invoked when the media source is ready for playback.

Parameters:

eventCallback: the callback that will be run
executor: the executor through which the callback should be invoked

public abstract void clearDrmEventCallback()

Clears the MediaPlayer2.DrmEventCallback.

public abstract java.lang.Object setAudioSessionId(int sessionId)

Sets the audio session ID.

Parameters:

sessionId: the audio session ID. The audio session ID is a system wide unique identifier for the audio stream played by this MediaPlayer2 instance. The primary use of the audio session ID is to associate audio effects to a particular instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, this effect will be applied only to the audio content of media players within the same audio session and not to the output mix. When created, a MediaPlayer2 instance automatically generates its own audio session ID. However, it is possible to force this player to be part of an already existing audio session by calling this method. This method must be called before one of the overloaded setMediaItem methods.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setMediaItem(MediaItem item)

Sets the media item as described by a MediaItem.

Parameters:

item: the descriptor of media item you want to play

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract MediaItem getCurrentMediaItem()

Gets the current media item as described by a MediaItem.

Returns:

the current MediaItem

public abstract java.lang.Object prepare()

Prepares the player for playback, asynchronously. After setting the datasource and the display surface, you need to call prepare().

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object play()

Starts or resumes playback. If playback had previously been paused, playback will continue from where it was paused. If playback had reached end of stream and been paused, or never started before, playback will start at the beginning. If the source had not been prepared, the player will prepare the source and play.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object pause()

Pauses playback. Call play() to resume.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object seekTo(long msec, int mode)

Moves the media to specified time position by considering the given mode.

When seekTo is finished, the user will be notified via MediaPlayer2.EventCallback.onInfo(MediaPlayer2, MediaItem, int, int) with MediaPlayer2.CALL_COMPLETED_SEEK_TO. There is at most one active seekTo processed at any time. If there is a to-be-completed seekTo, new seekTo requests will be queued in such a way that only the last request is kept. When current seekTo is completed, the queued request will be processed if that request is different from just-finished seekTo operation, i.e., the requested position or mode is different.

Parameters:

msec: the offset in milliseconds from the start to seek to. When seeking to the given time position, there is no guarantee that the media item has a frame located at the position. When this happens, a frame nearby will be rendered. If msec is negative, time position zero will be used. If msec is larger than duration, duration will be used.
mode: the mode indicating where exactly to seek to.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract long getCurrentPosition()

Gets the current playback position.

Returns:

the current position in milliseconds

public abstract long getDuration()

Gets the duration of the file.

Returns:

the duration in milliseconds, if no duration is available (for example, if streaming live content), -1 is returned.

public abstract long getBufferedPosition()

Gets the current buffered media source position received through progressive downloading. The received buffering percentage indicates how much of the content has been buffered or played. For example a buffering update of 80 percent when half the content has already been played indicates that the next 30 percent of the content to play has been buffered.

Returns:

the current buffered media source position in milliseconds

public abstract int getState()

Gets the current MediaPlayer2 state.

Returns:

the current MediaPlayer2 state.

public abstract java.lang.Object loopCurrent(boolean loop)

Configures the player to loop on the current media item.

Parameters:

loop: true if the current media item is meant to loop.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object skipToNext()

Tries to play next media item if applicable.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setNextMediaItem(MediaItem item)

Sets a single media item as described by a MediaItem which will be played after current media item is finished.

Parameters:

item: the descriptor of media item you want to play after current one

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setNextMediaItems(java.util.List<MediaItem> items)

Sets a list of media items to be played sequentially after current media item is done.

Parameters:

items: the list of media items you want to play after current one

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setAudioAttributes(AudioAttributesCompat attributes)

Sets the audio attributes for this MediaPlayer2. See AudioAttributesCompat for how to build and configure an instance of this class. You must call this method before MediaPlayer2.prepare() in order for the audio attributes to become effective thereafter.

Parameters:

attributes: a non-null set of audio attributes

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract AudioAttributesCompat getAudioAttributes()

Gets the audio attributes for this MediaPlayer2.

Returns:

attributes a set of audio attributes

public abstract int getAudioSessionId()

Returns the audio session ID.

Returns:

the audio session ID. MediaPlayer2.setAudioSessionId(int) Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.

public abstract java.lang.Object attachAuxEffect(int effectId)

Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation effect which can be applied on any sound source that directs a certain amount of its energy to this effect. This amount is defined by setAuxEffectSendLevel(). See MediaPlayer2.setAuxEffectSendLevel(float).

After creating an auxiliary effect (e.g. ), retrieve its ID with and use it when calling this method to attach the player to the effect.

To detach the effect from the player, call this method with a null effect id.

This method must be called after one of the overloaded setMediaItem methods.

Parameters:

effectId: system wide unique id of the effect to attach

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setAuxEffectSendLevel(float level)

Sets the send level of the player to the attached auxiliary effect. See MediaPlayer2.attachAuxEffect(int). The level value range is 0 to 1.0.

By default the send level is 0, so even if an effect is attached to the player this method must be called for the effect to be applied.

Note that the passed level value is a raw scalar. UI controls should be scaled logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, so an appropriate conversion from linear UI input x to level is: x == 0 -> level = 0 0 < x <= R -> level = 10^(72*(x-R)/20/R)

Parameters:

level: send level scalar

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setPlaybackParams(PlaybackParams params)

Sets playback rate using PlaybackParams. The player sets its internal PlaybackParams to the given input. This does not change the player state. For example, if this is called with the speed of 2.0f in MediaPlayer2.PLAYER_STATE_PAUSED, the player will just update internal property and stay paused. Once the client calls MediaPlayer2.play() afterwards, the player will start playback with the given speed. Calling this with zero speed is not allowed.

Parameters:

params: the playback params.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract PlaybackParams getPlaybackParams()

Gets the playback params, containing the current playback rate.

Returns:

the playback params.

public abstract int getVideoWidth()

Returns the width of the video.

Returns:

the width of the video, or 0 if there is no video, no display surface was set, or the width has not been determined yet. The MediaPlayer2.EventCallback can be registered via MediaPlayer2.setEventCallback(Executor, MediaPlayer2.EventCallback) to provide a notification MediaPlayer2.EventCallback.onVideoSizeChanged(MediaPlayer2, MediaItem, int, int) when the width is available.

public abstract int getVideoHeight()

Returns the height of the video.

Returns:

the height of the video, or 0 if there is no video, no display surface was set, or the height has not been determined yet. The MediaPlayer2.EventCallback can be registered via MediaPlayer2.setEventCallback(Executor, MediaPlayer2.EventCallback) to provide a notification MediaPlayer2.EventCallback.onVideoSizeChanged(MediaPlayer2, MediaItem, int, int) when the height is available.

public abstract java.lang.Object setSurface(Surface surface)

Sets the to be used as the sink for the video portion of the media. Setting a Surface will un-set any Surface or SurfaceHolder that was previously set. A null surface will result in only the audio track being played. If the Surface sends frames to a SurfaceTexture, the timestamps returned from SurfaceTexture will have an unspecified zero point. These timestamps cannot be directly compared between different media sources, different instances of the same media source, or multiple runs of the same program. The timestamp is normally monotonically increasing and is unaffected by time-of-day adjustments, but it is reset when the position is set.

Parameters:

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

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract java.lang.Object setPlayerVolume(float volume)

Sets the volume of the audio of the media to play, expressed as a linear multiplier on the audio samples. Note that this volume is specific to the player, and is separate from stream volume used across the platform.
A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified gain. See MediaPlayer2.getMaxPlayerVolume() for the volume range supported by this player.

Parameters:

volume: a value between 0.0f and MediaPlayer2.getMaxPlayerVolume().

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract float getPlayerVolume()

Returns the current volume of this player to this player. Note that it does not take into account the associated stream volume.

Returns:

the player volume.

public abstract java.util.List<MediaPlayer2.TrackInfo> getTrackInfo()

Returns a List of track information.

Returns:

List of track info. The total number of tracks is the array length. Must be called again if an external timed text source has been added after addTimedTextSource method is called.

public abstract int getSelectedTrack(int trackType)

Returns the index of the audio, video, or subtitle track currently selected for playback, The return value is an index into the array returned by MediaPlayer2.getTrackInfo(), and can be used in calls to MediaPlayer2.selectTrack(int) or MediaPlayer2.deselectTrack(int).

Parameters:

trackType: should be one of MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO, MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_AUDIO, or MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE

Returns:

index of the audio, video, or subtitle track currently selected for playback; a negative integer is returned when there is no selected track for trackType or when trackType is not one of audio, video, or subtitle.

See also: MediaPlayer2.getTrackInfo(), MediaPlayer2.selectTrack(int), MediaPlayer2.deselectTrack(int)

public abstract java.lang.Object selectTrack(int index)

Selects a track.

If a MediaPlayer2 is in invalid state, MediaPlayer2.CALL_STATUS_INVALID_OPERATION will be reported with MediaPlayer2.EventCallback.onCallCompleted(MediaPlayer2, MediaItem, int, int). If a MediaPlayer2 is in Playing state, the selected track is presented immediately. If a MediaPlayer2 is not in Started state, it just marks the track to be played.

In any valid state, if it is called multiple times on the same type of track (ie. Video, Audio, Timed Text), the most recent one will be chosen.

The first audio and video tracks are selected by default if available, even though this method is not called. However, no timed text track will be selected until this function is called.

Currently, only timed text tracks or audio tracks can be selected via this method.

Parameters:

index: the index of the track to be selected. The valid range of the index is 0..total number of track - 1. The total number of tracks as well as the type of each individual track can be found by calling MediaPlayer2.getTrackInfo() method.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

See also: MediaPlayer2.getTrackInfo()

public abstract java.lang.Object deselectTrack(int index)

Deselects a track.

Currently, the track must be a timed text track and no audio or video tracks can be deselected. If the timed text track identified by index has not been selected before, it throws an exception.

Parameters:

index: the index of the track to be deselected. The valid range of the index is 0..total number of tracks - 1. The total number of tracks as well as the type of each individual track can be found by calling MediaPlayer2.getTrackInfo() method.

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

See also: MediaPlayer2.getTrackInfo()

public abstract PersistableBundle getMetrics()

Return Metrics data about the current player.

Returns:

a containing the set of attributes and values available for the media being handled by this instance of MediaPlayer2 The attributes are descibed in MediaPlayer2.MetricsConstants. Additional vendor-specific fields may also be present in the return value.

public abstract MediaTimestamp getTimestamp()

Gets current playback position as a MediaTimestamp.

The MediaTimestamp represents how the media time correlates to the system time in a linear fashion using an anchor and a clock rate. During regular playback, the media time moves fairly constantly (though the anchor frame may be rebased to a current system time, the linear correlation stays steady). Therefore, this method does not need to be called often.

To help users get current playback position, this method always anchors the timestamp to the current system time, so MediaTimestamp.getAnchorMediaTimeUs() can be used as current playback position.

Returns:

a MediaTimestamp object if a timestamp is available, or null if no timestamp is available, e.g. because the media player has not been initialized.

See also: MediaTimestamp

public abstract void reset()

Resets the MediaPlayer2 to its uninitialized state. After calling this method, you will have to initialize it again by setting the media item and calling prepare().

public abstract void close()

Releases the resources held by this MediaPlayer2 object. It is considered good practice to call this method when you're done using the MediaPlayer2. In particular, whenever an Activity of an application is paused (its onPause() method is called), or stopped (its onStop() method is called), this method should be invoked to release the MediaPlayer2 object, unless the application has a special need to keep the object around. In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to call this method immediately if a MediaPlayer2 object is no longer needed may also lead to continuous battery consumption for mobile devices, and playback failure for other applications if no multiple instances of the same codec are supported on a device. Even if multiple instances of the same codec are supported, some performance degradation may be expected when unnecessary multiple instances are used at the same time.

public abstract void setOnDrmConfigHelper(MediaPlayer2.OnDrmConfigHelper listener)

Register a callback to be invoked for configuration of the DRM object before the session is created. The callback will be invoked synchronously during the execution of MediaPlayer2.prepareDrm(UUID).

Parameters:

listener: the callback that will be run

public abstract MediaPlayer2.DrmInfo getDrmInfo()

Retrieves the DRM Info associated with the current source

public abstract java.lang.Object prepareDrm(java.util.UUID uuid)

Prepares the DRM for the current source

If MediaPlayer2.OnDrmConfigHelper is registered, it will be called during preparation to allow configuration of the DRM properties before opening the DRM session. Note that the callback is called synchronously in the thread that called MediaPlayer2.prepareDrm(UUID). It should be used only for a series of getDrmPropertyString and setDrmPropertyString calls and refrain from any lengthy operation.

If the device has not been provisioned before, this call also provisions the device which involves accessing the provisioning server and can take a variable time to complete depending on the network connectivity. prepareDrm() runs in non-blocking mode by launching the provisioning in the background and returning. MediaPlayer2.DrmEventCallback.onDrmPrepared(MediaPlayer2, MediaItem, int) will be called when provisioning and preparation has finished. The application should check the status code returned with MediaPlayer2.DrmEventCallback.onDrmPrepared(MediaPlayer2, MediaItem, int) to proceed.

Parameters:

uuid: The UUID of the crypto scheme. If not known beforehand, it can be retrieved from the source through MediaPlayer2.getDrmInfo() or registering MediaPlayer2.DrmEventCallback.onDrmInfo(MediaPlayer2, MediaItem, MediaPlayer2.DrmInfo).

Returns:

a token which can be used to cancel the operation later with MediaPlayer2.cancel(Object).

public abstract void releaseDrm()

Releases the DRM session

The player has to have an active DRM session and be in stopped, or prepared state before this call is made. A reset() call will release the DRM session implicitly.

public abstract MediaDrm.KeyRequest getDrmKeyRequest(byte[] keySetId[], byte[] initData[], java.lang.String mimeType, int keyType, java.util.Map<java.lang.String, java.lang.String> optionalParameters)

A key request/response exchange occurs between the app and a license server to obtain or release keys used to decrypt encrypted content.

getDrmKeyRequest() is used to obtain an opaque key request byte array that is delivered to the license server. The opaque key request byte array is returned in KeyRequest.data. The recommended URL to deliver the key request to is returned in KeyRequest.defaultUrl.

After the app has received the key request response from the server, it should deliver to the response to the DRM engine plugin using the method MediaPlayer2.provideDrmKeyResponse(byte[], byte[]).

Parameters:

keySetId: is the key-set identifier of the offline keys being released when keyType is MediaDrm. It should be set to null for other key requests, when keyType is MediaDrm or MediaDrm.
initData: is the container-specific initialization data when the keyType is MediaDrm or MediaDrm. Its meaning is interpreted based on the mime type provided in the mimeType parameter. It could contain, for example, the content ID, key ID or other data obtained from the content metadata that is required in generating the key request. When the keyType is MediaDrm, it should be set to null.
mimeType: identifies the mime type of the content
keyType: specifies the type of the request. The request may be to acquire keys for streaming, MediaDrm, or for offline content MediaDrm, or to release previously acquired keys (MediaDrm), which are identified by a keySetId.
optionalParameters: are included in the key request message to allow a client application to provide additional message parameters to the server. This may be null if no additional parameters are to be sent.

public abstract byte[] provideDrmKeyResponse(byte[] keySetId[], byte[] response[])

A key response is received from the license server by the app, then it is provided to the DRM engine plugin using provideDrmKeyResponse. When the response is for an offline key request, a key-set identifier is returned that can be used to later restore the keys to a new session with the method MediaPlayer2.restoreDrmKeys(byte[]). When the response is for a streaming or release request, null is returned.

Parameters:

keySetId: When the response is for a release request, keySetId identifies the saved key associated with the release request (i.e., the same keySetId passed to the earlier MediaPlayer2.getDrmKeyRequest(byte[], byte[], String, int, Map) call. It MUST be null when the response is for either streaming or offline key requests.
response: the byte array response from the server

public abstract void restoreDrmKeys(byte[] keySetId[])

Restore persisted offline keys into a new session. keySetId identifies the keys to load, obtained from a prior call to MediaPlayer2.provideDrmKeyResponse(byte[], byte[]).

Parameters:

keySetId: identifies the saved key set to restore

public abstract java.lang.String getDrmPropertyString(java.lang.String propertyName)

Read a DRM engine plugin String property value, given the property name string.

Parameters:

propertyName: the property name Standard fields names are: MediaDrm, MediaDrm, MediaDrm, MediaDrm

public abstract void setDrmPropertyString(java.lang.String propertyName, java.lang.String value)

Set a DRM engine plugin String property value.

Parameters:

propertyName: the property name
value: the property value Standard fields names are: MediaDrm, MediaDrm, MediaDrm, MediaDrm

public void onPrepared(MediaItem mediaItem)

public void onMetadataChanged(MediaItem mediaItem)

public void onSeekCompleted(long positionMs)

public void onBufferingStarted(MediaItem mediaItem)

public void onBufferingEnded(MediaItem mediaItem)

public void onBandwidthSample(MediaItem mediaItem, int bitrateKbps)

public void onVideoRenderingStart(MediaItem mediaItem)

public void onVideoSizeChanged(MediaItem mediaItem, int width, int height)

public void onSubtitleData(MediaItem mediaItem, SubtitleData subtitleData)

public void onTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData)

public void onMediaItemStartedAsNext(MediaItem mediaItem)

public void onMediaItemEnded(MediaItem mediaItem)

public void onLoop(MediaItem mediaItem)

public void onMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp)

public void onPlaybackEnded(MediaItem mediaItem)

public void onError(MediaItem mediaItem, int what)

Source

/*
 * Copyright 2018 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.exoplayer;

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

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaDrm;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PersistableBundle;
import android.util.Log;
import android.util.Pair;
import android.view.Surface;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;
import androidx.media.AudioAttributesCompat;
import androidx.media2.MediaItem;
import androidx.media2.MediaPlayer2;
import androidx.media2.MediaTimestamp;
import androidx.media2.PlaybackParams;
import androidx.media2.SubtitleData;
import androidx.media2.TimedMetaData;
import androidx.media2.exoplayer.external.Player;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;

/**
 * An implementation of {@link MediaPlayer2} based on a repackaged version of ExoPlayer.
 *
 * @hide
 */
@TargetApi(Build.VERSION_CODES.KITKAT)
@RestrictTo(LIBRARY_GROUP)
@SuppressLint("RestrictedApi") // TODO(b/68398926): Remove once RestrictedApi checks are fixed.
public final class ExoPlayerMediaPlayer2Impl extends MediaPlayer2
        implements ExoPlayerWrapper.Listener {

    private static final String TAG = "ExoPlayerMediaPlayer2";

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final ExoPlayerWrapper mPlayer;

    private final Handler mTaskHandler;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    @GuardedBy("mTaskLock")
    final ArrayDeque<Task> mPendingTasks;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Object mTaskLock;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    @GuardedBy("mTaskLock")
    Task mCurrentTask;

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Object mLock;
    @GuardedBy("mLock")
    private Pair<Executor, EventCallback> mExecutorAndEventCallback;
    @SuppressWarnings("unused")
    @GuardedBy("mLock")
    private Pair<Executor, DrmEventCallback> mExecutorAndDrmEventCallback;
    @GuardedBy("mLock")
    private HandlerThread mHandlerThread;

    /** Creates a new ExoPlayer wrapper using the specified context. */
    public ExoPlayerMediaPlayer2Impl(@NonNull Context context) {
        mHandlerThread = new HandlerThread("ExoMediaPlayer2Thread");
        mHandlerThread.start();
        mPlayer = new ExoPlayerWrapper(
                context.getApplicationContext(),
                /* listener= */ this,
                mHandlerThread.getLooper());
        // Player callbacks will be called on the task handler thread.
        mTaskHandler = new Handler(mPlayer.getLooper());
        mPendingTasks = new ArrayDeque<>();
        mTaskLock = new Object();
        mLock = new Object();
        resetPlayer();
    }

    // Command queue and events implementation.
    // TODO: Consider refactoring to share implementation with MediaPlayer2Impl.

    @Override
    public Object notifyWhenCommandLabelReached(@NonNull final Object label) {
        return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
            @Override
            void process() {
                notifyMediaPlayer2Event(new Mp2EventNotifier() {
                    @Override
                    public void notify(EventCallback cb) {
                        cb.onCommandLabelReached(ExoPlayerMediaPlayer2Impl.this, label);
                    }
                });
            }
        });
    }

    @Override
    public void clearPendingCommands() {
        synchronized (mTaskLock) {
            mPendingTasks.clear();
        }
    }

    @Override
    public boolean cancel(Object token) {
        synchronized (mTaskLock) {
            return mPendingTasks.remove(token);
        }
    }

    private Object addTask(Task task) {
        synchronized (mTaskLock) {
            mPendingTasks.add(task);
            processPendingTask();
        }
        return task;
    }

    @GuardedBy("mTaskLock")
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void processPendingTask() {
        if (mCurrentTask == null && !mPendingTasks.isEmpty()) {
            Task task = mPendingTasks.removeFirst();
            mCurrentTask = task;
            mTaskHandler.post(task);
        }
    }

    @Override
    public void setEventCallback(@NonNull Executor executor, @NonNull EventCallback eventCallback) {
        Preconditions.checkNotNull(executor);
        Preconditions.checkNotNull(eventCallback);
        synchronized (mLock) {
            mExecutorAndEventCallback = Pair.create(executor, eventCallback);
        }
    }

    @Override
    public void clearEventCallback() {
        synchronized (mLock) {
            mExecutorAndEventCallback = null;
        }
    }

    @Override
    public void setDrmEventCallback(@NonNull Executor executor,
            @NonNull DrmEventCallback eventCallback) {
        Preconditions.checkNotNull(executor);
        Preconditions.checkNotNull(eventCallback);
        synchronized (mLock) {
            mExecutorAndDrmEventCallback = Pair.create(executor, eventCallback);
        }
    }

    @Override
    public void clearDrmEventCallback() {
        synchronized (mLock) {
            mExecutorAndDrmEventCallback = null;
        }
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) {
        final Pair<Executor, EventCallback> executorAndEventCallback;
        synchronized (mLock) {
            executorAndEventCallback = mExecutorAndEventCallback;
        }
        if (executorAndEventCallback != null) {
            Executor executor = executorAndEventCallback.first;
            final EventCallback eventCallback = executorAndEventCallback.second;
            try {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        notifier.notify(eventCallback);
                    }
                });
            } catch (RejectedExecutionException e) {
                // The given executor is shutting down.
                Log.w(TAG, "The given executor is shutting down. Ignoring the player event.");
            }
        }
    }

    // Player implementation.

    @Override
    public Object setAudioSessionId(final int sessionId) {
        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
            @Override
            void process() {
                mPlayer.setAudioSessionId(sessionId);
            }
        });
    }

    @Override
    public Object setMediaItem(@NonNull final MediaItem item) {
        return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
            @Override
            void process() {
                mPlayer.setMediaItem(item);
            }
        });
    }

    @Override
    public MediaItem getCurrentMediaItem() {
        return runPlayerCallableBlocking(new Callable<MediaItem>() {
            @Override
            public MediaItem call() throws Exception {
                return mPlayer.getCurrentMediaItem();
            }
        });
    }

    @Override
    public Object prepare() {
        return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
            @Override
            void process() {
                mPlayer.prepare();
            }
        });
    }

    @Override
    public Object play() {
        return addTask(new Task(CALL_COMPLETED_PLAY, false) {
            @Override
            void process() {
                mPlayer.play();
            }
        });
    }

    @Override
    public Object pause() {
        return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
            @Override
            void process() {
                mPlayer.pause();
            }
        });
    }

    @Override
    public Object seekTo(final long msec, final int mode) {
        return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
            @Override
            void process() {
                mPlayer.seekTo(msec, mode);
            }
        });
    }

    @Override
    public long getCurrentPosition() {
        return runPlayerCallableBlocking(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                return mPlayer.getCurrentPosition();
            }
        });
    }

    @Override
    public long getDuration() {
        return runPlayerCallableBlocking(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                return mPlayer.getDuration();
            }
        });
    }

    @Override
    public long getBufferedPosition() {
        return runPlayerCallableBlocking(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                return mPlayer.getBufferedPosition();
            }
        });
    }

    @Override
    public @MediaPlayer2.MediaPlayer2State int getState() {
        return runPlayerCallableBlocking(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return mPlayer.getState();
            }
        });
    }

    @Override
    public Object loopCurrent(final boolean loop) {
        return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
            @Override
            void process() {
                mPlayer.loopCurrent(loop);
            }
        });
    }

    @Override
    public Object skipToNext() {
        return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
            @Override
            void process() {
                mPlayer.skipToNext();
            }
        });
    }

    @Override
    public Object setNextMediaItem(@NonNull final MediaItem item) {
        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
            @Override
            void process() {
                mPlayer.setNextMediaItem(item);
            }
        });
    }

    @Override
    public Object setNextMediaItems(@NonNull final List<MediaItem> items) {
        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
            @Override
            void process() {
                mPlayer.setNextMediaItems(items);
            }
        });
    }

    @Override
    public Object setAudioAttributes(@NonNull final AudioAttributesCompat attributes) {
        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
            @Override
            void process() {
                mPlayer.setAudioAttributes(attributes);
            }
        });
    }

    @Override
    public AudioAttributesCompat getAudioAttributes() {
        return runPlayerCallableBlocking(new Callable<AudioAttributesCompat>() {
            @Override
            public AudioAttributesCompat call() throws Exception {
                return mPlayer.getAudioAttributes();
            }
        });
    }

    @Override
    public int getAudioSessionId() {
        return runPlayerCallableBlocking(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return mPlayer.getAudioSessionId();
            }
        });
    }

    @Override
    public Object attachAuxEffect(final int effectId) {
        return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
            @Override
            void process() {
                mPlayer.attachAuxEffect(effectId);
            }
        });
    }

    @Override
    public Object setAuxEffectSendLevel(final float auxEffectSendLevel) {
        return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
            @Override
            void process() {
                mPlayer.setAuxEffectSendLevel(auxEffectSendLevel);
            }
        });
    }

    @Override
    public Object setPlaybackParams(@NonNull final PlaybackParams params) {
        return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
            @Override
            void process() {
                mPlayer.setPlaybackParams(params);
            }
        });
    }

    @Override
    @NonNull
    public PlaybackParams getPlaybackParams() {
        return runPlayerCallableBlocking(new Callable<PlaybackParams>() {
            @Override
            public PlaybackParams call() throws Exception {
                return mPlayer.getPlaybackParams();
            }
        });
    }

    @Override
    public int getVideoWidth() {
        return runPlayerCallableBlocking(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return mPlayer.getVideoWidth();
            }
        });
    }

    @Override
    public int getVideoHeight() {
        return runPlayerCallableBlocking(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return mPlayer.getVideoHeight();
            }
        });
    }

    @Override
    public Object setSurface(final Surface surface) {
        return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
            @Override
            void process() {
                mPlayer.setSurface(surface);
            }
        });
    }

    @Override
    public Object setPlayerVolume(final float volume) {
        return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
            @Override
            void process() {
                mPlayer.setVolume(volume);
            }
        });
    }

    @Override
    public float getPlayerVolume() {
        return runPlayerCallableBlocking(new Callable<Float>() {
            @Override
            public Float call() throws Exception {
                return mPlayer.getVolume();
            }
        });
    }

    @Override
    public List<TrackInfo> getTrackInfo() {
        return runPlayerCallableBlocking(new Callable<List<TrackInfo>>() {
            @Override
            public List<TrackInfo> call() throws Exception {
                return mPlayer.getTrackInfo();
            }
        });
    }
    @Override
    public int getSelectedTrack(final int trackType) {
        return runPlayerCallableBlocking(new Callable<Integer>() {
            @Override
            public Integer call() {
                return mPlayer.getSelectedTrack(trackType);
            }
        });
    }

    @Override
    public Object selectTrack(final int index) {
        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
            @Override
            void process() {
                mPlayer.selectTrack(index);
            }
        });
    }

    @Override
    public Object deselectTrack(final int index) {
        return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
            @Override
            void process() {
                mPlayer.deselectTrack(index);
            }
        });
    }

    @Override
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public PersistableBundle getMetrics() {
        if (Build.VERSION.SDK_INT < 21) {
            return null;
        }
        return runPlayerCallableBlocking(new Callable<PersistableBundle>() {
            @Override
            public PersistableBundle call() throws Exception {
                return mPlayer.getMetricsV21();
            }
        });
    }

    @Override
    public MediaTimestamp getTimestamp() {
        return runPlayerCallableBlocking(new Callable<MediaTimestamp>() {
            @Override
            public MediaTimestamp call() {
                return mPlayer.getTimestamp();
            }
        });
    }

    @Override
    public void reset() {
        clearPendingCommands();

        // Make sure that the current task finishes.
        Task currentTask;
        synchronized (mTaskLock) {
            currentTask = mCurrentTask;
        }
        if (currentTask != null) {
            synchronized (currentTask) {
                try {
                    while (!currentTask.mDone) {
                        currentTask.wait();
                    }
                } catch (InterruptedException e) {
                    // Suppress interruption.
                }
            }
        }
        mTaskHandler.removeCallbacksAndMessages(null);
        runPlayerCallableBlocking(new Callable<Void>() {
            @Override
            public Void call() {
                mPlayer.reset();
                return null;
            }
        });
    }

    @Override
    public void close() {
        clearEventCallback();
        HandlerThread handlerThread;
        synchronized (mLock) {
            handlerThread = mHandlerThread;
            if (handlerThread == null) {
                return;
            }
            mHandlerThread = null;
        }
        runPlayerCallableBlocking(new Callable<Void>() {
            @Override
            public Void call() {
                mPlayer.close();
                return null;
            }
        });
        handlerThread.quit();
    }

    @Override
    public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
        throw new UnsupportedOperationException();
    }

    @Override
    public DrmInfo getDrmInfo() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object prepareDrm(@NonNull final UUID uuid) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void releaseDrm() {
        throw new UnsupportedOperationException();
    }

    @Override
    @NonNull
    public MediaDrm.KeyRequest getDrmKeyRequest(byte[] keySetId, byte[] initData, String mimeType,
            int keyType, Map<String, String> optionalParameters) {
        throw new UnsupportedOperationException();
    }

    @Override
    public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void restoreDrmKeys(@NonNull byte[] keySetId) {
        throw new UnsupportedOperationException();
    }

    @Override
    @NonNull
    public String getDrmPropertyString(@NonNull String propertyName) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setDrmPropertyString(@NonNull String propertyName, @NonNull String value) {
        throw new UnsupportedOperationException();
    }

    // ExoPlayerWrapper.Listener implementation.

    @Override
    public void onPrepared(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_PREPARED);
        synchronized (mTaskLock) {
            if (mCurrentTask != null
                    && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
                    && ObjectsCompat.equals(mCurrentTask.mDSD, mediaItem)
                    && mCurrentTask.mNeedToWaitForEventToComplete) {
                mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
                mCurrentTask = null;
                processPendingTask();
            }
        }
    }

    @Override
    public void onMetadataChanged(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_METADATA_UPDATE);
    }

    @Override
    public void onSeekCompleted(long positionMs) {
        synchronized (mTaskLock) {
            if (mCurrentTask != null
                    && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
                    && mCurrentTask.mNeedToWaitForEventToComplete) {
                mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
                mCurrentTask = null;
                processPendingTask();
            }
        }
    }

    @Override
    public void onBufferingStarted(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_BUFFERING_START);
    }

    @Override
    public void onBufferingEnded(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_BUFFERING_END);
    }

    @Override
    public void onBandwidthSample(MediaItem mediaItem, int bitrateKbps) {
        notifyOnInfo(mediaItem, MEDIA_INFO_NETWORK_BANDWIDTH, bitrateKbps);
    }

    @Override
    public void onVideoRenderingStart(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_VIDEO_RENDERING_START);
    }

    @Override
    public void onVideoSizeChanged(final MediaItem mediaItem, final int width, final int height) {
        notifyMediaPlayer2Event(new ExoPlayerMediaPlayer2Impl.Mp2EventNotifier() {
            @Override
            public void notify(MediaPlayer2.EventCallback callback) {
                callback.onVideoSizeChanged(
                        ExoPlayerMediaPlayer2Impl.this,
                        mediaItem,
                        width,
                        height);
            }
        });
    }

    @Override
    public void onSubtitleData(final MediaItem mediaItem, final SubtitleData subtitleData) {
        notifyMediaPlayer2Event(new Mp2EventNotifier() {
            @Override
            public void notify(EventCallback cb) {
                cb.onSubtitleData(
                        ExoPlayerMediaPlayer2Impl.this, mediaItem, subtitleData);
            }
        });
    }

    @Override
    public void onTimedMetadata(final MediaItem mediaItem, final TimedMetaData timedMetaData) {
        notifyMediaPlayer2Event(new Mp2EventNotifier() {
            @Override
            public void notify(EventCallback cb) {
                cb.onTimedMetaDataAvailable(
                        ExoPlayerMediaPlayer2Impl.this, mediaItem, timedMetaData);
            }
        });
    }

    @Override
    public void onMediaItemStartedAsNext(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_START);
    }

    @Override
    public void onMediaItemEnded(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_END);
    }

    @Override
    public void onLoop(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_REPEAT);
    }

    @Override
    public void onMediaTimeDiscontinuity(
            final MediaItem mediaItem, final MediaTimestamp mediaTimestamp) {
        notifyMediaPlayer2Event(new Mp2EventNotifier() {
            @Override
            public void notify(EventCallback cb) {
                cb.onMediaTimeDiscontinuity(
                        ExoPlayerMediaPlayer2Impl.this, mediaItem, mediaTimestamp);
            }
        });
    }

    @Override
    public void onPlaybackEnded(MediaItem mediaItem) {
        notifyOnInfo(mediaItem, MEDIA_INFO_DATA_SOURCE_LIST_END);
    }

    @Override
    public void onError(final MediaItem mediaItem, final int what) {
        synchronized (mTaskLock) {
            if (mCurrentTask != null
                    && mCurrentTask.mNeedToWaitForEventToComplete) {
                mCurrentTask.sendCompleteNotification(CALL_STATUS_ERROR_UNKNOWN);
                mCurrentTask = null;
                processPendingTask();
            }
        }
        notifyMediaPlayer2Event(new Mp2EventNotifier() {
            @Override
            public void notify(EventCallback cb) {
                cb.onError(ExoPlayerMediaPlayer2Impl.this, mediaItem, what, /* extra= */ 0);
            }
        });
    }

    // Internal functionality.

    private void notifyOnInfo(MediaItem mediaItem, int what) {
        notifyOnInfo(mediaItem, what, /* extra= */ 0);
    }

    private void notifyOnInfo(final MediaItem mediaItem, final int what, final int extra) {
        notifyMediaPlayer2Event(new ExoPlayerMediaPlayer2Impl.Mp2EventNotifier() {
            @Override
            public void notify(MediaPlayer2.EventCallback callback) {
                callback.onInfo(ExoPlayerMediaPlayer2Impl.this, mediaItem, what, extra);
            }
        });
    }

    private void resetPlayer() {
        runPlayerCallableBlocking(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                mPlayer.reset();
                return null;
            }
        });
    }

    /**
     * Runs the specified callable on the player thread, blocking the calling thread until a result
     * is returned.
     *
     * <p>Note: ExoPlayer methods apart from {@link Player#release} are asynchronous, so calling
     * player methods will not block the caller thread for a substantial amount of time.
     */
    private <T> T runPlayerCallableBlocking(final Callable<T> callable) {
        final ResolvableFuture<T> future = ResolvableFuture.create();
        boolean success = mTaskHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    future.set(callable.call());
                } catch (Throwable e) {
                    future.setException(e);
                }
            }
        });
        Preconditions.checkState(success);
        try {
            T result;
            boolean wasInterrupted = false;
            while (true) {
                try {
                    result = future.get();
                    break;
                } catch (InterruptedException e) {
                    // We always wait for player calls to return.
                    wasInterrupted = true;
                }
            }
            if (wasInterrupted) {
                Thread.currentThread().interrupt();
            }
            return result;
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            Log.e(TAG, "Internal player error", cause);
            throw new IllegalStateException(cause);
        }
    }

    private interface Mp2EventNotifier {
        void notify(EventCallback callback);
    }

    private abstract class Task implements Runnable {
        final int mMediaCallType;
        final boolean mNeedToWaitForEventToComplete;

        MediaItem mDSD;
        @GuardedBy("this")
        boolean mDone;

        Task(int mediaCallType, boolean needToWaitForEventToComplete) {
            mMediaCallType = mediaCallType;
            mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
        }

        abstract void process() throws IOException, NoDrmSchemeException;

        @Override
        public void run() {
            int status = CALL_STATUS_NO_ERROR;
            boolean skip = false;
            if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
                synchronized (mTaskLock) {
                    Task next = mPendingTasks.peekFirst();
                    if (next != null && next.mMediaCallType == CALL_COMPLETED_SEEK_TO) {
                        skip = true;
                    }
                }
            }
            if (!skip) {
                try {
                    if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
                            && mPlayer.hasError()) {
                        status = CALL_STATUS_INVALID_OPERATION;
                    } else {
                        process();
                    }
                } catch (IllegalStateException e) {
                    status = CALL_STATUS_INVALID_OPERATION;
                } catch (IllegalArgumentException e) {
                    status = CALL_STATUS_BAD_VALUE;
                } catch (SecurityException e) {
                    status = CALL_STATUS_PERMISSION_DENIED;
                } catch (IOException e) {
                    status = CALL_STATUS_ERROR_IO;
                } catch (Exception e) {
                    status = CALL_STATUS_ERROR_UNKNOWN;
                }
            } else {
                status = CALL_STATUS_SKIPPED;
            }

            mDSD = mPlayer.getCurrentMediaItem();

            if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR || skip) {
                sendCompleteNotification(status);

                synchronized (mTaskLock) {
                    mCurrentTask = null;
                    processPendingTask();
                }
            }
            // reset() might be waiting for this task. Notify that the task is done.
            synchronized (this) {
                mDone = true;
                notifyAll();
            }
        }

        void sendCompleteNotification(final int status) {
            if (mMediaCallType >= SEPARATE_CALL_COMPLETE_CALLBACK_START) {
                // These methods have a separate call complete callback and it should be already
                // called within process().
                return;
            }
            notifyMediaPlayer2Event(new Mp2EventNotifier() {
                @Override
                public void notify(EventCallback callback) {
                    callback.onCallCompleted(
                            ExoPlayerMediaPlayer2Impl.this, mDSD, mMediaCallType, status);
                }
            });
        }
    }

}