Gradle dependencies
compile group: 'androidx.media', name: 'media-widget', version: '1.0.0-alpha06'
- groupId: androidx.media
- artifactId: media-widget
- version: 1.0.0-alpha06
Artifact androidx.media:media-widget:1.0.0-alpha06 it located at Google repository (https://maven.google.com/)
Summary
| Fields | 
|---|
| from RemoteSessionPlayer | VOLUME_CONTROL_ABSOLUTE, VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE | 
| from SessionPlayer | BUFFERING_STATE_BUFFERING_AND_PLAYABLE, BUFFERING_STATE_BUFFERING_AND_STARVED, BUFFERING_STATE_COMPLETE, BUFFERING_STATE_UNKNOWN, PLAYER_STATE_ERROR, PLAYER_STATE_IDLE, PLAYER_STATE_PAUSED, PLAYER_STATE_PLAYING, REPEAT_MODE_ALL, REPEAT_MODE_GROUP, REPEAT_MODE_NONE, REPEAT_MODE_ONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP, SHUFFLE_MODE_NONE, UNKNOWN_TIME | 
| Methods | 
|---|
| public abstract <any> | addPlaylistItem(int index, MediaItem item) 
 Adds the media item to the playlist at position index. | 
| public abstract java.util.concurrent.Future<SessionPlayer.PlayerResult> | adjustVolume(int direction) 
 Adjust player volume with the direction. | 
| public void | close() 
 | 
| public abstract AudioAttributesCompat | getAudioAttributes() 
 Gets the AudioAttributesCompat that media player has. | 
| public abstract long | getBufferedPosition() 
 Gets the buffered position of current playback, or SessionPlayer.UNKNOWN_TIME if unknown. | 
| public abstract int | getBufferingState() 
 Returns the current buffering state of the player. | 
| public abstract MediaItem | getCurrentMediaItem() 
 Gets the current media item. | 
| public abstract int | getCurrentMediaItemIndex() 
 Gets the index of current media item in playlist. | 
| public abstract long | getCurrentPosition() 
 Gets the current playback head position. | 
| public abstract long | getDuration() 
 Gets the duration of the current media item, or SessionPlayer.UNKNOWN_TIME if unknown. | 
| public abstract int | getMaxVolume() 
 Gets the maximum volume that can be used in RemoteSessionPlayer.setVolume(int). | 
| public abstract int | getNextMediaItemIndex() 
 Gets the next item index in the playlist. | 
| public abstract float | getPlaybackSpeed() 
 Gets the actual playback speed to be used by the player when playing. | 
| public abstract int | getPlayerState() 
 Gets the current player state. | 
| public abstract java.util.List<MediaItem> | getPlaylist() 
 Gets the playlist. | 
| public abstract MediaMetadata | getPlaylistMetadata() 
 Gets the playlist metadata. | 
| public abstract int | getPreviousMediaItemIndex() 
 Gets the previous item index in the playlist. | 
| public abstract int | getRepeatMode() 
 Gets the repeat mode. | 
| public abstract int | getShuffleMode() 
 Gets the shuffle mode. | 
| public abstract int | getVolume() 
 Gets the current volume of this player to this player. | 
| public abstract int | getVolumeControlType() 
 Gets the volume type. | 
| public abstract <any> | pause() 
 Pauses playback. | 
| public abstract <any> | play() 
 Plays the playback. | 
| public abstract <any> | prepare() 
 Prepares the media items for playback. | 
| public abstract <any> | removePlaylistItem(int index) 
 Removes the media item from the playlist | 
| public abstract <any> | replacePlaylistItem(int index, MediaItem item) 
 Replaces the media item at index in the playlist. | 
| public abstract <any> | seekTo(long position) 
 Seeks to the specified position. | 
| public abstract <any> | setAudioAttributes(AudioAttributesCompat attributes) 
 Sets the AudioAttributesCompat to be used during the playback of the media. | 
| public abstract <any> | setMediaItem(MediaItem item) 
 Sets a MediaItem for playback. | 
| public abstract <any> | setPlaybackSpeed(float playbackSpeed) 
 Sets the playback speed. | 
| public abstract <any> | setPlaylist(java.util.List<MediaItem> list, MediaMetadata metadata) 
 Sets a list of MediaItem with metadata. | 
| public abstract <any> | setRepeatMode(int repeatMode) 
 Sets the repeat mode. | 
| public abstract <any> | setShuffleMode(int shuffleMode) 
 Sets the shuffle mode. | 
| public abstract java.util.concurrent.Future<SessionPlayer.PlayerResult> | setVolume(int volume) 
 Sets the volume of the audio of the media to play, expressed as a linear multiplier
 on the audio samples. | 
| public abstract <any> | skipToNextPlaylistItem() 
 Skips to the next item in the playlist. | 
| public abstract <any> | skipToPlaylistItem(int index) 
 Skips to the the media item. | 
| public abstract <any> | skipToPreviousPlaylistItem() 
 Skips to the previous item in the playlist. | 
| public abstract <any> | updatePlaylistMetadata(MediaMetadata metadata) 
 Updates the playlist metadata while keeping the playlist as-is. | 
| from SessionPlayer | getCallbacks, registerPlayerCallback, unregisterPlayerCallback | 
| from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait | 
Constructors
Methods
public abstract <any> 
play()
Plays the playback.
public abstract <any> 
prepare()
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 <any> 
pause()
Pauses playback.
public abstract <any> 
seekTo(long position)
Seeks to the specified position. Moves the playback head to the specified position.
Parameters:
position: the new playback position in ms. The value should be in the range of start
 and end positions defined in MediaItem.
public abstract long 
getCurrentPosition()
Gets the current playback head position.
Returns:
the current playback position in ms, or SessionPlayer.UNKNOWN_TIME if unknown.
public abstract long 
getDuration()
Gets the duration of the current media item, or SessionPlayer.UNKNOWN_TIME if unknown.
Returns:
the duration in ms, or SessionPlayer.UNKNOWN_TIME.
public abstract long 
getBufferedPosition()
Gets the buffered position of current playback, or SessionPlayer.UNKNOWN_TIME if unknown.
Returns:
the buffered position in ms, or SessionPlayer.UNKNOWN_TIME.
public abstract int 
getPlayerState()
Gets the current player state.
Returns:
the current player state
See also: SessionPlayer.PlayerCallback.onPlayerStateChanged(SessionPlayer, int), SessionPlayer.PLAYER_STATE_IDLE, SessionPlayer.PLAYER_STATE_PAUSED, SessionPlayer.PLAYER_STATE_PLAYING, SessionPlayer.PLAYER_STATE_ERROR
public abstract int 
getBufferingState()
Returns the current buffering state of the player.
 During the buffering, see SessionPlayer.getBufferedPosition() for the quantifying the amount
 already buffered.
Returns:
the buffering state.
See also: SessionPlayer.getBufferedPosition()
Sets the AudioAttributesCompat to be used during the playback of the media.
 
 You must call this method in SessionPlayer.PLAYER_STATE_IDLE in order for the audio attributes to
 become effective thereafter.
Parameters:
attributes: non-null AudioAttributes.
Gets the AudioAttributesCompat that media player has.
public abstract <any> 
setMediaItem(
MediaItem item)
Sets a MediaItem for playback.
 
 It's recommended to fill MediaMetadata in each MediaItem especially for the
 duration information with the key 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
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's
 completed.
Parameters:
item: the descriptor of media item you want to play
Returns:
a  which represents the pending completion of the command.
public abstract 
MediaItem getCurrentMediaItem()
Gets the current media item.
Returns:
the current media item. Can be null only when media item or playlist hasn't
         been set.
public abstract int 
getCurrentMediaItemIndex()
Gets the index of current media item in playlist. This value may be updated when
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.
Returns:
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 
getPreviousMediaItemIndex()
Gets the previous item index in the playlist. The returned value can be outdated after
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.
Returns:
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 
getNextMediaItemIndex()
Gets the next item index in the playlist. The returned value can be outdated after
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) or
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) is called.
Returns:
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 <any> 
setPlaybackSpeed(float playbackSpeed)
Sets the playback speed. A value of 1.0f is the default playback value.
 
 After changing the playback speed, it is recommended to query the actual speed supported
 by the player, see SessionPlayer.getPlaybackSpeed().
