/* * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.media2.common; import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.media.MediaFormat; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.util.Log; import android.view.Surface; import androidx.annotation.CallSuper; import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.concurrent.futures.ResolvableFuture; import androidx.core.util.Pair; import androidx.media.AudioAttributesCompat; import androidx.versionedparcelable.CustomVersionedParcelable; import androidx.versionedparcelable.NonParcelField; import androidx.versionedparcelable.ParcelField; import androidx.versionedparcelable.VersionedParcelize; import com.google.common.util.concurrent.ListenableFuture; import java.io.Closeable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.Executor; /** * Base interface for all media players that want media session. *
* APIs that return {@link ListenableFuture} should be the asynchronous calls and shouldn't block * the calling thread. This guarantees the APIs are safe to be called on the main thread. * *
Topics covered here are: *
* ** While in this state, you should call {@link #setMediaItem(MediaItem)} or * {@link #setPlaylist(List, MediaMetadata)}. Check returned {@link ListenableFuture} for * potential error. *
* Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}. * *
* Call {@link #play()} to resume or start playback from the position where it paused. * *
* In this state, {@link PlayerCallback#onBufferingStateChanged( * SessionPlayer, MediaItem, int)} will be called regularly to tell the buffering status. *
* Playback state would remain {@link #PLAYER_STATE_PLAYING} when the currently playing * media item is changed. *
* When the playback reaches the end of stream, the behavior depends on repeat mode, set by * {@link #setRepeatMode(int)}. If the repeat mode was set to {@link #REPEAT_MODE_NONE}, * the player will transfer to the {@link #PLAYER_STATE_PAUSED}. Otherwise, the * SessionPlayer object remains in the {@link #PLAYER_STATE_PLAYING} and playback will be * ongoing. * *
* In general, playback might fail due to various reasons such as unsupported audio/video * format, poorly interleaved audio/video, resolution too high, streaming timeout, and * others. In addition, due to programming errors, a playback control operation might be * performed from an invalid state. In these cases the player * may transition to this state. *
* *
* Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error * state and/or provide a method to recover from the error state. Take a look at documentations of * specific subclass that you're interested in. *
* Most methods can be called from any non-Error state. In case they're called in invalid state, * the implementation should ignore and would return {@link PlayerResult} with * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}. The following table lists the methods that * aren't guaranteed to successfully running if they're called from the associated invalid states. *
*
Method Name | Invalid States |
---|---|
setAudioAttributes | {Paused, Playing} |
prepare | {Paused, Playing} |
play | {Idle} |
pause | {Idle} |
seekTo | {Idle} |
* On success, this transfers the player state to {@link #PLAYER_STATE_PLAYING} and
* a {@link PlayerResult} should be returned with the current media item when the command
* was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
* it should be ignored and a {@link PlayerResult} should be returned with
* {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
*/
@NonNull
public abstract ListenableFuture
* On success, this transfers the player state to {@link #PLAYER_STATE_PAUSED} and
* a {@link PlayerResult} should be returned with the current media item when the command
* was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
* it should be ignored and a {@link PlayerResult} should be returned with
* {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
*/
@NonNull
public abstract ListenableFuture
* On success, this transfers the player state from {@link #PLAYER_STATE_IDLE} to
* {@link #PLAYER_STATE_PAUSED} and a {@link PlayerResult} should be returned with the prepared
* media item when the command completed. If it's not called in {@link #PLAYER_STATE_IDLE},
* it should be ignored and {@link PlayerResult} should be returned with
* {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
*/
@NonNull
public abstract ListenableFuture
* The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
* calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored and
* a {@link PlayerResult} should be returned with
* {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
* @param position the new playback position in ms. The value should be in the range of start
* and end positions defined in {@link MediaItem}.
*/
@NonNull
public abstract ListenableFuture
* The supported playback speed range depends on the underlying player implementation, so it is
* recommended to query the actual speed of the player via {@link #getPlaybackSpeed()} after the
* operation completes. In particular, please note that player implementations may not support
* reverse playback.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param playbackSpeed the requested playback speed
* @return a {@link ListenableFuture} representing the pending completion of the command
* @see #getPlaybackSpeed()
* @see PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
*/
@NonNull
public abstract ListenableFuture
* You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
* become effective thereafter. Otherwise, the call would be ignored and {@link PlayerResult}
* should be returned with {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param attributes non-null
* The position is the relative position based on the {@link MediaItem#getStartPosition()}.
* So the position {@code 0} means the start position of the {@link MediaItem}.
*
* @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown
*/
public abstract long getCurrentPosition();
/**
* Gets the duration of the current media item, or {@link #UNKNOWN_TIME} if unknown. If the
* current {@link MediaItem} has either start or end position, then duration would be adjusted
* accordingly instead of returning the whole size of the {@link MediaItem}.
*
* @return the duration in ms, or {@link #UNKNOWN_TIME} if unknown
*/
public abstract long getDuration();
/**
* Gets the position for how much has been buffered, or {@link #UNKNOWN_TIME} if unknown.
*
* The position is the relative position based on the {@link MediaItem#getStartPosition()}.
* So the position {@code 0} means the start position of the {@link MediaItem}.
*
* @return the buffered position in ms, or {@link #UNKNOWN_TIME} if unknown
*/
public abstract long getBufferedPosition();
/**
* Returns the current buffering state of the player.
*
* During the buffering, see {@link #getBufferedPosition()} for the quantifying the amount
* already buffered.
*
* @return the buffering state, or {@link #BUFFERING_STATE_UNKNOWN} if unknown
* @see #getBufferedPosition()
*/
@BuffState
public abstract int getBufferingState();
/**
* Gets the actual playback speed to be used by the player when playing. A value of {@code 1.0f}
* is the default playback value, and a negative value indicates reverse playback.
*
* Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
*
* @return the actual playback speed
*/
public abstract float getPlaybackSpeed();
/**
* Gets the size of the video.
*
* @return the size of the video. The width and height of size could be 0 if there is no video
* or the size has not been determined yet.
* @see PlayerCallback#onVideoSizeChanged(SessionPlayer, VideoSize)
*/
@NonNull
public VideoSize getVideoSize() {
throw new UnsupportedOperationException("getVideoSize is not implemented");
}
/**
* Sets the {@link Surface} to be used as the sink for the video portion of the media.
*
* A null surface will reset any Surface and result in only the audio track being played.
*
* On success, a {@link SessionPlayer.PlayerResult} is returned with
* the current media item when the command completed.
*
* @param surface the {@link Surface} to be used for the video portion of the media
* @return a {@link ListenableFuture} which represents the pending completion of the command
*/
@NonNull
public ListenableFuture
* This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
* would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
*
* Ensure uniqueness of each {@link MediaItem} in the playlist so the session can uniquely
* identity individual items. All {@link MediaItem}s shouldn't be {@code null} as well.
*
* It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
* duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
* it to the controller.
*
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
* when it's completed. The current media item would be the first item in the playlist.
*
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* when a media item in the playlist is a {@link FileMediaItem}.
*
* On success, a {@link PlayerResult} should be returned with the first media item of the
* playlist when the command completed.
*
* @param list a list of {@link MediaItem} objects to set as a play list
* @throws IllegalArgumentException if the given list is {@code null} or empty, or has
* duplicated media items.
* @return a {@link ListenableFuture} which represents the pending completion of the command
* @see #setMediaItem
* @see PlayerCallback#onPlaylistChanged
* @see PlayerCallback#onCurrentMediaItemChanged
*/
@NonNull
public abstract ListenableFuture
* This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
* would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
*
* It's recommended to fill {@link MediaMetadata} in {@link MediaItem} especially for the
* duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
* it to the controller.
*
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged} and {@link PlayerCallback#onCurrentMediaItemChanged}
* when it's completed. The current item would be the item given here.
*
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
*
* On success, a {@link PlayerResult} should be returned with {@code item} set.
*
* @param item the descriptor of media item you want to play
* @return a {@link ListenableFuture} which represents the pending completion of the command
* @see #setPlaylist
* @see PlayerCallback#onPlaylistChanged
* @see PlayerCallback#onCurrentMediaItemChanged
* @throws IllegalArgumentException if the given item is {@code null}.
*/
@NonNull
public abstract ListenableFuture
* If index is less than or equal to the current index of the playlist,
* the current index of the playlist should be increased correspondingly.
*
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
*
* On success, a {@link PlayerResult} should be returned with {@code item} added.
*
* @param index the index of the item you want to add in the playlist
* @param item the media item you want to add
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with {@code item} removed.
*
* If the last item is removed, the player should be moved to {@link #PLAYER_STATE_IDLE}.
*
* @param index the index of the item you want to remove in the playlist
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
*
* On success, a {@link PlayerResult} should be returned with {@code item} set.
*
* @param index the index of the item to replace in the playlist
* @param item the new item
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with {@code item} set.
*
* @param fromIndex the media item's initial index in the playlist
* @param toIndex the media item's target index in the playlist
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
@NonNull
public ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @return a {@link ListenableFuture} representing the pending completion of the command
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param index the index of the item you want to play in the playlist
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)} when it's
* completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param metadata metadata of the playlist
* @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onRepeatModeChanged(SessionPlayer, int)} when it's completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param repeatMode repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
* @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
*/
@NonNull
public abstract ListenableFuture
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onShuffleModeChanged(SessionPlayer, int)} when it's completed.
*
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed.
*
* @param shuffleMode the shuffle mode
* @return a {@link ListenableFuture} representing the pending completion of the command
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
* @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
*/
@NonNull
public abstract ListenableFuture
* The types of tracks supported may vary based on player implementation.
*
* @return list of tracks. The total number of tracks is the size of the list. If empty,
* the implementation should return an empty list instead of {@code null}.
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*/
@NonNull
public List
* Generally one track will be selected for each track type.
*
* The types of tracks supported may vary based on player implementation.
*
* Note: {@link #getTracks()} returns the list of tracks that can be selected, but the
* list may be invalidated when {@link PlayerCallback#onTracksChanged(SessionPlayer, List)}
* is called.
*
* @param trackInfo track to be selected
* @return a {@link ListenableFuture} representing the pending completion of the command
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
* @see PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)
*/
@NonNull
public ListenableFuture
* Generally, a track should already be selected in order to be deselected, and audio and video
* tracks should not be deselected.
*
* The types of tracks supported may vary based on player implementation.
*
* Note: {@link #getSelectedTrack(int)} returns the currently selected track per track type that
* can be deselected, but the list may be invalidated when
* {@link PlayerCallback#onTracksChanged(SessionPlayer, List)} is called.
*
* @param trackInfo track to be deselected
* @return a {@link ListenableFuture} which represents the pending completion of the command
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
* @see PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)
*/
@NonNull
public ListenableFuture
* The returned value can be outdated after
* {@link PlayerCallback#onTracksChanged(SessionPlayer, List)},
* {@link PlayerCallback#onTrackSelected(SessionPlayer, TrackInfo)},
* or {@link PlayerCallback#onTrackDeselected(SessionPlayer, TrackInfo)} is called.
*
* @param trackType type of selected track
* @return selected track info
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*/
@Nullable
public TrackInfo getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
throw new UnsupportedOperationException(
"getSelectedTrack is not implemented");
}
/**
* Removes all existing references to callbacks and executors.
*
* Note: Sub classes of {@link SessionPlayer} that override this API should call this super
* method.
*/
@CallSuper
@Override
public void close() {
synchronized (mLock) {
mCallbacks.clear();
}
}
/**
* Class for the player to return each audio/video/subtitle track's metadata.
*
* Note: TrackInfo holds a MediaFormat instance, but only the following key-values will be
* supported when sending it over different processes:
*
* {@link SessionPlayer#getPreviousMediaItemIndex()} and
* {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
* is called if the current media item is the first or last item in the playlist.
*
* @param player playlist agent for this event
* @param shuffleMode shuffle mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
* @see #getShuffleMode()
*/
public void onShuffleModeChanged(@NonNull SessionPlayer player,
@ShuffleMode int shuffleMode) {
}
/**
* Called when the repeat mode is changed.
*
* {@link SessionPlayer#getPreviousMediaItemIndex()} and
* {@link SessionPlayer#getNextMediaItemIndex()} values can be outdated when this callback
* is called if the current media item is the first or last item in the playlist.
*
* @param player player for this event
* @param repeatMode repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
* @see #getRepeatMode()
*/
public void onRepeatModeChanged(@NonNull SessionPlayer player,
@RepeatMode int repeatMode) {
}
/**
* Called when the player's current media item has changed. Generally called after a new
* media item is set through {@link #setPlaylist} or {@link #setMediaItem}, or after
* skipping to a different item in a given playlist.
*
* @param player the player whose media item changed
* @param item the new current media item. This can be {@code null} when the state of
* the player becomes {@link #PLAYER_STATE_IDLE}.
* @see #getCurrentMediaItem()
*/
public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
@Nullable MediaItem item) {
}
/**
* Called when the player finished playing. Playback state would be also set
* {@link #PLAYER_STATE_PAUSED} with it.
*
* This will be called only when the repeat mode is set to {@link #REPEAT_MODE_NONE}.
*
* @param player the player whose playback is completed
* @see #REPEAT_MODE_NONE
*/
public void onPlaybackCompleted(@NonNull SessionPlayer player) {
}
/**
* Called when the player's current audio attributes are changed.
*
* @param player the player whose audio attributes are changed
* @param attributes the new current audio attributes
* @see #getAudioAttributes()
*/
public void onAudioAttributesChanged(@NonNull SessionPlayer player,
@Nullable AudioAttributesCompat attributes) {
}
/**
* Called to indicate the video size
*
* The video size (width and height) could be 0 if there was no video,
* no display surface was set, or the value was not determined yet.
*
* This callback is generally called when player updates video size, but will also be
* called when {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)}
* is called.
*
* @param player the player associated with this callback
* @param size the size of the video
* @see #getVideoSize()
*/
public void onVideoSizeChanged(@NonNull SessionPlayer player, @NonNull VideoSize size) {
}
/**
* Called when the player's subtitle track has new subtitle data available.
* @param player the player that reports the new subtitle data
* @param item the MediaItem of this media item
* @param track the track that has the subtitle data
* @param data the subtitle data
*/
public void onSubtitleData(@NonNull SessionPlayer player, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {
}
/**
* Called when the tracks of the current media item is changed such as
* 1) when tracks of a media item become available,
* 2) when new tracks are found during playback, or
* 3) when the current media item is changed.
*
* When it's called, you should invalidate previous track information and use the new
* tracks to call {@link #selectTrack(TrackInfo)} or
* {@link #deselectTrack(TrackInfo)}.
*
* @param player the player associated with this callback
* @param tracks the list of tracks. It can be empty
* @see #getTracks()
*/
public void onTracksChanged(@NonNull SessionPlayer player,
@NonNull List
* This callback will generally be called only after calling
* {@link #deselectTrack(TrackInfo)}.
*
* @param player the player associated with this callback
* @param trackInfo the deselected track
* @see #deselectTrack(TrackInfo)
*/
public void onTrackDeselected(@NonNull SessionPlayer player, @NonNull TrackInfo trackInfo) {
}
}
/**
* Result class of the asynchronous APIs.
*
* Subclass may extend this class for providing more result and/or custom result code. For the
* custom result code, follow the convention below to avoid potential code duplication.
*
*
* Subclass of the {@link SessionPlayer} may have defined customized extra code other than
* codes defined here. Check the documentation of the class that you're interested in.
*
* @return result code
* @see #RESULT_ERROR_UNKNOWN
* @see #RESULT_ERROR_INVALID_STATE
* @see #RESULT_ERROR_BAD_VALUE
* @see #RESULT_ERROR_PERMISSION_DENIED
* @see #RESULT_ERROR_IO
* @see #RESULT_ERROR_NOT_SUPPORTED
* @see #RESULT_INFO_SKIPPED
*/
@Override
@ResultCode
public int getResultCode() {
return mResultCode;
}
/**
* Gets the completion time of the command. Being more specific, it's the same as
* {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
*
* @return completion time of the command
*/
@Override
public long getCompletionTime() {
return mCompletionTime;
}
/**
* Gets the {@link MediaItem} for which the command was executed. In other words, this is
* the item sent as an argument of the command if any, otherwise the current media item when
* the command completed.
*
* @return media item when the command completed. Can be {@code null} for an error, or
* the current media item was {@code null}
*/
@Override
@Nullable
public MediaItem getMediaItem() {
return mItem;
}
}
}
AudioAttributes
.
*/
@NonNull
public abstract ListenableFuture
*
*
* @see #getTracks
*/
@VersionedParcelize(isCustom = true)
public static class TrackInfo extends CustomVersionedParcelable {
public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
public static final int MEDIA_TRACK_TYPE_METADATA = 5;
private static final String KEY_IS_FORMAT_NULL =
"androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_FORMAT_NULL";
private static final String KEY_IS_SELECTABLE =
"androidx.media2.common.SessionPlayer.TrackInfo.KEY_IS_SELECTABLE";
/**
* @hide
*/
@IntDef(flag = false, /*prefix = "MEDIA_TRACK_TYPE",*/ value = {
MEDIA_TRACK_TYPE_UNKNOWN,
MEDIA_TRACK_TYPE_VIDEO,
MEDIA_TRACK_TYPE_AUDIO,
MEDIA_TRACK_TYPE_SUBTITLE,
MEDIA_TRACK_TYPE_METADATA,
})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
public @interface MediaTrackType {}
@ParcelField(1)
int mId;
// Removed @ParcelField(2)
@ParcelField(3)
int mTrackType;
// Parceled via mParcelableExtras.
@NonParcelField
@Nullable
MediaFormat mFormat;
// Parceled via mParcelableExtras.
@NonParcelField
boolean mIsSelectable;
// For extra information containing MediaFormat and isSelectable data. Should only be used
// by onPreParceling() and onPostParceling().
@ParcelField(4)
Bundle mParcelableExtras;
@NonParcelField
private final Object mLock = new Object();
// WARNING: Adding a new ParcelField may break old library users (b/152830728)
/**
* Used for VersionedParcelable
*/
TrackInfo() {
// no-op
}
/**
* Constructor to create a TrackInfo instance.
*
* Note: The default value for {@link #isSelectable()} is false.
*
* @param id id of track unique across {@link MediaItem}s
* @param type type of track. Can be video, audio or subtitle
* @param format format of track
*/
public TrackInfo(int id, int type, @Nullable MediaFormat format) {
this(id, type, format, /* isSelectable= */ false);
}
/**
* Constructor to create a TrackInfo instance.
*
* @param id id of track unique across {@link MediaItem}s
* @param type type of track. Can be video, audio or subtitle
* @param format format of track
* @param isSelectable whether track can be selected via
* {@link SessionPlayer#selectTrack(TrackInfo)}.
*/
public TrackInfo(int id, int type, @Nullable MediaFormat format, boolean isSelectable) {
mId = id;
mTrackType = type;
mFormat = format;
mIsSelectable = isSelectable;
}
/**
* Gets the track type.
* @return MediaTrackType which indicates if the track is video, audio or subtitle
*/
@MediaTrackType
public int getTrackType() {
return mTrackType;
}
/**
* Gets the language code of the track.
* @return {@link Locale} which includes the language information
*/
@NonNull
public Locale getLanguage() {
String language = mFormat != null ? mFormat.getString(MediaFormat.KEY_LANGUAGE) : null;
if (language == null) {
language = "und";
}
return new Locale(language);
}
/**
* Gets the {@link MediaFormat} of the track. If the format is
* unknown or could not be determined, null is returned.
*/
@Nullable
public MediaFormat getFormat() {
return mFormat;
}
/**
* Gets the id of the track.
* The id is used by {@link #selectTrack(TrackInfo)} and {@link #deselectTrack(TrackInfo)}
* to identify the track to be (de)selected.
* So, it's highly recommended to ensure that the id of each track is unique across
* {@link MediaItem}s to avoid potential mis-selection when a stale {@link TrackInfo} is
* used.
*
* @return id of the track
*/
public int getId() {
return mId;
}
/**
* Whether the current track can be selected via {@link #selectTrack(TrackInfo)} or not.
*
* @return true if the current track can be selected; false if otherwise.
*/
public boolean isSelectable() {
return mIsSelectable;
}
@Override
@NonNull
public String toString() {
StringBuilder out = new StringBuilder(128);
out.append(getClass().getName());
out.append('#').append(mId);
out.append('{');
switch (mTrackType) {
case MEDIA_TRACK_TYPE_VIDEO:
out.append("VIDEO");
break;
case MEDIA_TRACK_TYPE_AUDIO:
out.append("AUDIO");
break;
case MEDIA_TRACK_TYPE_SUBTITLE:
out.append("SUBTITLE");
break;
case MEDIA_TRACK_TYPE_METADATA:
out.append("METADATA");
break;
default:
out.append("UNKNOWN");
break;
}
out.append(", ").append(mFormat);
out.append(", isSelectable=").append(mIsSelectable);
out.append("}");
return out.toString();
}
@Override
public int hashCode() {
return mId;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TrackInfo)) {
return false;
}
TrackInfo other = (TrackInfo) obj;
return mId == other.mId;
}
/**
* @hide
* @param isStream
*/
@RestrictTo(LIBRARY)
@Override
public void onPreParceling(boolean isStream) {
synchronized (mLock) {
mParcelableExtras = new Bundle();
mParcelableExtras.putBoolean(KEY_IS_FORMAT_NULL, mFormat == null);
if (mFormat != null) {
putStringValueToBundle(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
putStringValueToBundle(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
putIntValueToBundle(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
mParcelableExtras);
putIntValueToBundle(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
putIntValueToBundle(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
}
mParcelableExtras.putBoolean(KEY_IS_SELECTABLE, mIsSelectable);
}
}
/**
* @hide
*/
@RestrictTo(LIBRARY)
@Override
public void onPostParceling() {
if (mParcelableExtras != null && !mParcelableExtras.getBoolean(KEY_IS_FORMAT_NULL)) {
mFormat = new MediaFormat();
setStringValueToMediaFormat(MediaFormat.KEY_LANGUAGE, mFormat, mParcelableExtras);
setStringValueToMediaFormat(MediaFormat.KEY_MIME, mFormat, mParcelableExtras);
setIntValueToMediaFormat(MediaFormat.KEY_IS_FORCED_SUBTITLE, mFormat,
mParcelableExtras);
setIntValueToMediaFormat(MediaFormat.KEY_IS_AUTOSELECT, mFormat, mParcelableExtras);
setIntValueToMediaFormat(MediaFormat.KEY_IS_DEFAULT, mFormat, mParcelableExtras);
}
if (mParcelableExtras == null || !mParcelableExtras.containsKey(KEY_IS_SELECTABLE)) {
mIsSelectable = mTrackType != MEDIA_TRACK_TYPE_VIDEO;
} else {
mIsSelectable = mParcelableExtras.getBoolean(KEY_IS_SELECTABLE);
}
}
private static void putIntValueToBundle(
String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
if (mediaFormat.containsKey(intValueKey)) {
bundle.putInt(intValueKey, mediaFormat.getInteger(intValueKey));
}
}
private static void putStringValueToBundle(
String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
if (mediaFormat.containsKey(stringValueKey)) {
bundle.putString(stringValueKey, mediaFormat.getString(stringValueKey));
}
}
private static void setIntValueToMediaFormat(
String intValueKey, MediaFormat mediaFormat, Bundle bundle) {
if (bundle.containsKey(intValueKey)) {
mediaFormat.setInteger(intValueKey, bundle.getInt(intValueKey));
}
}
private static void setStringValueToMediaFormat(
String stringValueKey, MediaFormat mediaFormat, Bundle bundle) {
if (bundle.containsKey(stringValueKey)) {
mediaFormat.setString(stringValueKey, bundle.getString(stringValueKey));
}
}
}
/**
* A callback class to receive notifications for events on the session player. See
* {@link #registerPlayerCallback(Executor, PlayerCallback)} to register this callback.
*/
public abstract static class PlayerCallback {
/**
* Called when the state of the player has changed.
*
* @param player the player whose state has changed
* @param playerState the new state of the player
* @see #getPlayerState()
*/
public void onPlayerStateChanged(@NonNull SessionPlayer player,
@PlayerState int playerState) {
}
/**
* Called when a buffering events for a media item happened.
*
* @param player the player that is buffering
* @param item the media item for which buffering is happening
* @param buffState the new buffering state
* @see #getBufferingState()
*/
public void onBufferingStateChanged(@NonNull SessionPlayer player,
@Nullable MediaItem item, @BuffState int buffState) {
}
/**
* Called when the playback speed has changed.
*
* @param player the player that has changed the playback speed
* @param playbackSpeed the new playback speed
* @see #getPlaybackSpeed()
*/
public void onPlaybackSpeedChanged(@NonNull SessionPlayer player,
float playbackSpeed) {
}
/**
* Called when {@link #seekTo(long)} is completed.
*
* @param player the player that has completed seeking
* @param position the previous seeking request
* @see #getCurrentPosition()
*/
public void onSeekCompleted(@NonNull SessionPlayer player, long position) {
}
/**
* Called when a playlist is changed. It's also called after {@link #setPlaylist} or
* {@link #setMediaItem}.
*
* @param player the player that has changed the playlist and playlist metadata
* @param list new playlist
* @param metadata new metadata
* @see #getPlaylist()
* @see #getPlaylistMetadata()
*/
public void onPlaylistChanged(@NonNull SessionPlayer player,
@Nullable List
*
*/
public static class PlayerResult implements BaseResult {
/**
* @hide
*/
@IntDef(flag = false, /*prefix = "RESULT",*/ value = {
RESULT_SUCCESS,
RESULT_ERROR_UNKNOWN,
RESULT_ERROR_INVALID_STATE,
RESULT_ERROR_BAD_VALUE,
RESULT_ERROR_PERMISSION_DENIED,
RESULT_ERROR_IO,
RESULT_ERROR_NOT_SUPPORTED,
RESULT_INFO_SKIPPED})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY)
public @interface ResultCode {}
private final int mResultCode;
private final long mCompletionTime;
private final MediaItem mItem;
/**
* Constructor that uses the current system clock as the completion time.
*
* @param resultCode result code. Recommends to use the standard code defined here.
* @param item media item when the command completed
*/
// Note: resultCode is intentionally not annotated for subclass to return extra error codes.
public PlayerResult(int resultCode, @Nullable MediaItem item) {
this(resultCode, item, SystemClock.elapsedRealtime());
}
// Note: resultCode is intentionally not annotated for subclass to return extra error codes.
private PlayerResult(int resultCode, @Nullable MediaItem item, long completionTime) {
mResultCode = resultCode;
mItem = item;
mCompletionTime = completionTime;
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@NonNull
public static ListenableFuture