/*
* 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.os.Build;
import android.os.SystemClock;
import android.util.Log;
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 com.google.common.util.concurrent.ListenableFuture;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Base interface for all media players that want media session.
* <p>
* APIs that return {@link ListenableFuture} should be the asynchronous calls and shouldn't block
* the calling thread. This guarantees the APIs are safe to be called on the main thread.
*
* <p>Topics covered here are:
* <ol>
* <li><a href="#BestPractices">Best practices</a>
* <li><a href="#PlayerStates">Player states</a>
* <li><a href="#InvalidStates">Invalid method calls</a>
* </ol>
*
* <h3 id="BestPractices">Best practices</h3>
*
* Here are best practices when implementing/using SessionPlayer:
*
* <ul>
* <li>When updating UI, you should respond to {@link PlayerCallback} invocations instead of
* {@link PlayerResult} objects since the player can be controlled by others.
* <li>When a SessionPlayer object is no longer being used, call {@link #close()} as soon as
* possible to release the resources used by the internal player engine associated with the
* SessionPlayer. For example, if a player uses hardware decoder, other player instances may
* fallback to software decoders or fail to play. You cannot use SessionPlayer instance after
* you call {@link #close()}. There is no way to reuse the instance.
* <li>The current playback position can be retrieved with a call to {@link #getCurrentPosition()},
* which is helpful for applications such as a music player that need to keep track of the playback
* progress.
* <li>The playback position can be adjusted with a call to {@link #seekTo(long)}. Although the
* asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
* while to finish, especially for audio/video being streamed.
* <li>You can call {@link #seekTo(long)} from the {@link #PLAYER_STATE_PAUSED}. In these cases, if
* you are playing a video stream and the requested position is valid, one video frame may be
* displayed.
* </ul>
*
* <h3 id="PlayerStates">Player states</h3>
* The playback control of audio/video files is managed as a state machine. The SessionPlayer
* defines four states:
* <ol>
* <li>{@link #PLAYER_STATE_IDLE}: Initial state after the instantiation.
* <p>
* While in this state, you should call {@link #setMediaItem(MediaItem)} or
* {@link #setPlaylist(List, MediaMetadata)}. Check returned {@link ListenableFuture} for
* potential error.
* <p>
* Calling {@link #prepare()} transfers this object to {@link #PLAYER_STATE_PAUSED}.
*
* <li>{@link #PLAYER_STATE_PAUSED}: State when the audio/video playback is paused.
* <p>
* Call {@link #play()} to resume or start playback from the position where it paused.
*
* <li>{@link #PLAYER_STATE_PLAYING}: State when the player plays the media item.
* <p>
* In this state, {@link PlayerCallback#onBufferingStateChanged(
* SessionPlayer, MediaItem, int)} will be called regularly to tell the buffering status.
* <p>
* Playback state would remain {@link #PLAYER_STATE_PLAYING} when the currently playing
* media item is changed.
* <p>
* When the playback reaches the end of stream, the behavior depends on repeat mode, set by
* {@link #setRepeatMode(int)}. If the repeat mode was set to {@link #REPEAT_MODE_NONE},
* the player will transfer to the {@link #PLAYER_STATE_PAUSED}. Otherwise, the
* SessionPlayer object remains in the {@link #PLAYER_STATE_PLAYING} and playback will be
* ongoing.
*
* <li>{@link #PLAYER_STATE_ERROR}: State when the playback failed and player cannot be
* recovered by itself.
* <p>
* In general, playback might fail due to various reasons such as unsupported audio/video
* format, poorly interleaved audio/video, resolution too high, streaming timeout, and
* others. In addition, due to programming errors, a playback control operation might be
* performed from an <a href="#InvalidStates">invalid state</a>. In these cases the player
* may transition to this state.
* </ol>
* <p>
*
* <h3 id="InvalidStates">Invalid method calls</h3>
* The only method you safely call from the {@link #PLAYER_STATE_ERROR} is {@link #close()}.
* Any other methods might throw an exception or return meaningless data.
* <p>
* Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error
* state and/or provide a method to recover from the error state. Take a look at documentations of
* specific class that you're interested in.
* <p>
* Most methods can be called from any non-Error state. They will either perform their work or
* silently have no effect. The following table lists the methods that aren't guaranteed to
* successfully running if they're called from the associated invalid states.
* <p>
* <table>
* <tr><th>Method Name</th> <th>Invalid States</th></tr>
* <tr><td>setMediaItem</td> <td>{Paused, Playing}</td></tr>
* <tr><td>setPlaylist</td> <td>{Paused, Playing}</td></tr>
* <tr><td>prepare</td> <td>{Paused, Playing}</td></tr>
* <tr><td>play</td> <td>{Idle}</td></tr>
* <tr><td>pause</td> <td>{Idle}</td></tr>
* <tr><td>seekTo</td> <td>{Idle}</td></tr>
* </table>
*/
// Previously MediaSessionCompat.Callback.
// Players can extend this directly (e.g. MediaPlayer) or create wrapper and control underlying
// player.
// Preferably it can be interface, but API guideline requires to use abstract class.
@TargetApi(Build.VERSION_CODES.P)
public abstract class SessionPlayer implements AutoCloseable {
private static final String TAG = "SessionPlayer";
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@IntDef({
PLAYER_STATE_IDLE,
PLAYER_STATE_PAUSED,
PLAYER_STATE_PLAYING,
PLAYER_STATE_ERROR})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerState {
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@IntDef({
BUFFERING_STATE_UNKNOWN,
BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
BUFFERING_STATE_BUFFERING_AND_STARVED,
BUFFERING_STATE_COMPLETE})
@Retention(RetentionPolicy.SOURCE)
public @interface BuffState {
}
/**
* State when the player is idle, and needs configuration to start playback.
*/
public static final int PLAYER_STATE_IDLE = 0;
/**
* State when the player's playback is paused
*/
public static final int PLAYER_STATE_PAUSED = 1;
/**
* State when the player's playback is ongoing
*/
public static final int PLAYER_STATE_PLAYING = 2;
/**
* State when the player is in error state and cannot be recovered self.
*/
public static final int PLAYER_STATE_ERROR = 3;
/**
* Buffering state is unknown.
*/
public static final int BUFFERING_STATE_UNKNOWN = 0;
/**
* Buffering state indicating the player is buffering but enough has been buffered
* for this player to be able to play the content.
* See {@link #getBufferedPosition()} for how far is buffered already.
*/
public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1;
/**
* Buffering state indicating the player is buffering, but the player is currently starved
* for data, and cannot play.
*/
public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2;
/**
* Buffering state indicating the player is done buffering, and the remainder of the content is
* available for playback.
*/
public static final int BUFFERING_STATE_COMPLETE = 3;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
REPEAT_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
public @interface RepeatMode {
}
/**
* Playback will be stopped at the end of the playing media list.
*/
public static final int REPEAT_MODE_NONE = 0;
/**
* Playback of the current playing media item will be repeated.
*/
public static final int REPEAT_MODE_ONE = 1;
/**
* Playing media list will be repeated.
*/
public static final int REPEAT_MODE_ALL = 2;
/**
* Playback of the playing media group will be repeated.
* A group is a logical block of media items which is specified in the section 5.7 of the
* Bluetooth AVRCP 1.6. An example of a group is the playlist.
*/
public static final int REPEAT_MODE_GROUP = 3;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
@Retention(RetentionPolicy.SOURCE)
public @interface ShuffleMode {
}
/**
* Media list will be played in order.
*/
public static final int SHUFFLE_MODE_NONE = 0;
/**
* Media list will be played in shuffled order.
*/
public static final int SHUFFLE_MODE_ALL = 1;
/**
* Media group will be played in shuffled order.
* A group is a logical block of media items which is specified in the section 5.7 of the
* Bluetooth AVRCP 1.6. An example of a group is the playlist.
*/
public static final int SHUFFLE_MODE_GROUP = 2;
public static final long UNKNOWN_TIME = -1;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final List<Pair<PlayerCallback, Executor>> mCallbacks = new ArrayList<>();
/**
* Plays the playback.
*/
public abstract @NonNull ListenableFuture<PlayerResult> play();
/**
* Pauses playback.
*/
public abstract @NonNull ListenableFuture<PlayerResult> pause();
/**
* Prepares the media items for playback. During this time, the player may allocate resources
* required to play, such as audio and video decoders.
*/
public abstract @NonNull ListenableFuture<PlayerResult> prepare();
/**
* Seeks to the specified position. Moves the playback head to the specified position.
*
* @param position the new playback position in ms. The value should be in the range of start
* and end positions defined in {@link MediaItem}.
*/
public abstract @NonNull ListenableFuture<PlayerResult> seekTo(long position);
/**
* Sets the playback speed. A value of {@code 1.0f} is the default playback value.
* <p>
* After changing the playback speed, it is recommended to query the actual speed supported
* by the player, see {@link #getPlaybackSpeed()}.
*
* @param playbackSpeed playback speed
*/
public abstract @NonNull ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed);
/**
* Sets the {@link AudioAttributesCompat} to be used during the playback of the media.
* <p>
* You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
* become effective thereafter.
*
* @param attributes non-null <code>AudioAttributes</code>.
*/
public abstract @NonNull ListenableFuture<PlayerResult> setAudioAttributes(
@NonNull AudioAttributesCompat attributes);
/**
* Gets the current player state.
*
* @return the current player state
* @see PlayerCallback#onPlayerStateChanged(SessionPlayer, int)
* @see #PLAYER_STATE_IDLE
* @see #PLAYER_STATE_PAUSED
* @see #PLAYER_STATE_PLAYING
* @see #PLAYER_STATE_ERROR
*/
public abstract @PlayerState int getPlayerState();
/**
* Gets the current playback head position.
*
* @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.
*
* @return the duration in ms, or {@link #UNKNOWN_TIME}.
*/
public abstract long getDuration();
/**
* Gets the buffered position of current playback, or {@link #UNKNOWN_TIME} if unknown.
* @return the buffered position in ms, or {@link #UNKNOWN_TIME}.
*/
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.
* @see #getBufferedPosition()
*/
public abstract @BuffState int getBufferingState();
/**
* Gets the actual playback speed to be used by the player when playing.
* <p>
* Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
*
* @return the actual playback speed
*/
public abstract float getPlaybackSpeed();
/**
* Sets a list of {@link MediaItem} with metadata. Ensure uniqueness of each {@link MediaItem}
* in the playlist so the session can uniquely identity individual items. All
* {@link MediaItem}s shouldn't be {@code null} as well.
* <p>
* It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
* duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
* it to the controller.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* 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 PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
public abstract @NonNull ListenableFuture<PlayerResult> setPlaylist(
@NonNull List<MediaItem> list, @Nullable MediaMetadata metadata);
/**
* Gets the {@link AudioAttributesCompat} that media player has.
*/
public abstract @Nullable AudioAttributesCompat getAudioAttributes();
/**
* Sets a {@link MediaItem} for playback.
* <p>
* It's recommended to fill {@link MediaMetadata} in each {@link MediaItem} especially for the
* duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
* it to the controller.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* @param item the descriptor of media item you want to play
* @return a {@link ListenableFuture} which represents the pending completion of the command.
* @throws IllegalArgumentException if the given item is {@code null}.
*/
public abstract @NonNull ListenableFuture<PlayerResult> setMediaItem(
@NonNull MediaItem item);
/**
* Adds the media item to the playlist at position index. Index equals or greater than
* the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
* the playlist.
* <p>
* The implementation may not change the currently playing media item.
* If index is less than or equal to the current index of the playlist,
* the current index of the playlist will be increased correspondingly.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* @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)
*/
public abstract @NonNull ListenableFuture<PlayerResult> addPlaylistItem(int index,
@NonNull MediaItem item);
/**
* Removes the media item from the playlist
* <p>
* The implementation may not change the currently playing media item even when it's removed.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* @param index the index of the item you want to remove in the playlist
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
public abstract @NonNull ListenableFuture<PlayerResult> removePlaylistItem(
@IntRange(from = 0) int index);
/**
* Replaces the media item at index in the playlist. This can be also used to update metadata of
* an item.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
*
* @param index the index of the item to replace in the playlist
* @param item the new item
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
public abstract @NonNull ListenableFuture<PlayerResult> replacePlaylistItem(int index,
@NonNull MediaItem item);
/**
* Skips to the previous item in the playlist.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
public abstract @NonNull ListenableFuture<PlayerResult> skipToPreviousPlaylistItem();
/**
* Skips to the next item in the playlist.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
public abstract @NonNull ListenableFuture<PlayerResult> skipToNextPlaylistItem();
/**
* Skips to the the media item.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
*
* @param index The index of the item you want to play in the playlist
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
public abstract @NonNull ListenableFuture<PlayerResult> skipToPlaylistItem(
@IntRange(from = 0) int index);
/**
* Updates the playlist metadata while keeping the playlist as-is.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)} when it's
* completed.
*
* @param metadata metadata of the playlist
* @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
*/
public abstract @NonNull ListenableFuture<PlayerResult> updatePlaylistMetadata(
@Nullable MediaMetadata metadata);
/**
* Sets the repeat mode.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onRepeatModeChanged(SessionPlayer, int)} when it's completed.
*
* @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)
*/
public abstract @NonNull ListenableFuture<PlayerResult> setRepeatMode(
@RepeatMode int repeatMode);
/**
* Sets the shuffle mode.
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onShuffleModeChanged(SessionPlayer, int)} when it's completed.
*
* @param shuffleMode The shuffle mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
* @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
*/
public abstract @NonNull ListenableFuture<PlayerResult> setShuffleMode(
@ShuffleMode int shuffleMode);
/**
* Gets the playlist.
*
* @return playlist, or null if none is set.
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
*/
public abstract @Nullable List<MediaItem> getPlaylist();
/**
* Gets the playlist metadata.
*
* @return metadata metadata of the playlist, or null if none is set
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
* @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
*/
public abstract @Nullable MediaMetadata getPlaylistMetadata();
/**
* Gets the repeat mode.
*
* @return repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
* @see PlayerCallback#onRepeatModeChanged(SessionPlayer, int)
*/
public abstract @RepeatMode int getRepeatMode();
/**
* Gets the shuffle mode.
*
* @return The shuffle mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
* @see PlayerCallback#onShuffleModeChanged(SessionPlayer, int)
*/
public abstract @ShuffleMode int getShuffleMode();
/**
* Gets the current media item.
*
* @return the current media item. Can be {@code null} only when media item or playlist hasn't
* been set.
*/
public abstract @Nullable MediaItem getCurrentMediaItem();
/**
* Gets the index of current media item in playlist. This value may be updated when
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
*
* @return the index of current media item. Can be -1 only when current media item is null or
* playlist hasn't been set.
*/
public abstract int getCurrentMediaItemIndex();
/**
* Gets the previous item index in the playlist. The returned value can be outdated after
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
*
* @return the index of previous media item. Can be -1 only when previous media item does not
* exist or playlist hasn't been set.
*/
public abstract int getPreviousMediaItemIndex();
/**
* Gets the next item index in the playlist. The returned value can be outdated after
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} or
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} is called.
*
* @return the index of next media item. Can be -1 only when next media item does not exist or
* playlist hasn't been set.
*/
public abstract int getNextMediaItemIndex();
// Listeners / Callback related
// Intentionally final not to allow developers to change the behavior
/**
* Register {@link PlayerCallback} to listen changes.
*
* @param executor a callback Executor
* @param callback a PlayerCallback
* @throws IllegalArgumentException if executor or callback is {@code null}.
*/
public final void registerPlayerCallback(
@NonNull /*@CallbackExecutor*/ Executor executor,
@NonNull PlayerCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor shouldn't be null");
}
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
synchronized (mLock) {
for (Pair<PlayerCallback, Executor> pair : mCallbacks) {
if (pair.first == callback && pair.second != null) {
Log.w(TAG, "callback is already added. Ignoring.");
return;
}
}
mCallbacks.add(new Pair<>(callback, executor));
}
}
/**
* Unregister the previously registered {@link PlayerCallback}.
*
* @param callback the callback to be removed
* @throws IllegalArgumentException if the callback is {@code null}.
*/
public final void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
synchronized (mLock) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
if (mCallbacks.get(i).first == callback) {
mCallbacks.remove(i);
}
}
}
}
/**
* Gets the callbacks with executors for subclasses to notify player events.
*
* @return map of callbacks and its executors
*/
protected final @NonNull List<Pair<PlayerCallback, Executor>> getCallbacks() {
List<Pair<PlayerCallback, Executor>> list = new ArrayList<>();
synchronized (mLock) {
list.addAll(mCallbacks);
}
return list;
}
/**
* 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.
*
* @param player the player that has changed the playlist and playlist metadata.
* @param list new playlist
* @param metadata new metadata
* @see #getPlaylist()
* @see #getPlaylistMetadata()
*/
public void onPlaylistChanged(@NonNull SessionPlayer player,
@Nullable List<MediaItem> list, @Nullable MediaMetadata metadata) {
}
/**
* Called when a playlist metadata is changed.
*
* @param player the player that has changed the playlist metadata.
* @param metadata new metadata
* @see #getPlaylistMetadata()
*/
public void onPlaylistMetadataChanged(@NonNull SessionPlayer player,
@Nullable MediaMetadata metadata) {
}
/**
* Called when the shuffle mode is changed.
*
* @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.
*
* @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.
*
* @param player the player whose media item changed.
* @param item the new current media item.
* @see #getCurrentMediaItem()
*/
public void onCurrentMediaItemChanged(@NonNull SessionPlayer player,
@NonNull MediaItem item) {
}
/**
* Called when the player finished playing. Playback state would be also set
* {@link #PLAYER_STATE_PAUSED} with it.
* <p>
* This will be called only when the repeat mode is set to {@link #REPEAT_MODE_NONE}.
*
* @param player the player whose playback is completed.
* @see #REPEAT_MODE_NONE
*/
public void onPlaybackCompleted(@NonNull SessionPlayer player) {
}
/**
* Called when the player's current audio attributes are changed.
*
* @param player the player whose audio attributes are changed.
* @param attributes the new current audio attributes
* @see #getAudioAttributes()
*/
public void onAudioAttributesChanged(@NonNull SessionPlayer player,
@Nullable AudioAttributesCompat attributes) {
}
}
/**
* Result class of the asynchronous APIs.
* <p>
* Subclass may extend this class for providing more result and/or custom result code. For the
* custom result code, follow the convention below to avoid potential code duplication.
* <p>
* <ul>
* <li>Predefined error code: Negative integers greater than -100. (i.e. -100 < code < 0)
* <li>Custom error code: Negative integers equal to or less than -1000. (i.e. code < -1000)
* <li>Predefined info code: Positive integers less than 100. (i.e. 0 < code < 100)
* <li>Custom Info code: Positive integers equal to or greater than 1000. (i.e. code > +1000)
* </ul>
*/
public static class PlayerResult implements BaseResult {
/**
* @hide
*/
@IntDef(flag = false, /*prefix = "RESULT_CODE",*/ value = {
RESULT_CODE_SUCCESS,
RESULT_CODE_UNKNOWN_ERROR,
RESULT_CODE_INVALID_STATE,
RESULT_CODE_BAD_VALUE,
RESULT_CODE_PERMISSION_DENIED,
RESULT_CODE_IO_ERROR,
RESULT_CODE_SKIPPED})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
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 is 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;
}
static ListenableFuture<PlayerResult> createFuture(int resultCode) {
ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
result.set(new PlayerResult(resultCode, null));
return result;
}
/**
* Gets the result code.
* <p>
* Subclass of the {@link SessionPlayer} may have defined customized extra code other than
* codes defined here. Check the documentation of the class that you're interested in.
*
* @return result code.
* @see #RESULT_CODE_UNKNOWN_ERROR
* @see #RESULT_CODE_INVALID_STATE
* @see #RESULT_CODE_BAD_VALUE
* @see #RESULT_CODE_PERMISSION_DENIED
* @see #RESULT_CODE_IO_ERROR
* @see #RESULT_CODE_SKIPPED
*/
@Override
public @ResultCode 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 is 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 was completed.
*
* @return media item when the command is completed. Can be {@code null} for an error, or
* the current media item was {@code null}.
*/
@Override
public @Nullable MediaItem getMediaItem() {
return mItem;
}
}
}