Parameters:
playbackSpeed: playback speed
public abstract float 
getPlaybackSpeed()
Gets the actual playback speed to be used by the player when playing.
 
 Note that it may differ from the speed set in SessionPlayer.setPlaybackSpeed(float).
Returns:
the actual playback speed
public abstract int 
getVolume()
Gets the current volume of this player to this player.
 
 Note that it does not take into account the associated stream volume because the playback is
 happening outside of the phone device.
Returns:
the player volume.
public abstract java.util.concurrent.Future<SessionPlayer.PlayerResult> 
adjustVolume(int direction)
Adjust player volume with the direction. Override this API to customize volume change in
 remote device
 
 Default implement adjust volume by 1
 
 This would be ignored when volume control type is RemoteSessionPlayer.VOLUME_CONTROL_FIXED.
Parameters:
direction: direction of the volume changes. Positive value for volume up, negative for
                  volume down.
public abstract java.util.concurrent.Future<SessionPlayer.PlayerResult> 
setVolume(int 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 indicates muting. See RemoteSessionPlayer.getMaxVolume() for the volume range
 supported by this player.
Parameters:
volume: a value between 0.0f and RemoteSessionPlayer.getMaxVolume().
public abstract int 
getMaxVolume()
Gets the maximum volume that can be used in RemoteSessionPlayer.setVolume(int).
Returns:
the maximum volume. Shouldn't be negative.
public abstract int 
getVolumeControlType()
Gets the volume type.
 
 This shouldn't be changed after instantiation.
Returns:
one of the volume type
See also: RemoteSessionPlayer.VOLUME_CONTROL_FIXED, RemoteSessionPlayer.VOLUME_CONTROL_RELATIVE, RemoteSessionPlayer.VOLUME_CONTROL_ABSOLUTE
public abstract <any> 
setPlaylist(java.util.List<MediaItem> list, 
MediaMetadata metadata)
Sets a list of MediaItem with metadata. Ensure uniqueness of each MediaItem
 in the playlist so the session can uniquely identity individual items. All
 MediaItems shouldn't be null as well.
 
 It's recommended to fill MediaMetadata in each MediaItem especially for the
 duration information with the key 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
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's
 completed.
Parameters:
list: A list of MediaItem objects to set as a play list.
Returns:
a  which represents the pending completion of the command.
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)
public abstract <any> 
addPlaylistItem(int index, 
MediaItem item)
Adds the media item to the playlist at position index. Index equals or greater than
 the current playlist size (e.g. MAX_VALUE) will add the item at the end of
 the playlist.
 
 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.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's
 completed.
