FileDocCategorySizeDatePackage
RemoteControlClient.javaAPI DocAndroid 5.1 API46830Thu Mar 12 22:22:30 GMT 2015android.media

RemoteControlClient

public class RemoteControlClient extends Object
RemoteControlClient enables exposing information meant to be consumed by remote controls capable of displaying metadata, artwork and media transport control buttons.

A remote control client object is associated with a media button event receiver. This event receiver must have been previously registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the RemoteControlClient can be registered through {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.

Here is an example of creating a RemoteControlClient instance after registering a media button event receiver:

ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
// build the PendingIntent for the remote control client
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(myEventReceiver);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
// create and register the remote control client
RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
myAudioManager.registerRemoteControlClient(myRemoteControlClient);
deprecated
Use {@link MediaSession} instead.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
public static final int
PLAYSTATE_STOPPED
Playback state of a RemoteControlClient which is stopped.
public static final int
PLAYSTATE_PAUSED
Playback state of a RemoteControlClient which is paused.
public static final int
PLAYSTATE_PLAYING
Playback state of a RemoteControlClient which is playing media.
public static final int
PLAYSTATE_FAST_FORWARDING
Playback state of a RemoteControlClient which is fast forwarding in the media it is currently playing.
public static final int
PLAYSTATE_REWINDING
Playback state of a RemoteControlClient which is fast rewinding in the media it is currently playing.
public static final int
PLAYSTATE_SKIPPING_FORWARDS
Playback state of a RemoteControlClient which is skipping to the next logical chapter (such as a song in a playlist) in the media it is currently playing.
public static final int
PLAYSTATE_SKIPPING_BACKWARDS
Playback state of a RemoteControlClient which is skipping back to the previous logical chapter (such as a song in a playlist) in the media it is currently playing.
public static final int
PLAYSTATE_BUFFERING
Playback state of a RemoteControlClient which is buffering data to play before it can start or resume playback.
public static final int
PLAYSTATE_ERROR
Playback state of a RemoteControlClient which cannot perform any playback related operation because of an internal error. Examples of such situations are no network connectivity when attempting to stream data from a server, or expired user credentials when trying to play subscription-based content.
public static final int
PLAYSTATE_NONE
public static final int
PLAYBACK_TYPE_LOCAL
public static final int
PLAYBACK_TYPE_REMOTE
private static final int
PLAYBACK_TYPE_MIN
private static final int
PLAYBACK_TYPE_MAX
public static final int
PLAYBACK_VOLUME_FIXED
public static final int
PLAYBACK_VOLUME_VARIABLE
public static final int
PLAYBACKINFO_INVALID_VALUE
public static final long
PLAYBACK_POSITION_INVALID
public static final long
PLAYBACK_POSITION_ALWAYS_UNKNOWN
public static final float
PLAYBACK_SPEED_1X
public static final int
PLAYBACKINFO_PLAYBACK_TYPE
public static final int
PLAYBACKINFO_VOLUME
public static final int
PLAYBACKINFO_VOLUME_MAX
public static final int
PLAYBACKINFO_VOLUME_HANDLING
public static final int
PLAYBACKINFO_USES_STREAM
public static final int
FLAG_KEY_MEDIA_PREVIOUS
Flag indicating a RemoteControlClient makes use of the "previous" media key.
public static final int
FLAG_KEY_MEDIA_REWIND
Flag indicating a RemoteControlClient makes use of the "rewind" media key.
public static final int
FLAG_KEY_MEDIA_PLAY
Flag indicating a RemoteControlClient makes use of the "play" media key.
public static final int
FLAG_KEY_MEDIA_PLAY_PAUSE
Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
public static final int
FLAG_KEY_MEDIA_PAUSE
Flag indicating a RemoteControlClient makes use of the "pause" media key.
public static final int
FLAG_KEY_MEDIA_STOP
Flag indicating a RemoteControlClient makes use of the "stop" media key.
public static final int
FLAG_KEY_MEDIA_FAST_FORWARD
Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
public static final int
FLAG_KEY_MEDIA_NEXT
Flag indicating a RemoteControlClient makes use of the "next" media key.
public static final int
FLAG_KEY_MEDIA_POSITION_UPDATE
Flag indicating a RemoteControlClient can receive changes in the media playback position through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set in order for components that display the RemoteControlClient information, to display and let the user control media playback position.
public static final int
FLAG_KEY_MEDIA_RATING
Flag indicating a RemoteControlClient supports ratings. This flag must be set in order for components that display the RemoteControlClient information, to display ratings information, and, if ratings are declared editable (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate the media, with values being received through the interface set with {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}.
public static final int
FLAGS_KEY_MEDIA_NONE
public static final int
FLAG_INFORMATION_REQUEST_METADATA
public static final int
FLAG_INFORMATION_REQUEST_KEY_MEDIA
public static final int
FLAG_INFORMATION_REQUEST_PLAYSTATE
public static final int
FLAG_INFORMATION_REQUEST_ALBUM_ART
private android.media.session.MediaSession
mSession
public static int
MEDIA_POSITION_READABLE
public static int
MEDIA_POSITION_WRITABLE
public static final int
DEFAULT_PLAYBACK_VOLUME_HANDLING
public static final int
DEFAULT_PLAYBACK_VOLUME
private final Object
mCacheLock
Lock for all cached data
private int
mPlaybackState
Cache for the playback state. Access synchronized on mCacheLock
private long
mPlaybackStateChangeTimeMs
Time of last play state change Access synchronized on mCacheLock
private long
mPlaybackPositionMs
Last playback position in ms reported by the user
private float
mPlaybackSpeed
Last playback speed reported by the user
private android.graphics.Bitmap
mOriginalArtwork
Cache for the artwork bitmap. Access synchronized on mCacheLock Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be accessed to be resized, in which case a copy will be made. This would add overhead in Bundle operations.
private int
mTransportControlFlags
Cache for the transport control mask. Access synchronized on mCacheLock
private android.os.Bundle
mMetadata
Cache for the metadata strings. Access synchronized on mCacheLock This is re-initialized in apply() and so cannot be final.
private OnPlaybackPositionUpdateListener
mPositionUpdateListener
Listener registered by user of RemoteControlClient to receive requests for playback position update requests.
private OnGetPlaybackPositionListener
mPositionProvider
Provider registered by user of RemoteControlClient to provide the current playback position.
private OnMetadataUpdateListener
mMetadataUpdateListener
Listener registered by user of RemoteControlClient to receive edit changes to metadata it exposes.
private int
mCurrentClientGenId
The current remote control client generation ID across the system, as known by this object
private final android.app.PendingIntent
mRcMediaIntent
The media button intent description associated with this remote control client (can / should include target component for intent handling, used when persisting media button event receiver across reboots).
private boolean
mNeedsPositionSync
Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
private android.media.session.PlaybackState
mSessionPlaybackState
Cache for the current playback state using Session APIs.
private MediaMetadata
mMediaMetadata
Cache for metadata using Session APIs. This is re-initialized in apply().
public static final int
RCSE_ID_UNREGISTERED
private MediaSession.Callback
mTransportListener
private EventHandler
mEventHandler
private static final int
MSG_POSITION_DRIFT_CHECK
private static final long
POSITION_REFRESH_PERIOD_PLAYING_MS
Period for playback position drift checks, 15s when playing at 1x or slower.
private static final long
POSITION_REFRESH_PERIOD_MIN_MS
Minimum period for playback position drift checks, never more often when every 2s, when fast forwarding or rewinding.
private static final long
POSITION_DRIFT_MAX_MS
The value above which the difference between client-reported playback position and estimated position is considered a drift.
Constructors Summary
public RemoteControlClient(android.app.PendingIntent mediaButtonIntent)
Class constructor.

param
mediaButtonIntent The intent that will be sent for the media button events sent by remote controls. This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} action, and have a component that will handle the intent (set with {@link Intent#setComponent(ComponentName)}) registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before this new RemoteControlClient can itself be registered with {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
see
AudioManager#registerMediaButtonEventReceiver(ComponentName)
see
AudioManager#registerRemoteControlClient(RemoteControlClient)


                                                                                                    
       
        mRcMediaIntent = mediaButtonIntent;

        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
        }
    
public RemoteControlClient(android.app.PendingIntent mediaButtonIntent, android.os.Looper looper)
Class constructor for a remote control client whose internal event handling happens on a user-provided Looper.

param
mediaButtonIntent The intent that will be sent for the media button events sent by remote controls. This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} action, and have a component that will handle the intent (set with {@link Intent#setComponent(ComponentName)}) registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before this new RemoteControlClient can itself be registered with {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
param
looper The Looper running the event loop.
see
AudioManager#registerMediaButtonEventReceiver(ComponentName)
see
AudioManager#registerRemoteControlClient(RemoteControlClient)

        mRcMediaIntent = mediaButtonIntent;

        mEventHandler = new EventHandler(this, looper);
    
Methods Summary
public android.media.RemoteControlClient$MetadataEditoreditMetadata(boolean startEmpty)
Creates a {@link MetadataEditor}.

param
startEmpty Set to false if you want the MetadataEditor to contain the metadata that was previously applied to the RemoteControlClient, or true if it is to be created empty.
return
a new MetadataEditor instance.

        MetadataEditor editor = new MetadataEditor();
        if (startEmpty) {
            editor.mEditorMetadata = new Bundle();
            editor.mEditorArtwork = null;
            editor.mMetadataChanged = true;
            editor.mArtworkChanged = true;
            editor.mEditableKeys = 0;
        } else {
            editor.mEditorMetadata = new Bundle(mMetadata);
            editor.mEditorArtwork = mOriginalArtwork;
            editor.mMetadataChanged = false;
            editor.mArtworkChanged = false;
        }
        // USE_SESSIONS
        if (startEmpty || mMediaMetadata == null) {
            editor.mMetadataBuilder = new MediaMetadata.Builder();
        } else {
            editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata);
        }
        return editor;
    
private static longgetCheckPeriodFromSpeed(float speed)
Compute the period at which the estimated playback position should be compared against the actual playback position. Is a funciton of playback speed.

param
speed 1.0f is normal playback speed
return
the period in ms

                                            
         
        if (Math.abs(speed) <= 1.0f) {
            return POSITION_REFRESH_PERIOD_PLAYING_MS;
        } else {
            return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)),
                    POSITION_REFRESH_PERIOD_MIN_MS);
        }
    
