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 MediaPlayer2 | CALL_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 |
Methods |
---|
public abstract java.lang.Object | attachAuxEffect(int effectId)
Attaches an auxiliary effect to the player. |
public abstract boolean | cancel(java.lang.Object token)
Cancels the asynchronous call previously submitted. |
public abstract void | clearDrmEventCallback()
Clears the MediaPlayer2.DrmEventCallback. |
public abstract void | clearEventCallback()
Clears the MediaPlayer2.EventCallback. |
public abstract void | clearPendingCommands()
Discards all pending commands. |
public abstract void | close()
Releases the resources held by this MediaPlayer2 object. |
public abstract java.lang.Object | deselectTrack(int index)
Deselects a track. |
public abstract AudioAttributesCompat | getAudioAttributes()
Gets the audio attributes for this MediaPlayer2. |
public abstract int | getAudioSessionId()
Returns the audio session ID. |
public abstract long | getBufferedPosition()
Gets the current buffered media source position received through progressive downloading. |
public abstract MediaItem | getCurrentMediaItem()
Gets the current media item as described by a MediaItem. |
public abstract long | getCurrentPosition()
Gets the current playback position. |
public abstract MediaPlayer2.DrmInfo | getDrmInfo()
Retrieves the DRM Info associated with the current source |
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. |
public abstract java.lang.String | getDrmPropertyString(java.lang.String propertyName)
Read a DRM engine plugin String property value, given the property name string. |
public abstract long | getDuration()
Gets the duration of the file. |
public abstract PersistableBundle | getMetrics()
Return Metrics data about the current player. |
public abstract PlaybackParams | getPlaybackParams()
Gets the playback params, containing the current playback rate. |
public abstract float | getPlayerVolume()
Returns the current volume of this player to this player. |
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). |
public abstract int | getState()
Gets the current MediaPlayer2 state. |
public abstract MediaTimestamp | getTimestamp()
Gets current playback position as a MediaTimestamp. |
public abstract java.util.List<MediaPlayer2.TrackInfo> | getTrackInfo()
Returns a List of track information. |
public abstract int | getVideoHeight()
Returns the height of the video. |
public abstract int | getVideoWidth()
Returns the width of the video. |
public abstract java.lang.Object | loopCurrent(boolean loop)
Configures the player to loop on the current media item. |
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. |
public void | onBandwidthSample(MediaItem mediaItem, int bitrateKbps)
|
public void | onBufferingEnded(MediaItem mediaItem)
|
public void | onBufferingStarted(MediaItem mediaItem)
|
public void | onError(MediaItem mediaItem, int what)
|
public void | onLoop(MediaItem mediaItem)
|
public void | onMediaItemEnded(MediaItem mediaItem)
|
public void | onMediaItemStartedAsNext(MediaItem mediaItem)
|
public void | onMediaTimeDiscontinuity(MediaItem mediaItem, MediaTimestamp mediaTimestamp)
|
public void | onMetadataChanged(MediaItem mediaItem)
|
public void | onPlaybackEnded(MediaItem mediaItem)
|
public void | onPrepared(MediaItem mediaItem)
|
public void | onSeekCompleted(long positionMs)
|
public void | onSubtitleData(MediaItem mediaItem, SubtitleData subtitleData)
|
public void | onTimedMetadata(MediaItem mediaItem, TimedMetaData timedMetaData)
|
public void | onVideoRenderingStart(MediaItem mediaItem)
|
public void | onVideoSizeChanged(MediaItem mediaItem, int width, int height)
|
public abstract java.lang.Object | pause()
Pauses playback. |
public abstract java.lang.Object | play()
Starts or resumes playback. |
public abstract java.lang.Object | prepare()
Prepares the player for playback, asynchronously. |
public abstract java.lang.Object | prepareDrm(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 void | releaseDrm()
Releases the DRM session |
public abstract void | reset()
Resets the MediaPlayer2 to its uninitialized state. |
public abstract void | restoreDrmKeys(byte[] keySetId[])
Restore persisted offline keys into a new session. |
public abstract java.lang.Object | seekTo(long msec, int mode)
Moves the media to specified time position by considering the given mode. |
public abstract java.lang.Object | selectTrack(int index)
Selects a track. |
public abstract java.lang.Object | setAudioAttributes(AudioAttributesCompat attributes)
Sets the audio attributes for this MediaPlayer2. |
public abstract java.lang.Object | setAudioSessionId(int sessionId)
Sets the audio session ID. |
public abstract java.lang.Object | setAuxEffectSendLevel(float level)
Sets the send level of the player to the attached auxiliary effect. |
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. |
public abstract void | setDrmPropertyString(java.lang.String propertyName, java.lang.String value)
Set a DRM engine plugin String property value. |
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. |
public abstract java.lang.Object | setMediaItem(MediaItem item)
Sets the media item as described by a MediaItem. |
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. |
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. |
public abstract void | setOnDrmConfigHelper(MediaPlayer2.OnDrmConfigHelper listener)
Register a callback to be invoked for configuration of the DRM object before
the session is created. |
public abstract java.lang.Object | setPlaybackParams(PlaybackParams params)
Sets playback rate using PlaybackParams. |
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. |
public abstract java.lang.Object | setSurface(Surface surface)
Sets the to be used as the sink for the video portion of
the media. |
public abstract java.lang.Object | skipToNext()
Tries to play next media item if applicable. |
from MediaPlayer2 | create, getMaxPlayerVolume, seekTo |
from java.lang.Object | clone, 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.
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.
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).
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).
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).
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.
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.
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
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
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
onMediaItemStartedAsNext(
MediaItem mediaItem)
public void
onMediaItemEnded(
MediaItem mediaItem)
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);
}
});
}
}
}