Parameters:
index: the index of the item you want to add in the playlist
item: the media item you want to add
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)
public abstract <any> 
removePlaylistItem(int index)
Removes the media item from the playlist
 
 The implementation may not change the currently playing media item even when it's removed.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's
 completed.
Parameters:
index: the index of the item you want to remove in the playlist
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)
public abstract <any> 
replacePlaylistItem(int index, 
MediaItem item)
Replaces the media item at index in the playlist. This can be also used to update metadata of
 an item.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata) when it's
 completed.
Parameters:
index: the index of the item to replace in the playlist
item: the new item
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)
public abstract <any> 
skipToPreviousPlaylistItem()
Skips to the previous item in the playlist.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's
 completed.
See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)
public abstract <any> 
skipToNextPlaylistItem()
Skips to the next item in the playlist.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's
 completed.
See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)
public abstract <any> 
skipToPlaylistItem(int index)
Skips to the the media item.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem) when it's
 completed.
Parameters:
index: The index of the item you want to play in the playlist
See also: SessionPlayer.PlayerCallback.onCurrentMediaItemChanged(SessionPlayer, MediaItem)
public abstract <any> 
updatePlaylistMetadata(
MediaMetadata metadata)
Updates the playlist metadata while keeping the playlist as-is.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata) when it's
 completed.
