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
MediaPlayer2 implementation for platform version P (28).
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 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 float | getMaxPlayerVolume()
|
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 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 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, seekTo |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
MediaPlayer2Impl()
Default constructor.
When done with the MediaPlayer2Impl, you should call MediaPlayer2Impl.close(),
to free the resources. If not released, too many MediaPlayer2Impl instances may
result in an exception.
Methods
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.
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.
close() may be safely called after a prior close().
This class implements the Java AutoCloseable interface and
may be used with try-with-resources.
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
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
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
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 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.
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 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 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
MediaItem getCurrentMediaItem()
Gets the current media item as described by a MediaItem.
Returns:
the current MediaItem
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
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 float
getMaxPlayerVolume()
Returns:
the maximum volume that can be used in MediaPlayer2.setPlayerVolume(float).
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 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 void
clearPendingCommands()
Discards all pending commands.
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 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 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 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).
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
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 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 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.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()
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.
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
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.
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
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;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.annotation.TargetApi;
import android.media.AudioAttributes;
import android.media.DeniedByServerException;
import android.media.MediaDataSource;
import android.media.MediaDrm;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.media.ResourceBusyException;
import android.media.UnsupportedSchemeException;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.text.TextUtils;
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.collection.ArrayMap;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;
import androidx.media.AudioAttributesCompat;
import androidx.media2.SessionPlayer.BuffState;
import androidx.media2.SessionPlayer.PlayerState;
import androidx.media2.common.TrackInfoImpl;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* MediaPlayer2 implementation for platform version P (28).
* @hide
*/
@TargetApi(Build.VERSION_CODES.P)
@RestrictTo(LIBRARY_GROUP)
public final class MediaPlayer2Impl extends MediaPlayer2 {
private static final String TAG = "MediaPlayer2Impl";
private static final int SOURCE_STATE_ERROR = -1;
private static final int SOURCE_STATE_INIT = 0;
private static final int SOURCE_STATE_PREPARING = 1;
private static final int SOURCE_STATE_PREPARED = 2;
@SuppressWarnings("WeakerAccess") /* synthetic access */
static final android.media.PlaybackParams DEFAULT_PLAYBACK_PARAMS =
new android.media.PlaybackParams().allowDefaults();
@SuppressWarnings("WeakerAccess") /* synthetic access */
static ArrayMap<Integer, Integer> sInfoEventMap;
@SuppressWarnings("WeakerAccess") /* synthetic access */
static ArrayMap<Integer, Integer> sErrorEventExtraMap;
@SuppressWarnings("WeakerAccess") /* synthetic access */
static ArrayMap<Integer, Integer> sStateMap;
static ArrayMap<Integer, Integer> sTrackTypeMap;
static {
sInfoEventMap = new ArrayMap<>();
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_UNKNOWN, MEDIA_INFO_UNKNOWN);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT, MEDIA_INFO_UNKNOWN);
sInfoEventMap.put(
MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, MEDIA_INFO_VIDEO_RENDERING_START);
sInfoEventMap.put(
MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, MEDIA_INFO_VIDEO_TRACK_LAGGING);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_START, MEDIA_INFO_BUFFERING_START);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_END, MEDIA_INFO_BUFFERING_END);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, MEDIA_INFO_BAD_INTERLEAVING);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, MEDIA_INFO_NOT_SEEKABLE);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_METADATA_UPDATE, MEDIA_INFO_METADATA_UPDATE);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, MEDIA_INFO_AUDIO_NOT_PLAYING);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, MEDIA_INFO_VIDEO_NOT_PLAYING);
sInfoEventMap.put(
MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, MEDIA_INFO_UNSUPPORTED_SUBTITLE);
sInfoEventMap.put(MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT, MEDIA_INFO_SUBTITLE_TIMED_OUT);
sErrorEventExtraMap = new ArrayMap<>();
sErrorEventExtraMap.put(MediaPlayer.MEDIA_ERROR_IO, MEDIA_ERROR_IO);
sErrorEventExtraMap.put(MediaPlayer.MEDIA_ERROR_MALFORMED, MEDIA_ERROR_MALFORMED);
sErrorEventExtraMap.put(MediaPlayer.MEDIA_ERROR_UNSUPPORTED, MEDIA_ERROR_UNSUPPORTED);
sErrorEventExtraMap.put(MediaPlayer.MEDIA_ERROR_TIMED_OUT, MEDIA_ERROR_TIMED_OUT);
sStateMap = new ArrayMap<>();
sStateMap.put(PLAYER_STATE_IDLE, SessionPlayer.PLAYER_STATE_IDLE);
sStateMap.put(PLAYER_STATE_PREPARED, SessionPlayer.PLAYER_STATE_PAUSED);
sStateMap.put(PLAYER_STATE_PAUSED, SessionPlayer.PLAYER_STATE_PAUSED);
sStateMap.put(PLAYER_STATE_PLAYING, SessionPlayer.PLAYER_STATE_PLAYING);
sStateMap.put(PLAYER_STATE_ERROR, SessionPlayer.PLAYER_STATE_ERROR);
sTrackTypeMap = new ArrayMap<>();
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN,
TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN);
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO,
TrackInfo.MEDIA_TRACK_TYPE_VIDEO);
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO,
TrackInfo.MEDIA_TRACK_TYPE_AUDIO);
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT,
TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN);
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE,
TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE);
sTrackTypeMap.put(MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA,
TrackInfo.MEDIA_TRACK_TYPE_METADATA);
}
MediaPlayerSourceQueue mPlayer;
private HandlerThread mHandlerThread;
private final Handler mEndPositionHandler;
private final Handler mTaskHandler;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Object mTaskLock = new Object();
@GuardedBy("mTaskLock")
@SuppressWarnings("WeakerAccess") /* synthetic access */
final ArrayDeque<Task> mPendingTasks = new ArrayDeque<>();
@GuardedBy("mTaskLock")
@SuppressWarnings("WeakerAccess") /* synthetic access */
Task mCurrentTask;
private final Object mLock = new Object();
//--- guarded by |mLock| start
private Pair<Executor, EventCallback> mMp2EventCallbackRecord;
private Pair<Executor, DrmEventCallback> mDrmEventCallbackRecord;
//--- guarded by |mLock| end
@SuppressWarnings("WeakerAccess") /* synthetic access */
void handleDataSourceError(final DataSourceError err) {
if (err == null) {
return;
}
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
callback.onError(MediaPlayer2Impl.this, err.mDSD, err.mWhat, err.mExtra);
}
});
}
/**
* Default constructor.
* <p>When done with the MediaPlayer2Impl, you should call {@link #close()},
* to free the resources. If not released, too many MediaPlayer2Impl instances may
* result in an exception.</p>
*/
public MediaPlayer2Impl() {
mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
mHandlerThread.start();
Looper looper = mHandlerThread.getLooper();
mEndPositionHandler = new Handler(looper);
mTaskHandler = new Handler(looper);
// TODO: To make sure MediaPlayer1 listeners work, the caller thread should have a looper.
// Fix the framework or document this behavior.
mPlayer = new MediaPlayerSourceQueue();
}
@Override
public boolean cancel(Object token) {
synchronized (mTaskLock) {
return mPendingTasks.remove(token);
}
}
/**
* Releases the resources held by this {@code 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.
*
* {@code close()} may be safely called after a prior {@code close()}.
* This class implements the Java {@code AutoCloseable} interface and
* may be used with try-with-resources.
*/
@Override
public void close() {
clearEventCallback();
clearDrmEventCallback();
mPlayer.release();
if (mHandlerThread != null) {
mHandlerThread.quitSafely();
mHandlerThread = null;
}
}
@Override
public Object play() {
return addTask(new Task(CALL_COMPLETED_PLAY, false) {
@Override
void process() {
mPlayer.play();
}
});
}
@Override
public Object prepare() {
return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
@Override
void process() throws IOException {
mPlayer.prepareAsync();
}
});
}
@Override
public Object pause() {
return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
@Override
void process() {
mPlayer.pause();
}
});
}
@Override
public Object skipToNext() {
return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
@Override
void process() {
mPlayer.skipToNext();
}
});
}
@Override
public long getCurrentPosition() {
return mPlayer.getCurrentPosition();
}
@Override
public long getDuration() {
return mPlayer.getDuration();
}
@Override
public long getBufferedPosition() {
// Use cached buffered percent for now.
return mPlayer.getBufferedPosition();
}
@Override
public @MediaPlayer2State int getState() {
return mPlayer.getMediaPlayer2State();
}
@SessionPlayer.PlayerState int getPlayerState() {
return mPlayer.getPlayerState();
}
/**
* Gets the current buffering state of the player.
* During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
* buffered.
*/
@SessionPlayer.BuffState int getBufferingState() {
return mPlayer.getBufferingState();
}
@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 @Nullable AudioAttributesCompat getAudioAttributes() {
return mPlayer.getAudioAttributes();
}
@Override
public Object setMediaItem(@NonNull final MediaItem item) {
return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
@Override
void process() {
Preconditions.checkArgument(item != null, "the MediaItem cannot be null");
// TODO: setMediaItem could update exist media item
try {
mPlayer.setFirst(item);
} catch (IOException e) {
Log.e(TAG, "process: setMediaItem", e);
}
}
});
}
@Override
public Object setNextMediaItem(@NonNull final MediaItem item) {
return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
@Override
void process() {
Preconditions.checkArgument(item != null, "the MediaItem cannot be null");
handleDataSourceError(mPlayer.setNext(item));
}
});
}
@Override
public Object setNextMediaItems(@NonNull final List<MediaItem> items) {
return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
@Override
void process() {
if (items == null || items.size() == 0) {
throw new IllegalArgumentException("media item list cannot be null or empty.");
}
for (MediaItem item : items) {
if (item == null) {
throw new IllegalArgumentException(
"MediaItem in the source list cannot be null.");
}
}
handleDataSourceError(mPlayer.setNextMultiple(items));
}
});
}
@Override
public @Nullable MediaItem getCurrentMediaItem() {
return mPlayer.getFirst().getDSD();
}
@Override
public Object loopCurrent(final boolean loop) {
return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
@Override
void process() {
mPlayer.setLooping(loop);
}
});
}
@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 mPlayer.getVolume();
}
@Override
public float getMaxPlayerVolume() {
return 1.0f;
}
@Override
public Object notifyWhenCommandLabelReached(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(MediaPlayer2Impl.this, label);
}
});
}
});
}
@Override
public Object setSurface(final Surface surface) {
return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
@Override
void process() {
mPlayer.setSurface(surface);
}
});
}
@Override
public void clearPendingCommands() {
synchronized (mTaskLock) {
mPendingTasks.clear();
}
}
private Object addTask(Task task) {
synchronized (mTaskLock) {
mPendingTasks.add(task);
processPendingTask_l();
}
return task;
}
@GuardedBy("mTaskLock")
@SuppressWarnings("WeakerAccess") /* synthetic access */
void processPendingTask_l() {
if (mCurrentTask != null) {
return;
}
if (!mPendingTasks.isEmpty()) {
Task task = mPendingTasks.removeFirst();
mCurrentTask = task;
mTaskHandler.post(task);
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static void handleDataSource(MediaPlayerSource src)
throws IOException {
final MediaItem item = src.getDSD();
Preconditions.checkArgument(item != null, "the MediaItem cannot be null");
MediaPlayer player = src.getPlayer();
if (item instanceof CallbackMediaItem) {
player.setDataSource(new MediaDataSource() {
DataSourceCallback mDataSource =
((CallbackMediaItem) item).getDataSourceCallback();
@Override
public int readAt(long position, byte[] buffer, int offset, int size)
throws IOException {
return mDataSource.readAt(position, buffer, offset, size);
}
@Override
public long getSize() throws IOException {
return mDataSource.getSize();
}
@Override
public void close() throws IOException {
mDataSource.close();
}
});
} else if (item instanceof FileMediaItem) {
FileMediaItem fitem = (FileMediaItem) item;
player.setDataSource(
fitem.getFileDescriptor(),
fitem.getFileDescriptorOffset(),
fitem.getFileDescriptorLength());
} else if (item instanceof UriMediaItem) {
UriMediaItem uitem = (UriMediaItem) item;
player.setDataSource(
uitem.getUriContext(),
uitem.getUri(),
uitem.getUriHeaders(),
uitem.getUriCookies());
} else {
throw new IllegalArgumentException(
"Unsupported media item description. " + item.toString());
}
}
@Override
public int getVideoWidth() {
return mPlayer.getVideoWidth();
}
@Override
public int getVideoHeight() {
return mPlayer.getVideoHeight();
}
@Override
public PersistableBundle getMetrics() {
return mPlayer.getMetrics();
}
@Override
public Object setPlaybackParams(@NonNull final PlaybackParams params) {
return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
@Override
void process() {
mPlayer.setPlaybackParams(params.getPlaybackParams());
}
});
}
@Override
@NonNull
public PlaybackParams getPlaybackParams() {
return new PlaybackParams.Builder(mPlayer.getPlaybackParams()).build();
}
@Override
public Object seekTo(final long msec, @SeekMode final int mode) {
return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
@Override
void process() {
mPlayer.seekTo(msec, mode);
}
});
}
@Override
@Nullable
public MediaTimestamp getTimestamp() {
return mPlayer.getTimestamp();
}
/**
* 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().
*/
@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) {
}
}
}
mEndPositionHandler.removeCallbacksAndMessages(null);
mTaskHandler.removeCallbacksAndMessages(null);
mPlayer.reset();
}
@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 int getAudioSessionId() {
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 level) {
return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
@Override
void process() {
mPlayer.setAuxEffectSendLevel(level);
}
});
}
@Override
public List<TrackInfo> getTrackInfo() {
MediaPlayer.TrackInfo[] list = mPlayer.getTrackInfo();
List<TrackInfo> trackList = new ArrayList<>();
for (MediaPlayer.TrackInfo info : list) {
int trackType = sTrackTypeMap.get(info.getTrackType());
MediaFormat format = info.getFormat();
if (format != null && TextUtils.equals(
format.getString(MediaFormat.KEY_MIME), MediaFormat.MIMETYPE_TEXT_VTT)) {
// Hide WebVTT track. For background, see b/120081663.
trackType = TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN;
}
trackList.add(new TrackInfoImpl(trackType, format));
}
return trackList;
}
@Override
public int getSelectedTrack(int trackType) {
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
public void setEventCallback(@NonNull Executor executor,
@NonNull EventCallback eventCallback) {
if (eventCallback == null) {
throw new IllegalArgumentException("Illegal null EventCallback");
}
if (executor == null) {
throw new IllegalArgumentException(
"Illegal null Executor for the EventCallback");
}
synchronized (mLock) {
mMp2EventCallbackRecord = new Pair(executor, eventCallback);
}
}
@Override
public void clearEventCallback() {
synchronized (mLock) {
mMp2EventCallbackRecord = null;
}
}
// Modular DRM begin
@Override
public void setOnDrmConfigHelper(final OnDrmConfigHelper listener) {
mPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
@Override
public void onDrmConfig(MediaPlayer mp) {
MediaPlayerSource src = mPlayer.getSourceForPlayer(mp);
MediaItem item = src == null ? null : src.getDSD();
listener.onDrmConfig(MediaPlayer2Impl.this, item);
}
});
}
@Override
public void setDrmEventCallback(@NonNull Executor executor,
@NonNull DrmEventCallback eventCallback) {
if (eventCallback == null) {
throw new IllegalArgumentException("Illegal null EventCallback");
}
if (executor == null) {
throw new IllegalArgumentException(
"Illegal null Executor for the EventCallback");
}
synchronized (mLock) {
mDrmEventCallbackRecord = new Pair(executor, eventCallback);
}
}
@Override
public void clearDrmEventCallback() {
synchronized (mLock) {
mDrmEventCallbackRecord = null;
}
}
@Override
public DrmInfo getDrmInfo() {
MediaPlayer.DrmInfo info = mPlayer.getDrmInfo();
return info == null ? null : new DrmInfoImpl(info.getPssh(), info.getSupportedSchemes());
}
@Override
public Object prepareDrm(@NonNull final UUID uuid) {
return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, false) {
@Override
void process() {
int status = PREPARE_DRM_STATUS_SUCCESS;
try {
mPlayer.prepareDrm(uuid);
} catch (ResourceBusyException e) {
status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
} catch (MediaPlayer.ProvisioningServerErrorException e) {
status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
} catch (MediaPlayer.ProvisioningNetworkErrorException e) {
status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
} catch (UnsupportedSchemeException e) {
status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
} catch (Exception e) {
status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
final int prepareDrmStatus = status;
notifyDrmEvent(new DrmEventNotifier() {
@Override
public void notify(DrmEventCallback cb) {
cb.onDrmPrepared(MediaPlayer2Impl.this, mDSD, prepareDrmStatus);
}
});
}
});
}
@Override
public void releaseDrm() throws NoDrmSchemeException {
try {
mPlayer.releaseDrm();
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@Override
@NonNull
public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId,
@Nullable byte[] initData, @Nullable String mimeType, int keyType,
@Nullable Map<String, String> optionalParameters)
throws NoDrmSchemeException {
try {
return mPlayer.getKeyRequest(keySetId, initData, mimeType, keyType, optionalParameters);
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@Override
public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
throws NoDrmSchemeException, DeniedByServerException {
try {
return mPlayer.provideKeyResponse(keySetId, response);
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@Override
public void restoreDrmKeys(@NonNull final byte[] keySetId)
throws NoDrmSchemeException {
try {
mPlayer.restoreKeys(keySetId);
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@Override
@NonNull
public String getDrmPropertyString(@NonNull String propertyName)
throws NoDrmSchemeException {
try {
return mPlayer.getDrmPropertyString(propertyName);
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@Override
public void setDrmPropertyString(@NonNull String propertyName,
@NonNull String value)
throws NoDrmSchemeException {
try {
mPlayer.setDrmPropertyString(propertyName, value);
} catch (MediaPlayer.NoDrmSchemeException e) {
throw new NoDrmSchemeException(e.getMessage());
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) {
final Pair<Executor, EventCallback> record;
synchronized (mLock) {
record = mMp2EventCallbackRecord;
}
if (record != null) {
try {
record.first.execute(new Runnable() {
@Override
public void run() {
notifier.notify(record.second);
}
});
} catch (RejectedExecutionException e) {
// The given executor is shutting down.
Log.w(TAG, "The given executor is shutting down. Ignoring the player event.");
}
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void notifyDrmEvent(final DrmEventNotifier notifier) {
final Pair<Executor, DrmEventCallback> record;
synchronized (mLock) {
record = mDrmEventCallbackRecord;
}
if (record != null) {
try {
record.first.execute(new Runnable() {
@Override
public void run() {
notifier.notify(record.second);
}
});
} catch (RejectedExecutionException e) {
// The given executor is shutting down.
Log.w(TAG, "The given executor is shutting down. Ignoring the player event.");
}
}
}
private interface Mp2EventNotifier {
void notify(EventCallback callback);
}
private interface DrmEventNotifier {
void notify(DrmEventCallback callback);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void setEndPositionTimerIfNeeded(
final MediaPlayer.OnCompletionListener completionListener,
final MediaPlayerSource src, android.media.MediaTimestamp timeitem) {
if (src == mPlayer.getFirst()) {
mEndPositionHandler.removeCallbacksAndMessages(null);
MediaItem item = src.getDSD();
if (item.getEndPosition() != MediaItem.POSITION_UNKNOWN) {
if (timeitem.getMediaClockRate() > 0.0f) {
long nowNs = System.nanoTime();
long elapsedTimeUs = (nowNs - timeitem.getAnchorSytemNanoTime()) / 1000;
long nowMediaMs = (timeitem.getAnchorMediaTimeUs() + elapsedTimeUs) / 1000;
long timeLeftMs = (long) ((item.getEndPosition() - nowMediaMs)
/ timeitem.getMediaClockRate());
mEndPositionHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mPlayer.getFirst() != src) {
return;
}
mPlayer.pause();
completionListener.onCompletion(src.getPlayer());
}
}, timeLeftMs < 0 ? 0 : timeLeftMs);
}
}
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void clearListeners(final MediaPlayerSource src) {
MediaPlayer p = src.getPlayer();
p.setOnPreparedListener(null);
p.setOnVideoSizeChangedListener(null);
p.setOnInfoListener(null);
p.setOnCompletionListener(null);
p.setOnErrorListener(null);
p.setOnSeekCompleteListener(null);
p.setOnTimedMetaDataAvailableListener(null);
p.setOnBufferingUpdateListener(null);
p.clearOnMediaTimeDiscontinuityListener();
p.clearOnSubtitleDataListener();
p.setOnDrmInfoListener(null);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void setUpListeners(final MediaPlayerSource src) {
MediaPlayer p = src.getPlayer();
final MediaPlayer.OnPreparedListener preparedListener =
new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
handleDataSourceError(mPlayer.onPrepared(mp));
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
MediaPlayer2Impl mp2 = MediaPlayer2Impl.this;
MediaItem item = src.getDSD();
callback.onInfo(mp2, item, MEDIA_INFO_PREPARED, 0);
}
});
synchronized (mTaskLock) {
if (mCurrentTask != null
&& mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
&& ObjectsCompat.equals(mCurrentTask.mDSD, src.getDSD())
&& mCurrentTask.mNeedToWaitForEventToComplete) {
mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
mCurrentTask = null;
processPendingTask_l();
}
}
}
};
p.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
if (src.getPlayer().getDuration() >= 0) {
// Seeks to the start position. We call seek operation even when the start
// position is 0 in order to show the preview.
src.getPlayer().seekTo((int) src.getDSD().getStartPosition(),
MediaPlayer.SEEK_CLOSEST);
// In this case, PREPARED notification will be sent when seek is done.
} else {
// The content is not seekable. e.g. live contents.
preparedListener.onPrepared(mp);
}
}
});
p.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, final int width, final int height) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onVideoSizeChanged(MediaPlayer2Impl.this, src.getDSD(), width, height);
}
});
}
});
p.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(final MediaPlayer mp, final int what, final int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
mPlayer.setBufferingState(
mp, SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
mPlayer.setBufferingState(
mp, SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
break;
}
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
mPlayer.onStartedAsNext(mp);
return;
}
int w = sInfoEventMap.getOrDefault(what, MEDIA_INFO_UNKNOWN);
cb.onInfo(MediaPlayer2Impl.this, src.getDSD(), w, extra);
}
});
return true;
}
});
final MediaPlayer.OnCompletionListener completionListener =
new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
handleDataSourceError(mPlayer.onCompletion(mp));
}
};
p.setOnCompletionListener(completionListener);
p.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, final int what, final int extra) {
mPlayer.onError(mp);
synchronized (mTaskLock) {
if (mCurrentTask != null
&& mCurrentTask.mNeedToWaitForEventToComplete) {
mCurrentTask.sendCompleteNotification(CALL_STATUS_ERROR_UNKNOWN);
mCurrentTask = null;
processPendingTask_l();
}
}
final int w = (what == MediaPlayer.MEDIA_ERROR_UNKNOWN)
? sErrorEventExtraMap.getOrDefault(extra, MEDIA_ERROR_UNKNOWN)
: MEDIA_ERROR_UNKNOWN;
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onError(MediaPlayer2Impl.this, src.getDSD(), w, 0);
}
});
return true;
}
});
p.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mp) {
if (src.mMp2State == PLAYER_STATE_IDLE) {
// This seek request was for handling start position. Notify client that it's
// ready to start playback.
preparedListener.onPrepared(mp);
return;
}
synchronized (mTaskLock) {
if (mCurrentTask != null
&& mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
&& mCurrentTask.mNeedToWaitForEventToComplete) {
mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
mCurrentTask = null;
processPendingTask_l();
}
}
}
});
p.setOnTimedMetaDataAvailableListener(
new MediaPlayer.OnTimedMetaDataAvailableListener() {
@Override
public void onTimedMetaDataAvailable(MediaPlayer mp, final android.media
.TimedMetaData data) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onTimedMetaDataAvailable(MediaPlayer2Impl.this, src.getDSD(),
new TimedMetaData(data));
}
});
}
});
p.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, final int percent) {
if (percent >= 100) {
mPlayer.setBufferingState(
mp, SessionPlayer.BUFFERING_STATE_COMPLETE);
}
src.mBufferedPercentage.set(percent);
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onInfo(MediaPlayer2Impl.this, src.getDSD(),
MEDIA_INFO_BUFFERING_UPDATE, percent);
}
});
}
});
p.setOnMediaTimeDiscontinuityListener(
new MediaPlayer.OnMediaTimeDiscontinuityListener() {
@Override
public void onMediaTimeDiscontinuity(
MediaPlayer mp, final android.media.MediaTimestamp timestamp) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onMediaTimeDiscontinuity(
MediaPlayer2Impl.this, src.getDSD(),
new MediaTimestamp(timestamp));
}
});
setEndPositionTimerIfNeeded(completionListener, src, timestamp);
}
});
p.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() {
@Override
public void onSubtitleData(MediaPlayer mp, final android.media.SubtitleData data) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onSubtitleData(
MediaPlayer2Impl.this, src.getDSD(), new SubtitleData(data));
}
});
}
});
p.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
@Override
public void onDrmInfo(MediaPlayer mp, final MediaPlayer.DrmInfo drmInfo) {
notifyDrmEvent(new DrmEventNotifier() {
@Override
public void notify(DrmEventCallback cb) {
cb.onDrmInfo(MediaPlayer2Impl.this, src.getDSD(),
new DrmInfoImpl(drmInfo.getPssh(), drmInfo.getSupportedSchemes()));
}
});
}
});
}
/**
* Encapsulates the DRM properties of the source.
*/
public static final class DrmInfoImpl extends DrmInfo {
private Map<UUID, byte[]> mMapPssh;
private UUID[] mSupportedSchemes;
/**
* Returns the PSSH info of the media item for each supported DRM scheme.
*/
@Override
public Map<UUID, byte[]> getPssh() {
return mMapPssh;
}
/**
* Returns the intersection of the media item and the device DRM schemes.
* It effectively identifies the subset of the source's DRM schemes which
* are supported by the device too.
*/
@Override
public List<UUID> getSupportedSchemes() {
return Arrays.asList(mSupportedSchemes);
}
DrmInfoImpl(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) {
mMapPssh = pssh;
mSupportedSchemes = supportedSchemes;
}
private DrmInfoImpl(Parcel parcel) {
Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize());
int psshsize = parcel.readInt();
byte[] pssh = new byte[psshsize];
parcel.readByteArray(pssh);
Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh));
mMapPssh = parsePSSH(pssh, psshsize);
Log.v(TAG, "DrmInfoImpl() PSSH: " + mMapPssh);
int supportedDRMsCount = parcel.readInt();
mSupportedSchemes = new UUID[supportedDRMsCount];
for (int i = 0; i < supportedDRMsCount; i++) {
byte[] uuid = new byte[16];
parcel.readByteArray(uuid);
mSupportedSchemes[i] = bytesToUUID(uuid);
Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: "
+ mSupportedSchemes[i]);
}
Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize
+ " supportedDRMsCount: " + supportedDRMsCount);
}
private DrmInfoImpl makeCopy() {
return new DrmInfoImpl(this.mMapPssh, this.mSupportedSchemes);
}
private String arrToHex(byte[] bytes) {
String out = "0x";
for (int i = 0; i < bytes.length; i++) {
out += String.format("%02x", bytes[i]);
}
return out;
}
private UUID bytesToUUID(byte[] uuid) {
long msb = 0, lsb = 0;
for (int i = 0; i < 8; i++) {
msb |= (((long) uuid[i] & 0xff) << (8 * (7 - i)));
lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i)));
}
return new UUID(msb, lsb);
}
private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
final int uuidSize = 16;
final int dataLenSize = 4;
int len = psshsize;
int numentries = 0;
int i = 0;
while (len > 0) {
if (len < uuidSize) {
Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ "UUID: (%d < 16) pssh: %d", len, psshsize));
return null;
}
byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize);
UUID uuid = bytesToUUID(subset);
i += uuidSize;
len -= uuidSize;
// get data length
if (len < 4) {
Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ "datalen: (%d < 4) pssh: %d", len, psshsize));
return null;
}
subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
| ((subset[1] & 0xff) << 8) | (subset[0] & 0xff)
: ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
| ((subset[2] & 0xff) << 8) | (subset[3] & 0xff);
i += dataLenSize;
len -= dataLenSize;
if (len < datalen) {
Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ "data: (%d < %d) pssh: %d", len, datalen, psshsize));
return null;
}
byte[] data = Arrays.copyOfRange(pssh, i, i + datalen);
// skip the data
i += datalen;
len -= datalen;
Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
numentries, uuid, arrToHex(data), psshsize));
numentries++;
result.put(uuid, data);
}
return result;
}
}; // DrmInfoImpl
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
&& getState() == PLAYER_STATE_ERROR) {
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 = getCurrentMediaItem();
if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR || skip) {
sendCompleteNotification(status);
synchronized (mTaskLock) {
mCurrentTask = null;
processPendingTask_l();
}
}
// reset() might be waiting for this task. Notify that the task is done.
synchronized (this) {
mDone = true;
this.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 {@link #processs()}.
return;
}
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
cb.onCallCompleted(
MediaPlayer2Impl.this, mDSD, mMediaCallType, status);
}
});
}
};
private static class DataSourceError {
final MediaItem mDSD;
final int mWhat;
final int mExtra;
DataSourceError(MediaItem item, int what, int extra) {
mDSD = item;
mWhat = what;
mExtra = extra;
}
}
private class MediaPlayerSource {
volatile MediaItem mDSD;
MediaPlayer mPlayer;
final AtomicInteger mBufferedPercentage = new AtomicInteger(0);
int mSourceState = SOURCE_STATE_INIT;
@MediaPlayer2State int mMp2State = PLAYER_STATE_IDLE;
@BuffState int mBufferingState = SessionPlayer.BUFFERING_STATE_UNKNOWN;
@PlayerState int mPlayerState = SessionPlayer.PLAYER_STATE_IDLE;
boolean mPlayPending;
boolean mSetAsNextPlayer;
final Map<Integer, Integer> mSelectedTracks = new ArrayMap<>();
MediaPlayerSource(final MediaItem item) {
mDSD = item;
setUpListeners(this);
}
MediaItem getDSD() {
return mDSD;
}
synchronized MediaPlayer getPlayer() {
if (mPlayer == null) {
mPlayer = new MediaPlayer();
}
return mPlayer;
}
void release() {
clearListeners(this);
mPlayer.release();
}
void selectTrack(int index) {
final MediaPlayer mp = getPlayer();
mp.selectTrack(index);
MediaPlayer.TrackInfo[] trackInfos = mp.getTrackInfo();
if (index >= 0 && index < trackInfos.length) {
mSelectedTracks.put(trackInfos[index].getTrackType(), index);
}
}
void deselectTrack(int index) {
final MediaPlayer mp = getPlayer();
mp.deselectTrack(index);
MediaPlayer.TrackInfo[] trackInfos = mp.getTrackInfo();
if (index >= 0 && index < trackInfos.length) {
int trackType = trackInfos[index].getTrackType();
if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
|| trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
mSelectedTracks.remove(trackType);
}
}
}
int getSelectedTrack(int trackType) {
return mSelectedTracks.getOrDefault(trackType, -1);
}
}
private class MediaPlayerSourceQueue {
List<MediaPlayerSource> mQueue = new ArrayList<>();
Float mVolume = 1.0f;
Surface mSurface;
Integer mAuxEffect;
Float mAuxEffectSendLevel;
AudioAttributesCompat mAudioAttributes;
Integer mAudioSessionId;
android.media.PlaybackParams mPlaybackParams = DEFAULT_PLAYBACK_PARAMS;
android.media.PlaybackParams mPlaybackParamsToSetWhenStarting;
boolean mLooping;
MediaPlayerSourceQueue() {
mQueue.add(new MediaPlayerSource(null));
}
synchronized MediaPlayer getCurrentPlayer() {
return mQueue.get(0).getPlayer();
}
synchronized MediaPlayerSource getFirst() {
return mQueue.get(0);
}
synchronized void setFirst(MediaItem item) throws IOException {
if (mQueue.isEmpty()) {
mQueue.add(0, new MediaPlayerSource(item));
} else {
mQueue.get(0).mDSD = item;
setUpListeners(mQueue.get(0));
}
handleDataSource(mQueue.get(0));
}
synchronized DataSourceError setNext(MediaItem item) {
if (mQueue.isEmpty() || getFirst().getDSD() == null) {
throw new IllegalStateException();
}
// Clear next media items if any.
while (mQueue.size() >= 2) {
MediaPlayerSource src = mQueue.remove(1);
src.release();
}
MediaPlayerSource src = new MediaPlayerSource(item);
mQueue.add(1, src);
return prepareAt(1);
}
synchronized DataSourceError setNextMultiple(List<MediaItem> descs) {
if (mQueue.isEmpty() || getFirst().getDSD() == null) {
throw new IllegalStateException();
}
// Clear next media items if any.
while (mQueue.size() >= 2) {
MediaPlayerSource src = mQueue.remove(1);
src.release();
}
List<MediaPlayerSource> sources = new ArrayList<>();
for (MediaItem item: descs) {
sources.add(new MediaPlayerSource(item));
}
mQueue.addAll(1, sources);
return prepareAt(1);
}
synchronized void play() {
final MediaPlayerSource src = mQueue.get(0);
if (src.mSourceState == SOURCE_STATE_PREPARED) {
if (mPlaybackParamsToSetWhenStarting != null) {
src.getPlayer().setPlaybackParams(mPlaybackParamsToSetWhenStarting);
mPlaybackParamsToSetWhenStarting = null;
}
src.getPlayer().start();
setMp2State(src.getPlayer(), PLAYER_STATE_PLAYING);
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
callback.onInfo(MediaPlayer2Impl.this, src.getDSD(),
MEDIA_INFO_DATA_SOURCE_START, 0);
}
});
} else {
throw new IllegalStateException();
}
}
synchronized void release() {
getCurrentPlayer().release();
}
synchronized void prepareAsync() {
MediaPlayer mp = getCurrentPlayer();
mp.prepareAsync();
setBufferingState(mp, SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED);
}
synchronized void pause() {
MediaPlayerSource current = getFirst();
if (current.mMp2State == PLAYER_STATE_PREPARED) {
// MediaPlayer1 does not allow pause() in the prepared state. To workaround, call
// start() here right before calling pause().
current.mPlayer.start();
}
current.mPlayer.pause();
setMp2State(current.mPlayer, PLAYER_STATE_PAUSED);
}
synchronized long getCurrentPosition() {
// Throws an ISE here rather than relying on MediaPlayer1 implementation which returns
// a garbage value in the IDLE state.
if (getFirst().mMp2State == PLAYER_STATE_IDLE) {
throw new IllegalStateException();
}
return getCurrentPlayer().getCurrentPosition();
}
synchronized long getDuration() {
// Throws an ISE here rather than relying on MediaPlayer1 implementation which returns
// a garbage value in the IDLE state.
if (getFirst().mMp2State == PLAYER_STATE_IDLE) {
throw new IllegalStateException();
}
return getCurrentPlayer().getDuration();
}
synchronized long getBufferedPosition() {
// Throws an ISE here rather than relying on MediaPlayer1 implementation which returns
// a garbage value in the IDLE state.
if (getFirst().mMp2State == PLAYER_STATE_IDLE) {
throw new IllegalStateException();
}
MediaPlayerSource src = mQueue.get(0);
return (long) src.getPlayer().getDuration() * src.mBufferedPercentage.get() / 100;
}
synchronized void setAudioAttributes(AudioAttributesCompat attributes) {
mAudioAttributes = attributes;
AudioAttributes attr = mAudioAttributes == null
? null : (AudioAttributes) mAudioAttributes.unwrap();
getCurrentPlayer().setAudioAttributes(attr);
}
synchronized AudioAttributesCompat getAudioAttributes() {
return mAudioAttributes;
}
synchronized DataSourceError onPrepared(MediaPlayer mp) {
for (int i = 0; i < mQueue.size(); i++) {
MediaPlayerSource src = mQueue.get(i);
if (mp == src.getPlayer()) {
if (i == 0) {
if (src.mPlayPending) {
src.mPlayPending = false;
src.getPlayer().start();
setMp2State(src.getPlayer(), PLAYER_STATE_PLAYING);
} else {
setMp2State(src.getPlayer(), PLAYER_STATE_PREPARED);
}
}
src.mSourceState = SOURCE_STATE_PREPARED;
setBufferingState(src.getPlayer(),
SessionPlayer.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
if (i == 1) {
boolean hasVideo = false;
for (MediaPlayer.TrackInfo info : mp.getTrackInfo()) {
if (info.getTrackType()
== MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
hasVideo = true;
break;
}
}
// setNextMediaPlayer() does not pass surface to next player. Use it only
// for the audio-only media item.
if (!hasVideo) {
getCurrentPlayer().setNextMediaPlayer(src.mPlayer);
src.mSetAsNextPlayer = true;
}
}
return prepareAt(i + 1);
}
}
return null;
}
synchronized DataSourceError onCompletion(MediaPlayer mp) {
final MediaPlayerSource src = getFirst();
if (mLooping && mp == src.mPlayer) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
MediaPlayer2Impl mp2 = MediaPlayer2Impl.this;
MediaItem item = src.getDSD();
cb.onInfo(mp2, item, MEDIA_INFO_DATA_SOURCE_REPEAT, 0);
}
});
src.mPlayer.seekTo((int) src.getDSD().getStartPosition());
src.mPlayer.start();
setMp2State(mp, PLAYER_STATE_PLAYING);
return null;
}
if (mp == src.mPlayer) {
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback cb) {
MediaPlayer2Impl mp2 = MediaPlayer2Impl.this;
MediaItem item = src.getDSD();
cb.onInfo(mp2, item, MEDIA_INFO_DATA_SOURCE_END, 0);
}
});
} else {
Log.w(TAG, "Playback complete event from next player. Ignoring.");
}
if (!mQueue.isEmpty() && mp == src.mPlayer) {
if (mQueue.size() == 1) {
setMp2State(mp, PLAYER_STATE_PAUSED);
final MediaItem item = mQueue.get(0).getDSD();
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
callback.onInfo(MediaPlayer2Impl.this, item,
MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
}
});
return null;
} else {
if (mQueue.get(1).mSetAsNextPlayer) {
// Transition to next player will be handled by MEDIA_INFO_STARTED_AS_NEXT
// event later.
return null;
}
moveToNext();
return playCurrent();
}
} else {
Log.w(TAG, "Invalid playback complete callback from " + mp.toString());
return null;
}
}
synchronized void onStartedAsNext(MediaPlayer mp) {
if (mQueue.size() >= 2 && mQueue.get(1).mPlayer == mp) {
moveToNext();
final MediaPlayerSource src = getFirst();
setMp2State(src.getPlayer(), PLAYER_STATE_PLAYING);
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
callback.onInfo(MediaPlayer2Impl.this, src.getDSD(),
MEDIA_INFO_DATA_SOURCE_START, 0);
}
});
prepareAt(1);
applyProperties();
}
}
synchronized void moveToNext() {
final MediaPlayerSource src1 = mQueue.remove(0);
src1.release();
if (mQueue.isEmpty()) {
throw new IllegalStateException("player/source queue emptied");
}
}
synchronized DataSourceError playCurrent() {
DataSourceError err = null;
applyProperties();
final MediaPlayerSource src = mQueue.get(0);
if (src.mSourceState == SOURCE_STATE_PREPARED) {
// start next source only when it's in prepared state.
src.getPlayer().start();
setMp2State(src.getPlayer(), PLAYER_STATE_PLAYING);
notifyMediaPlayer2Event(new Mp2EventNotifier() {
@Override
public void notify(EventCallback callback) {
callback.onInfo(MediaPlayer2Impl.this, src.getDSD(),
MEDIA_INFO_DATA_SOURCE_START, 0);
}
});
prepareAt(1);
} else {
if (src.mSourceState == SOURCE_STATE_INIT) {
err = prepareAt(0);
}
src.mPlayPending = true;
}
return err;
}
synchronized void applyProperties() {
final MediaPlayerSource src = mQueue.get(0);
if (mSurface != null) {
src.getPlayer().setSurface(mSurface);
}
if (mVolume != null) {
src.getPlayer().setVolume(mVolume, mVolume);
}
if (mAudioAttributes != null) {
src.getPlayer().setAudioAttributes((AudioAttributes) mAudioAttributes.unwrap());
}
if (mAuxEffect != null) {
src.getPlayer().attachAuxEffect(mAuxEffect);
}
if (mAuxEffectSendLevel != null) {
src.getPlayer().setAuxEffectSendLevel(mAuxEffectSendLevel);
}
if (mPlaybackParams != DEFAULT_PLAYBACK_PARAMS) {
src.getPlayer().setPlaybackParams(mPlaybackParams);
}
}
synchronized void onError(MediaPlayer mp) {
setMp2State(mp, PLAYER_STATE_ERROR);
setBufferingState(mp, SessionPlayer.BUFFERING_STATE_UNKNOWN);
}
synchronized DataSourceError prepareAt(int n) {
if (n >= Math.min(2, mQueue.size())
|| mQueue.get(n).mSourceState != SOURCE_STATE_INIT
|| (n != 0 && getPlayerState() == SessionPlayer.PLAYER_STATE_IDLE)) {
// There is no next source or it's in preparing or prepared state.
return null;
}
MediaPlayerSource src = mQueue.get(n);
try {
// Apply audio session ID before calling setDataSource().
if (mAudioSessionId != null) {
src.getPlayer().setAudioSessionId(mAudioSessionId);
}
src.mSourceState = SOURCE_STATE_PREPARING;
handleDataSource(src);
src.getPlayer().prepareAsync();
return null;
} catch (Exception e) {
MediaItem item = src.getDSD();
setMp2State(src.getPlayer(), PLAYER_STATE_ERROR);
return new DataSourceError(item, MEDIA_ERROR_UNKNOWN, 0);
}
}
synchronized void skipToNext() {
if (mQueue.size() <= 1) {
throw new IllegalStateException("No next source available");
}
final MediaPlayerSource src = mQueue.get(0);
moveToNext();
if (src.mPlayerState == SessionPlayer.PLAYER_STATE_PLAYING || src.mPlayPending) {
playCurrent();
}
}
synchronized void setLooping(boolean loop) {
mLooping = loop;
}
synchronized void setPlaybackParams(final android.media.PlaybackParams params) {
if (params == null || params.getSpeed() == 0f) {
throw new IllegalArgumentException();
}
android.media.PlaybackParams current = getPlaybackParams();
MediaPlayerSource firstPlayer = mPlayer.getFirst();
if (firstPlayer.mMp2State != PLAYER_STATE_PLAYING) {
// MediaPlayer1 may start the playback on setPlaybackParams. Store the value here
// so that it can be applied later when starting the playback.
mPlaybackParamsToSetWhenStarting = params;
} else {
firstPlayer.mPlayer.setPlaybackParams(params);
mPlaybackParamsToSetWhenStarting = null;
}
mPlaybackParams = params;
}
synchronized float getVolume() {
return mVolume;
}
synchronized void setVolume(float volume) {
mVolume = volume;
getCurrentPlayer().setVolume(volume, volume);
}
synchronized void setSurface(Surface surface) {
mSurface = surface;
getCurrentPlayer().setSurface(surface);
}
synchronized int getVideoWidth() {
try {
return getCurrentPlayer().getVideoWidth();
} catch (IllegalStateException e) {
return 0;
}
}
synchronized int getVideoHeight() {
try {
return getCurrentPlayer().getVideoHeight();
} catch (IllegalStateException e) {
return 0;
}
}
synchronized PersistableBundle getMetrics() {
return getCurrentPlayer().getMetrics();
}
synchronized android.media.PlaybackParams getPlaybackParams() {
// PlaybackParams is mutable. Make a copy of mPlaybackParams and return.
Parcel parcel = Parcel.obtain();
mPlaybackParams.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
android.media.PlaybackParams ret =
android.media.PlaybackParams.CREATOR.createFromParcel(parcel);
parcel.recycle();
return ret;
}
synchronized void seekTo(long msec, int mode) {
MediaItem current = getFirst().getDSD();
Preconditions.checkArgument(
current.getStartPosition() <= msec && current.getEndPosition() >= msec,
"Requested seek position is out of range : " + msec);
getCurrentPlayer().seekTo(msec, mode);
}
synchronized void reset() {
mVolume = 1.0f;
mSurface = null;
mAuxEffect = null;
mAuxEffectSendLevel = null;
mAudioAttributes = null;
mAudioSessionId = null;
mPlaybackParams = DEFAULT_PLAYBACK_PARAMS;
mPlaybackParamsToSetWhenStarting = null;
mLooping = false;
MediaPlayerSource first = mQueue.get(0);
setMp2State(first.getPlayer(), PLAYER_STATE_IDLE);
setBufferingState(first.getPlayer(), SessionPlayer.BUFFERING_STATE_UNKNOWN);
for (MediaPlayerSource src : mQueue) {
src.release();
}
mQueue.clear();
mQueue.add(new MediaPlayerSource(null));
}
synchronized MediaTimestamp getTimestamp() {
android.media.MediaTimestamp t = getCurrentPlayer().getTimestamp();
return (t == null) ? null : new MediaTimestamp(t);
}
synchronized void setAudioSessionId(int sessionId) {
getCurrentPlayer().setAudioSessionId(sessionId);
mAudioSessionId = Integer.valueOf(sessionId);
}
synchronized int getAudioSessionId() {
return getCurrentPlayer().getAudioSessionId();
}
synchronized void attachAuxEffect(int effectId) {
getCurrentPlayer().attachAuxEffect(effectId);
mAuxEffect = Integer.valueOf(effectId);
}
synchronized void setAuxEffectSendLevel(float level) {
getCurrentPlayer().setAuxEffectSendLevel(level);
mAuxEffectSendLevel = Float.valueOf(level);
}
synchronized MediaPlayer.TrackInfo[] getTrackInfo() {
return getCurrentPlayer().getTrackInfo();
}
synchronized int getSelectedTrack(int trackType) {
final MediaPlayerSource currentPlayerSource = mQueue.get(0);
return currentPlayerSource.getSelectedTrack(trackType);
}
synchronized void selectTrack(int index) {
final MediaPlayerSource currentPlayerSource = mQueue.get(0);
currentPlayerSource.selectTrack(index);
}
synchronized void deselectTrack(int index) {
final MediaPlayerSource currentPlayerSource = mQueue.get(0);
currentPlayerSource.deselectTrack(index);
}
synchronized MediaPlayer.DrmInfo getDrmInfo() {
return getCurrentPlayer().getDrmInfo();
}
synchronized void prepareDrm(UUID uuid)
throws ResourceBusyException, MediaPlayer.ProvisioningServerErrorException,
MediaPlayer.ProvisioningNetworkErrorException, UnsupportedSchemeException {
getCurrentPlayer().prepareDrm(uuid);
}
synchronized void releaseDrm() throws MediaPlayer.NoDrmSchemeException {
getCurrentPlayer().stop();
getCurrentPlayer().releaseDrm();
}
synchronized byte[] provideKeyResponse(byte[] keySetId, byte[] response)
throws DeniedByServerException, MediaPlayer.NoDrmSchemeException {
return getCurrentPlayer().provideKeyResponse(keySetId, response);
}
synchronized void restoreKeys(byte[] keySetId) throws MediaPlayer.NoDrmSchemeException {
getCurrentPlayer().restoreKeys(keySetId);
}
synchronized String getDrmPropertyString(String propertyName)
throws MediaPlayer.NoDrmSchemeException {
return getCurrentPlayer().getDrmPropertyString(propertyName);
}
synchronized void setDrmPropertyString(String propertyName, String value)
throws MediaPlayer.NoDrmSchemeException {
getCurrentPlayer().setDrmPropertyString(propertyName, value);
}
synchronized void setOnDrmConfigHelper(MediaPlayer.OnDrmConfigHelper onDrmConfigHelper) {
getCurrentPlayer().setOnDrmConfigHelper(onDrmConfigHelper);
}
synchronized MediaDrm.KeyRequest getKeyRequest(byte[] keySetId, byte[] initData,
String mimeType,
int keyType, Map<String, String> optionalParameters)
throws MediaPlayer.NoDrmSchemeException {
return getCurrentPlayer().getKeyRequest(keySetId, initData, mimeType, keyType,
optionalParameters);
}
synchronized void setMp2State(MediaPlayer mp, @MediaPlayer2State int mp2State) {
for (final MediaPlayerSource src: mQueue) {
if (src.getPlayer() != mp) {
continue;
}
if (src.mMp2State == mp2State) {
return;
}
src.mMp2State = mp2State;
final int playerState = sStateMap.get(mp2State);
if (src.mPlayerState == playerState) {
return;
}
src.mPlayerState = playerState;
return;
}
}
synchronized void setBufferingState(MediaPlayer mp, @BuffState final int state) {
for (final MediaPlayerSource src: mQueue) {
if (src.getPlayer() != mp) {
continue;
}
if (src.mBufferingState == state) {
return;
}
src.mBufferingState = state;
return;
}
}
synchronized @MediaPlayer2State int getMediaPlayer2State() {
return mQueue.get(0).mMp2State;
}
synchronized @BuffState int getBufferingState() {
return mQueue.get(0).mBufferingState;
}
synchronized @PlayerState int getPlayerState() {
return mQueue.get(0).mPlayerState;
}
synchronized MediaPlayerSource getSourceForPlayer(MediaPlayer mp) {
for (MediaPlayerSource src: mQueue) {
if (src.getPlayer() == mp) {
return src;
}
}
return null;
}
}
}