public class

RoutePlayer

extends RemoteSessionPlayer

 java.lang.Object

androidx.media2.SessionPlayer

androidx.media2.RemoteSessionPlayer

↳androidx.media2.widget.RoutePlayer

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 RemoteSessionPlayerVOLUME_CONTROL_ABSOLUTE, VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE
from SessionPlayerBUFFERING_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
Constructors
publicRoutePlayer(Context context, MediaRouteSelector selector, MediaRouter.RouteInfo route)

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 voidclose()

public abstract AudioAttributesCompatgetAudioAttributes()

Gets the AudioAttributesCompat that media player has.

public abstract longgetBufferedPosition()

Gets the buffered position of current playback, or SessionPlayer.UNKNOWN_TIME if unknown.

public abstract intgetBufferingState()

Returns the current buffering state of the player.

public abstract MediaItemgetCurrentMediaItem()

Gets the current media item.

public abstract intgetCurrentMediaItemIndex()

Gets the index of current media item in playlist.

public abstract longgetCurrentPosition()

Gets the current playback head position.

public abstract longgetDuration()

Gets the duration of the current media item, or SessionPlayer.UNKNOWN_TIME if unknown.

public abstract intgetMaxVolume()

Gets the maximum volume that can be used in RemoteSessionPlayer.setVolume(int).

public abstract intgetNextMediaItemIndex()

Gets the next item index in the playlist.

public abstract floatgetPlaybackSpeed()

Gets the actual playback speed to be used by the player when playing.

public abstract intgetPlayerState()

Gets the current player state.

public abstract java.util.List<MediaItem>getPlaylist()

Gets the playlist.

public abstract MediaMetadatagetPlaylistMetadata()

Gets the playlist metadata.

public abstract intgetPreviousMediaItemIndex()

Gets the previous item index in the playlist.

public abstract intgetRepeatMode()

Gets the repeat mode.

public abstract intgetShuffleMode()

Gets the shuffle mode.

public abstract intgetVolume()

Gets the current volume of this player to this player.

public abstract intgetVolumeControlType()

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 SessionPlayergetCallbacks, registerPlayerCallback, unregisterPlayerCallback
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public RoutePlayer(Context context, MediaRouteSelector selector, MediaRouter.RouteInfo route)

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()

public abstract <any> setAudioAttributes(AudioAttributesCompat attributes)

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.

public abstract AudioAttributesCompat getAudioAttributes()

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)

public abstract MediaMetadata getPlaylistMetadata()

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)

public void close()

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;
    }
}