public android.media.session.MediaSessiongetMediaSession()
Get a {@link MediaSession} associated with this RCC. It will only have a session while it is registered with {@link AudioManager#registerRemoteControlClient}. The session returned should not be modified directly by the application but may be used with other APIs that require a session.

return
A media session object or null.

        return mSession;
    
public android.app.PendingIntentgetRcMediaIntent()

hide
Accessor to media button intent description (includes target component)


                   
       
        return mRcMediaIntent;
    
private voidonPositionDriftCheck()

        if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
        synchronized(mCacheLock) {
            if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
                return;
            }
            if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
                if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
                return;
            }
            long estPos = mPlaybackPositionMs + (long)
                    ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
            long actPos = mPositionProvider.onGetPlaybackPosition();
            if (actPos >= 0) {
                if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
                    // drift happened, report the new position
                    if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
                    setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
                } else {
                    if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
                    // no drift, schedule the next drift check
                    mEventHandler.sendMessageDelayed(
                            mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
                            getCheckPeriodFromSpeed(mPlaybackSpeed));
                }
            } else {
                // invalid position (negative value), can't check for drift
                mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
            }
        }
    
private voidonSeekTo(int generationId, long timeMs)

        synchronized (mCacheLock) {
            if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
                mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
            }
        }
    
private voidonUpdateMetadata(int generationId, int key, java.lang.Object value)

        synchronized (mCacheLock) {
            if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
                mMetadataUpdateListener.onMetadataUpdate(key, value);
            }
        }
    
