RemoteControlClientpublic 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); |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | public static final int | PLAYSTATE_STOPPEDPlayback state of a RemoteControlClient which is stopped. | public static final int | PLAYSTATE_PAUSEDPlayback state of a RemoteControlClient which is paused. | public static final int | PLAYSTATE_PLAYINGPlayback state of a RemoteControlClient which is playing media. | public static final int | PLAYSTATE_FAST_FORWARDINGPlayback state of a RemoteControlClient which is fast forwarding in the media
it is currently playing. | public static final int | PLAYSTATE_REWINDINGPlayback state of a RemoteControlClient which is fast rewinding in the media
it is currently playing. | public static final int | PLAYSTATE_SKIPPING_FORWARDSPlayback 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_BACKWARDSPlayback 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_BUFFERINGPlayback state of a RemoteControlClient which is buffering data to play before it can
start or resume playback. | public static final int | PLAYSTATE_ERRORPlayback 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_PREVIOUSFlag indicating a RemoteControlClient makes use of the "previous" media key. | public static final int | FLAG_KEY_MEDIA_REWINDFlag indicating a RemoteControlClient makes use of the "rewind" media key. | public static final int | FLAG_KEY_MEDIA_PLAYFlag indicating a RemoteControlClient makes use of the "play" media key. | public static final int | FLAG_KEY_MEDIA_PLAY_PAUSEFlag indicating a RemoteControlClient makes use of the "play/pause" media key. | public static final int | FLAG_KEY_MEDIA_PAUSEFlag indicating a RemoteControlClient makes use of the "pause" media key. | public static final int | FLAG_KEY_MEDIA_STOPFlag indicating a RemoteControlClient makes use of the "stop" media key. | public static final int | FLAG_KEY_MEDIA_FAST_FORWARDFlag indicating a RemoteControlClient makes use of the "fast forward" media key. | public static final int | FLAG_KEY_MEDIA_NEXTFlag indicating a RemoteControlClient makes use of the "next" media key. | public static final int | FLAG_KEY_MEDIA_POSITION_UPDATEFlag 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_RATINGFlag 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 | mCacheLockLock for all cached data | private int | mPlaybackStateCache for the playback state.
Access synchronized on mCacheLock | private long | mPlaybackStateChangeTimeMsTime of last play state change
Access synchronized on mCacheLock | private long | mPlaybackPositionMsLast playback position in ms reported by the user | private float | mPlaybackSpeedLast playback speed reported by the user | private android.graphics.Bitmap | mOriginalArtworkCache 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 | mTransportControlFlagsCache for the transport control mask.
Access synchronized on mCacheLock | private android.os.Bundle | mMetadataCache for the metadata strings.
Access synchronized on mCacheLock
This is re-initialized in apply() and so cannot be final. | private OnPlaybackPositionUpdateListener | mPositionUpdateListenerListener registered by user of RemoteControlClient to receive requests for playback position
update requests. | private OnGetPlaybackPositionListener | mPositionProviderProvider registered by user of RemoteControlClient to provide the current playback position. | private OnMetadataUpdateListener | mMetadataUpdateListenerListener registered by user of RemoteControlClient to receive edit changes to metadata
it exposes. | private int | mCurrentClientGenIdThe current remote control client generation ID across the system, as known by this object | private final android.app.PendingIntent | mRcMediaIntentThe 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 | mNeedsPositionSyncReflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. | private android.media.session.PlaybackState | mSessionPlaybackStateCache for the current playback state using Session APIs. | private MediaMetadata | mMediaMetadataCache 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_MSPeriod for playback position drift checks, 15s when playing at 1x or slower. | private static final long | POSITION_REFRESH_PERIOD_MIN_MSMinimum period for playback position drift checks, never more often when every 2s, when
fast forwarding or rewinding. | private static final long | POSITION_DRIFT_MAX_MSThe 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.
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.
mRcMediaIntent = mediaButtonIntent;
mEventHandler = new EventHandler(this, looper);
|
Methods Summary |
---|
public android.media.RemoteControlClient$MetadataEditor | editMetadata(boolean startEmpty)Creates a {@link MetadataEditor}.
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 long | getCheckPeriodFromSpeed(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.
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.MediaSession | getMediaSession()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 mSession;
| public android.app.PendingIntent | getRcMediaIntent()
return mRcMediaIntent;
| private void | onPositionDriftCheck()
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 void | onSeekTo(int generationId, long timeMs)
synchronized (mCacheLock) {
if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
}
}
| private void | onUpdateMetadata(int generationId, int key, java.lang.Object value)
synchronized (mCacheLock) {
if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
mMetadataUpdateListener.onMetadataUpdate(key, value);
}
}
| static boolean | playbackPositionShouldMove(int playstate)Returns whether, for the given playback state, the playback position is expected to
be changing.
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 void | registerWithSession(android.media.session.MediaSessionLegacyHelper helper)
helper.addRccListener(mRcMediaIntent, mTransportListener);
mSession = helper.getSession(mRcMediaIntent);
setTransportControlFlags(mTransportControlFlags);
| public void | setMetadataUpdateListener(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.
synchronized(mCacheLock) {
mMetadataUpdateListener = l;
}
| public void | setOnGetPlaybackPositionListener(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.
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 void | setPlaybackPositionUpdateListener(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.
synchronized(mCacheLock) {
mPositionUpdateListener = l;
}
| public void | setPlaybackState(int state)Sets the current playback state.
setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
false /* legacy API, converting to method with position and speed */);
| public void | setPlaybackState(int state, long timeInMs, float playbackSpeed)Sets the current playback state and the matching media position for the current playback
speed.
setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
| private void | setPlaybackStateInt(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 void | setTransportControlFlags(int transportControlFlags)Sets the flags for the media transport control buttons that this client supports.
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 void | unregisterWithSession(android.media.session.MediaSessionLegacyHelper helper)
helper.removeRccListener(mRcMediaIntent);
mSession = null;
|
|