Parameters:
metadata: metadata of the playlist
See also: SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
public abstract <any> 
setRepeatMode(int repeatMode)
Sets the repeat mode.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int) when it's completed.
Parameters:
repeatMode: repeat mode
See also: SessionPlayer.REPEAT_MODE_NONE, SessionPlayer.REPEAT_MODE_ONE, SessionPlayer.REPEAT_MODE_ALL, SessionPlayer.REPEAT_MODE_GROUP, SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int)
public abstract <any> 
setShuffleMode(int shuffleMode)
Sets the shuffle mode.
 
 The implementation must notify registered callbacks with
 SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int) when it's completed.
Parameters:
shuffleMode: The shuffle mode
See also: SessionPlayer.SHUFFLE_MODE_NONE, SessionPlayer.SHUFFLE_MODE_ALL, SessionPlayer.SHUFFLE_MODE_GROUP, SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int)
public abstract java.util.List<MediaItem> 
getPlaylist()
Gets the playlist.
Returns:
playlist, or null if none is set.
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata)
Gets the playlist metadata.
Returns:
metadata metadata of the playlist, or null if none is set
See also: SessionPlayer.PlayerCallback.onPlaylistChanged(SessionPlayer, List, MediaMetadata), SessionPlayer.PlayerCallback.onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
public abstract int 
getRepeatMode()
Gets the repeat mode.
Returns:
repeat mode
See also: SessionPlayer.REPEAT_MODE_NONE, SessionPlayer.REPEAT_MODE_ONE, SessionPlayer.REPEAT_MODE_ALL, SessionPlayer.REPEAT_MODE_GROUP, SessionPlayer.PlayerCallback.onRepeatModeChanged(SessionPlayer, int)
public abstract int 
getShuffleMode()
Gets the shuffle mode.
Returns:
The shuffle mode
See also: SessionPlayer.SHUFFLE_MODE_NONE, SessionPlayer.SHUFFLE_MODE_ALL, SessionPlayer.SHUFFLE_MODE_GROUP, SessionPlayer.PlayerCallback.onShuffleModeChanged(SessionPlayer, int)
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.widget;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_CODE_BAD_VALUE;
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_CODE_INVALID_STATE;
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_CODE_SUCCESS;
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_CODE_UNKNOWN_ERROR;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.util.Pair;
import androidx.media.AudioAttributesCompat;
import androidx.media2.MediaItem;
import androidx.media2.MediaMetadata;
import androidx.media2.RemoteSessionPlayer;
import androidx.media2.SessionPlayer;
import androidx.media2.UriMediaItem;
import androidx.mediarouter.media.MediaItemStatus;
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;
import androidx.mediarouter.media.MediaSessionStatus;
import androidx.mediarouter.media.RemotePlaybackClient;
import androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback;
import androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback;
import androidx.mediarouter.media.RemotePlaybackClient.StatusCallback;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
/**
 * @hide
 */
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(19)
public class RoutePlayer extends RemoteSessionPlayer {
    private static final String TAG = "RoutePlayer";
    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final int ITEM_NONE = -1;
    String mItemId;
    int mCurrentPlayerState;
    long mDuration;
    long mLastStatusChangedTime;
    long mPosition;
    boolean mCanResume;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    MediaRouter.RouteInfo mSelectedRoute;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final List<ResolvableFuture<PlayerResult>> mPendingVolumeResult = new ArrayList<>();
    private MediaItem mItem;
    private MediaRouter mMediaRouter;
    private RemotePlaybackClient mClient;
    private MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() {
        @Override
        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
            if (TextUtils.equals(route.getId(), mSelectedRoute.getId())) {
                final int volume = route.getVolume();
                for (int i = 0; i < mPendingVolumeResult.size(); i++) {
                    mPendingVolumeResult.get(i).set(new PlayerResult(
                            RESULT_CODE_SUCCESS, getCurrentMediaItem()));
                }
                mPendingVolumeResult.clear();
                List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
                for (Pair<PlayerCallback, Executor> pair : callbacks) {
                    if (pair.first instanceof RemoteSessionPlayer.Callback) {
                        final RemoteSessionPlayer.PlayerCallback callback = pair.first;
                        pair.second.execute(new Runnable() {
                            @Override
                            public void run() {
                                ((RemoteSessionPlayer.Callback) callback)
                                        .onVolumeChanged(RoutePlayer.this, volume);
                            }
                        });
                    }
                }
            }
        }
    };
    private StatusCallback mStatusCallback = new StatusCallback() {
        @Override
        public void onItemStatusChanged(Bundle data,
                String sessionId, MediaSessionStatus sessionStatus,
                String itemId, MediaItemStatus itemStatus) {
            if (DEBUG && !isSessionActive(sessionStatus)) {
                Log.v(TAG, "onItemStatusChanged() is called, but session is not active.");
            }
            mLastStatusChangedTime = SystemClock.elapsedRealtime();
            mPosition = itemStatus.getContentPosition();
            mCurrentPlayerState = convertPlaybackStateToPlayerState(itemStatus.getPlaybackState());
            List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
            for (Pair<PlayerCallback, Executor> pair : callbacks) {
                final PlayerCallback callback = pair.first;
                pair.second.execute(new Runnable() {
                    @Override
                    public void run() {
                        callback.onPlayerStateChanged(RoutePlayer.this, mCurrentPlayerState);
                    }
                });
            }
        }
    };
    public RoutePlayer(Context context, MediaRouteSelector selector,
            MediaRouter.RouteInfo route) {
        mMediaRouter = MediaRouter.getInstance(context);
        mMediaRouter.addCallback(selector, mRouterCallback);
        mSelectedRoute = route;
        mClient = new RemotePlaybackClient(context, route);
        mClient.setStatusCallback(mStatusCallback);
        if (mClient.isSessionManagementSupported()) {
            mClient.startSession(null, new SessionActionCallback() {
                @Override
                public void onResult(Bundle data,
                        String sessionId, MediaSessionStatus sessionStatus) {
                    if (DEBUG && !isSessionActive(sessionStatus)) {
                        Log.v(TAG, "RoutePlayer has been initialized, but session is not"
                                + "active.");
                    }
                }
            });
        }
    }
    @Override
    public ListenableFuture<PlayerResult> play() {
        if (mItem == null) {
            return createResult(RESULT_CODE_BAD_VALUE);
        }
        // RemotePlaybackClient cannot call resume(..) without calling pause(..) first.
        if (!mCanResume) {
            return playInternal();
        }
        if (mClient.isSessionManagementSupported()) {
            final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
            mClient.resume(null, new SessionActionCallback() {
                @Override
                public void onResult(Bundle data,
                        String sessionId, MediaSessionStatus sessionStatus) {
                    if (DEBUG && !isSessionActive(sessionStatus)) {
                        Log.v(TAG, "play() is called, but session is not active.");
                    }
                    // Do nothing since this returns the buffering state--
                    // StatusCallback#onItemStatusChanged is called when the session reaches the
                    // play state.
                    result.set(new PlayerResult(RESULT_CODE_SUCCESS, getCurrentMediaItem()));
                }
            });
        }
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> prepare() {
        return createResult();
    }
    @Override
    public ListenableFuture<PlayerResult> pause() {
        if (mClient.isSessionManagementSupported()) {
            final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
            mClient.pause(null, new SessionActionCallback() {
                @Override
                public void onResult(Bundle data,
                        String sessionId, MediaSessionStatus sessionStatus) {
                    if (DEBUG && !isSessionActive(sessionStatus)) {
                        Log.v(TAG, "pause() is called, but session is not active.");
                    }
                    mCanResume = true;
                    // Do not update playback state here since this returns the buffering state--
                    // StatusCallback#onItemStatusChanged is called when the session reaches the
                    // pause state.
                    result.set(new PlayerResult(RESULT_CODE_SUCCESS, getCurrentMediaItem()));
                }
            });
        }
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> seekTo(long pos) {
        if (mClient.isSessionManagementSupported()) {
            final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
            mClient.seek(mItemId, pos, null, new ItemActionCallback() {
                @Override
                public void onResult(Bundle data,
                        String sessionId, MediaSessionStatus sessionStatus,
                        String itemId, final MediaItemStatus itemStatus) {
                    if (DEBUG && !isSessionActive(sessionStatus)) {
                        Log.v(TAG, "seekTo(long) is called, but session is not active.");
                    }
                    if (itemStatus != null) {
                        List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
                        for (Pair<PlayerCallback, Executor> pair : callbacks) {
                            final PlayerCallback callback = pair.first;
                            pair.second.execute(new Runnable() {
                                @Override
                                public void run() {
                                    callback.onSeekCompleted(RoutePlayer.this,
                                            itemStatus.getContentPosition());
                                }
                            });
                        }
                    } else {
                        result.set(new PlayerResult(RESULT_CODE_UNKNOWN_ERROR,
                                getCurrentMediaItem()));
                    }
                }
            });
        }
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public long getCurrentPosition() {
        long expectedPosition = mPosition;
        if (mCurrentPlayerState == PLAYER_STATE_PLAYING) {
            expectedPosition = mPosition + (SystemClock.elapsedRealtime() - mLastStatusChangedTime);
        }
        return expectedPosition;
    }
    @Override
    public long getDuration() {
        return mDuration;
    }
    @Override
    public long getBufferedPosition() {
        return 0;
    }
    @Override
    public int getPlayerState() {
        return mCurrentPlayerState;
    }
    @Override
    public int getBufferingState() {
        return SessionPlayer.BUFFERING_STATE_UNKNOWN;
    }
    @Override
    public ListenableFuture<PlayerResult> setAudioAttributes(AudioAttributesCompat attributes) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public AudioAttributesCompat getAudioAttributes() {
        return null;
    }
    @Override
    public ListenableFuture<PlayerResult> setMediaItem(MediaItem item) {
        mItem = item;
        return createResult();
    }
    @Override
    public MediaItem getCurrentMediaItem() {
        return mItem;
    }
    @Override
    public int getCurrentMediaItemIndex() {
        return ITEM_NONE;
    }
    @Override
    public int getPreviousMediaItemIndex() {
        return ITEM_NONE;
    }
    @Override
    public int getNextMediaItemIndex() {
        return ITEM_NONE;
    }
    @Override
    public ListenableFuture<PlayerResult> setPlaybackSpeed(float speed) {
        // Do nothing
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public float getPlaybackSpeed() {
        return 1.0f;
    }
    @Override
    public int getVolume() {
        return mSelectedRoute.getVolume();
    }
    @Override
    public Future<PlayerResult> adjustVolume(int direction) {
        mSelectedRoute.requestUpdateVolume(direction);
        ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
        mPendingVolumeResult.add(result);
        return result;
    }
    @Override
    public Future<PlayerResult> setVolume(int volume) {
        mSelectedRoute.requestSetVolume(volume);
        ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
        mPendingVolumeResult.add(result);
        return result;
    }
    @Override
    public int getMaxVolume() {
        return mSelectedRoute.getVolumeMax();
    }
    @Override
    public int getVolumeControlType() {
        return mSelectedRoute.getVolumeHandling();
    }
    @Override
    public ListenableFuture<PlayerResult> setPlaylist(List<MediaItem> list,
            MediaMetadata metadata) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem item) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem item) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
        // TODO: implement
        return createResult(RESULT_CODE_INVALID_STATE);
    }
    @Override
    public List<MediaItem> getPlaylist() {
        List<MediaItem> list = new ArrayList<>();
        list.add(mItem);
        return list;
    }
    @Override
    public MediaMetadata getPlaylistMetadata() {
        return null;
    }
    @Override
    public int getRepeatMode() {
        return SessionPlayer.REPEAT_MODE_NONE;
    }
    @Override
    public int getShuffleMode() {
        return SessionPlayer.SHUFFLE_MODE_NONE;
    }
    @Override
    public void close() {
        if (mClient != null) {
            try {
                mClient.release();
            } catch (IllegalArgumentException e) {
                Log.d(TAG, "Receiver not registered");
            }
            mClient = null;
        }
        mMediaRouter.removeCallback(mRouterCallback);
    }
    void setCurrentPosition(long position) {
        mPosition = position;
    }
    boolean isSessionActive(MediaSessionStatus status) {
        if (status == null || status.getSessionState() == MediaSessionStatus.SESSION_STATE_ENDED
                || status.getSessionState() == MediaSessionStatus.SESSION_STATE_INVALIDATED) {
            return false;
        }
        return true;
    }
    int convertPlaybackStateToPlayerState(int playbackState) {
        int playerState = PLAYER_STATE_IDLE;
        switch (playbackState) {
            case MediaItemStatus.PLAYBACK_STATE_PENDING:
            case MediaItemStatus.PLAYBACK_STATE_FINISHED:
            case MediaItemStatus.PLAYBACK_STATE_CANCELED:
                playerState = PLAYER_STATE_IDLE;
                break;
            case MediaItemStatus.PLAYBACK_STATE_PLAYING:
                playerState = PLAYER_STATE_PLAYING;
                break;
            case MediaItemStatus.PLAYBACK_STATE_PAUSED:
            case MediaItemStatus.PLAYBACK_STATE_BUFFERING:
                playerState = PLAYER_STATE_PAUSED;
                break;
            case MediaItemStatus.PLAYBACK_STATE_INVALIDATED:
            case MediaItemStatus.PLAYBACK_STATE_ERROR:
                playerState =  PLAYER_STATE_ERROR;
                break;
        }
        return playerState;
    }
    private ListenableFuture<PlayerResult> playInternal() {
        if (!(mItem instanceof UriMediaItem)) {
            Log.w(TAG, "Data source type is not Uri." + mItem);
            return createResult(RESULT_CODE_BAD_VALUE);
        }
        final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
        mClient.play(((UriMediaItem) mItem).getUri(), "video/mp4", null, mPosition, null,
                new ItemActionCallback() {
                    @Override
                    public void onResult(Bundle data, String sessionId,
                            MediaSessionStatus sessionStatus,
                            String itemId, MediaItemStatus itemStatus) {
                        if (DEBUG && !isSessionActive(sessionStatus)) {
                            Log.v(TAG, "play() is called, but session is not active.");
                        }
                        mItemId = itemId;
                        if (itemStatus != null) {
                            mDuration = itemStatus.getContentDuration();
                        }
                        // Do not update playback state here since this returns the buffering state.
                        // StatusCallback#onItemStatusChanged is called when the session reaches the
                        // play state.
                        result.set(new PlayerResult(RESULT_CODE_SUCCESS, getCurrentMediaItem()));
                    }
                });
        return result;
    }
    private ListenableFuture<PlayerResult> createResult() {
        return createResult(RESULT_CODE_SUCCESS);
    }
    private ListenableFuture<PlayerResult> createResult(int code) {
        ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
        result.set(new PlayerResult(code, getCurrentMediaItem()));
        return result;
    }
}