static booleanplaybackPositionShouldMove(int playstate)
Returns whether, for the given playback state, the playback position is expected to be changing.

param
playstate the playback state to evaluate
return
true during any form of playback, false if it's not playing anything while in this playback state

        switch(playstate) {
            case PLAYSTATE_STOPPED:
            case PLAYSTATE_PAUSED:
            case PLAYSTATE_BUFFERING:
            case PLAYSTATE_ERROR:
            case PLAYSTATE_SKIPPING_FORWARDS:
            case PLAYSTATE_SKIPPING_BACKWARDS:
                return false;
            case PLAYSTATE_PLAYING:
            case PLAYSTATE_FAST_FORWARDING:
            case PLAYSTATE_REWINDING:
            default:
                return true;
        }
    
public voidregisterWithSession(android.media.session.MediaSessionLegacyHelper helper)

hide

        helper.addRccListener(mRcMediaIntent, mTransportListener);
        mSession = helper.getSession(mRcMediaIntent);
        setTransportControlFlags(mTransportControlFlags);
    
public voidsetMetadataUpdateListener(android.media.RemoteControlClient$OnMetadataUpdateListener l)
Sets the listener to be called whenever the metadata is updated. New metadata values will be received in the same thread as the one in which RemoteControlClient was created.

param
l the metadata update listener

        synchronized(mCacheLock) {
            mMetadataUpdateListener = l;
        }
    
public voidsetOnGetPlaybackPositionListener(android.media.RemoteControlClient$OnGetPlaybackPositionListener l)
Sets the listener to be called whenever the media current playback position is needed. Queries will be received in the same thread as the one in which RemoteControlClient was created.

param
l the listener to be called to retrieve the playback position

        synchronized(mCacheLock) {
            mPositionProvider = l;
            if ((mPositionProvider != null) && (mEventHandler != null)
                    && playbackPositionShouldMove(mPlaybackState)) {
                // playback position is already moving, but now we have a position provider,
                // so schedule a drift check right now
                mEventHandler.sendMessageDelayed(
                        mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
                        0 /*check now*/);
            }
        }
    
public voidsetPlaybackPositionUpdateListener(android.media.RemoteControlClient$OnPlaybackPositionUpdateListener l)
Sets the listener to be called whenever the media playback position is requested to be updated. Notifications will be received in the same thread as the one in which RemoteControlClient was created.

param
l the position update listener to be called

        synchronized(mCacheLock) {
            mPositionUpdateListener = l;
        }
    
public voidsetPlaybackState(int state)
Sets the current playback state.

param
state The current playback state, one of the following values: {@link #PLAYSTATE_STOPPED}, {@link #PLAYSTATE_PAUSED}, {@link #PLAYSTATE_PLAYING}, {@link #PLAYSTATE_FAST_FORWARDING}, {@link #PLAYSTATE_REWINDING}, {@link #PLAYSTATE_SKIPPING_FORWARDS}, {@link #PLAYSTATE_SKIPPING_BACKWARDS}, {@link #PLAYSTATE_BUFFERING}, {@link #PLAYSTATE_ERROR}.

        setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
                false /* legacy API, converting to method with position and speed */);
    
public voidsetPlaybackState(int state, long timeInMs, float playbackSpeed)
Sets the current playback state and the matching media position for the current playback speed.

param
state The current playback state, one of the following values: {@link #PLAYSTATE_STOPPED}, {@link #PLAYSTATE_PAUSED}, {@link #PLAYSTATE_PLAYING}, {@link #PLAYSTATE_FAST_FORWARDING}, {@link #PLAYSTATE_REWINDING}, {@link #PLAYSTATE_SKIPPING_FORWARDS}, {@link #PLAYSTATE_SKIPPING_BACKWARDS}, {@link #PLAYSTATE_BUFFERING}, {@link #PLAYSTATE_ERROR}.
param
timeInMs a 0 or positive value for the current media position expressed in ms (same unit as for when sending the media duration, if applicable, with {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
param
playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is playing (e.g. when state is {@link #PLAYSTATE_ERROR}).

        setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
    
private voidsetPlaybackStateInt(int state, long timeInMs, float playbackSpeed, boolean hasPosition)

        synchronized(mCacheLock) {
            if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
                    || (mPlaybackSpeed != playbackSpeed)) {
                // store locally
                mPlaybackState = state;
                // distinguish between an application not knowing the current playback position
                // at the moment and an application using the API where only the playback state
                // is passed, not the playback position.
                if (hasPosition) {
                    if (timeInMs < 0) {
                        mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
                    } else {
                        mPlaybackPositionMs = timeInMs;
                    }
                } else {
                    mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN;
                }
                mPlaybackSpeed = playbackSpeed;
                // keep track of when the state change occurred
                mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();

                // USE_SESSIONS
                if (mSession != null) {
                    int pbState = PlaybackState.getStateFromRccState(state);
                    long position = hasPosition ? mPlaybackPositionMs
                            : PlaybackState.PLAYBACK_POSITION_UNKNOWN;

                    PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
                    bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime());
                    bob.setErrorMessage(null);
                    mSessionPlaybackState = bob.build();
                    mSession.setPlaybackState(mSessionPlaybackState);
                }
            }
        }
    
public voidsetTransportControlFlags(int transportControlFlags)
Sets the flags for the media transport control buttons that this client supports.

param
transportControlFlags A combination of the following flags: {@link #FLAG_KEY_MEDIA_PREVIOUS}, {@link #FLAG_KEY_MEDIA_REWIND}, {@link #FLAG_KEY_MEDIA_PLAY}, {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, {@link #FLAG_KEY_MEDIA_PAUSE}, {@link #FLAG_KEY_MEDIA_STOP}, {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, {@link #FLAG_KEY_MEDIA_NEXT}, {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, {@link #FLAG_KEY_MEDIA_RATING}.

        synchronized(mCacheLock) {
            // store locally
            mTransportControlFlags = transportControlFlags;

            // USE_SESSIONS
            if (mSession != null) {
                PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
                bob.setActions(
                        PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
                mSessionPlaybackState = bob.build();
                mSession.setPlaybackState(mSessionPlaybackState);
            }
        }
    
public voidunregisterWithSession(android.media.session.MediaSessionLegacyHelper helper)

hide

        helper.removeRccListener(mRcMediaIntent);
        mSession = null;