am 668885a4: Merge "Add Session API calls to RCC and AudioManager"
* commit '668885a41af63ed7a889e7fb2fa939f5ce3150c6': Add Session API calls to RCC and AudioManager
This commit is contained in:
@@ -15103,6 +15103,7 @@ package android.media.routeprovider {
|
||||
package android.media.session {
|
||||
|
||||
public final class MediaMetadata implements android.os.Parcelable {
|
||||
method public boolean containsKey(java.lang.String);
|
||||
method public int describeContents();
|
||||
method public android.graphics.Bitmap getBitmap(java.lang.String);
|
||||
method public long getLong(java.lang.String);
|
||||
@@ -15118,6 +15119,7 @@ package android.media.session {
|
||||
field public static final java.lang.String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
|
||||
field public static final java.lang.String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
|
||||
field public static final java.lang.String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
|
||||
field public static final java.lang.String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
|
||||
field public static final java.lang.String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
|
||||
field public static final java.lang.String METADATA_KEY_DATE = "android.media.metadata.DATE";
|
||||
field public static final java.lang.String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
|
||||
@@ -15150,25 +15152,25 @@ package android.media.session {
|
||||
method public long getBufferPosition();
|
||||
method public java.lang.String getErrorMessage();
|
||||
method public long getPosition();
|
||||
method public float getSpeed();
|
||||
method public float getRate();
|
||||
method public int getState();
|
||||
method public void setActions(long);
|
||||
method public void setBufferPosition(long);
|
||||
method public void setErrorMessage(java.lang.String);
|
||||
method public void setPosition(long);
|
||||
method public void setSpeed(float);
|
||||
method public void setState(int);
|
||||
method public void setState(int, long, float);
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final long ACTION_FASTFORWARD = 64L; // 0x40L
|
||||
field public static final long ACTION_NEXT_ITEM = 32L; // 0x20L
|
||||
field public static final long ACTION_PAUSE = 2L; // 0x2L
|
||||
field public static final long ACTION_PLAY = 4L; // 0x4L
|
||||
field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
|
||||
field public static final long ACTION_PREVIOUS_ITEM = 16L; // 0x10L
|
||||
field public static final long ACTION_RATING = 128L; // 0x80L
|
||||
field public static final long ACTION_REWIND = 8L; // 0x8L
|
||||
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
|
||||
field public static final long ACTION_STOP = 1L; // 0x1L
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
|
||||
field public static final int PLAYSTATE_BUFFERING = 6; // 0x6
|
||||
field public static final int PLAYSTATE_CONNECTING = 8; // 0x8
|
||||
field public static final int PLAYSTATE_ERROR = 7; // 0x7
|
||||
@@ -15177,6 +15179,8 @@ package android.media.session {
|
||||
field public static final int PLAYSTATE_PAUSED = 2; // 0x2
|
||||
field public static final int PLAYSTATE_PLAYING = 3; // 0x3
|
||||
field public static final int PLAYSTATE_REWINDING = 5; // 0x5
|
||||
field public static final int PLAYSTATE_SKIPPING_BACKWARDS = 9; // 0x9
|
||||
field public static final int PLAYSTATE_SKIPPING_FORWARDS = 10; // 0xa
|
||||
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.RemoteController.OnClientUpdateListener;
|
||||
import android.media.session.MediaSessionLegacyHelper;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -48,6 +49,12 @@ import java.util.HashMap;
|
||||
*/
|
||||
public class AudioManager {
|
||||
|
||||
// If we should use the new sessions APIs.
|
||||
private final static boolean USE_SESSIONS = true;
|
||||
// If we should use the legacy APIs. If both are true information will be
|
||||
// duplicated through both paths. Currently this flag isn't used.
|
||||
private final static boolean USE_LEGACY = true;
|
||||
|
||||
private final Context mContext;
|
||||
private long mVolumeKeyUpTime;
|
||||
private final boolean mUseMasterVolume;
|
||||
@@ -421,6 +428,7 @@ public class AudioManager {
|
||||
public static final int USE_DEFAULT_STREAM_TYPE = Integer.MIN_VALUE;
|
||||
|
||||
private static IAudioService sService;
|
||||
private MediaSessionLegacyHelper mSessionHelper;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
@@ -431,6 +439,9 @@ public class AudioManager {
|
||||
com.android.internal.R.bool.config_useMasterVolume);
|
||||
mUseVolumeKeySounds = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_useVolumeKeySounds);
|
||||
if (USE_SESSIONS) {
|
||||
mSessionHelper = MediaSessionLegacyHelper.getHelper(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static IAudioService getService()
|
||||
@@ -2166,6 +2177,9 @@ public class AudioManager {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
|
||||
}
|
||||
if (USE_SESSIONS) {
|
||||
mSessionHelper.addMediaButtonListener(pi, mContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2239,6 +2253,9 @@ public class AudioManager {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
|
||||
}
|
||||
if (USE_SESSIONS) {
|
||||
mSessionHelper.removeMediaButtonListener(pi);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2263,6 +2280,9 @@ public class AudioManager {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
|
||||
}
|
||||
if (USE_SESSIONS) {
|
||||
rcClient.registerWithSession(mSessionHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2282,6 +2302,9 @@ public class AudioManager {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
|
||||
}
|
||||
if (USE_SESSIONS) {
|
||||
rcClient.unregisterWithSession(mSessionHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.media;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
@@ -106,6 +107,10 @@ public abstract class MediaMetadataEditor {
|
||||
*/
|
||||
protected Bundle mEditorMetadata;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected MediaMetadata.Builder mMetadataBuilder;
|
||||
|
||||
/**
|
||||
* Clears all the pending metadata changes set since the MediaMetadataEditor instance was
|
||||
@@ -120,6 +125,7 @@ public abstract class MediaMetadataEditor {
|
||||
}
|
||||
mEditorMetadata.clear();
|
||||
mEditorArtwork = null;
|
||||
mMetadataBuilder = new MediaMetadata.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,11 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.media.session.MediaMetadata;
|
||||
import android.media.session.MediaSessionLegacyHelper;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.media.session.Session;
|
||||
import android.media.session.TransportPerformer;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -336,6 +341,8 @@ public class RemoteControlClient
|
||||
*/
|
||||
public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
|
||||
|
||||
private Session mSession;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
* @param mediaButtonIntent The intent that will be sent for the media button events sent
|
||||
@@ -384,6 +391,22 @@ public class RemoteControlClient
|
||||
mEventHandler = new EventHandler(this, looper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void registerWithSession(MediaSessionLegacyHelper helper) {
|
||||
helper.addRccListener(mRcMediaIntent, mTransportListener);
|
||||
mSession = helper.getSession(mRcMediaIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void unregisterWithSession(MediaSessionLegacyHelper helper) {
|
||||
helper.removeRccListener(mRcMediaIntent);
|
||||
mSession = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to modify metadata in a {@link RemoteControlClient} object.
|
||||
* Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
|
||||
@@ -438,6 +461,15 @@ public class RemoteControlClient
|
||||
public synchronized MetadataEditor putString(int key, String value)
|
||||
throws IllegalArgumentException {
|
||||
super.putString(key, value);
|
||||
if (mMetadataBuilder != null) {
|
||||
// MediaMetadata supports all the same fields as MetadataEditor
|
||||
String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
|
||||
// But just in case, don't add things we don't understand
|
||||
if (metadataKey != null) {
|
||||
mMetadataBuilder.putString(metadataKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -459,6 +491,14 @@ public class RemoteControlClient
|
||||
public synchronized MetadataEditor putLong(int key, long value)
|
||||
throws IllegalArgumentException {
|
||||
super.putLong(key, value);
|
||||
if (mMetadataBuilder != null) {
|
||||
// MediaMetadata supports all the same fields as MetadataEditor
|
||||
String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
|
||||
// But just in case, don't add things we don't understand
|
||||
if (metadataKey != null) {
|
||||
mMetadataBuilder.putLong(metadataKey, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -476,6 +516,14 @@ public class RemoteControlClient
|
||||
public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
|
||||
throws IllegalArgumentException {
|
||||
super.putBitmap(key, bitmap);
|
||||
if (mMetadataBuilder != null) {
|
||||
// MediaMetadata supports all the same fields as MetadataEditor
|
||||
String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
|
||||
// But just in case, don't add things we don't understand
|
||||
if (metadataKey != null) {
|
||||
mMetadataBuilder.putBitmap(metadataKey, bitmap);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -501,7 +549,7 @@ public class RemoteControlClient
|
||||
Log.e(TAG, "Can't apply a previously applied MetadataEditor");
|
||||
return;
|
||||
}
|
||||
synchronized(mCacheLock) {
|
||||
synchronized (mCacheLock) {
|
||||
// assign the edited data
|
||||
mMetadata = new Bundle(mEditorMetadata);
|
||||
// add the information about editable keys
|
||||
@@ -521,6 +569,11 @@ public class RemoteControlClient
|
||||
// send to remote control display if conditions are met
|
||||
sendArtwork_syncCacheLock(null, 0, 0);
|
||||
}
|
||||
|
||||
// USE_SESSIONS
|
||||
if (mSession != null && mMetadataBuilder != null) {
|
||||
mSession.getTransportPerformer().setMetadata(mMetadataBuilder.build());
|
||||
}
|
||||
mApplied = true;
|
||||
}
|
||||
}
|
||||
@@ -546,6 +599,12 @@ public class RemoteControlClient
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -624,6 +683,15 @@ public class RemoteControlClient
|
||||
|
||||
// handle automatic playback position refreshes
|
||||
initiateCheckForDrift_syncCacheLock();
|
||||
|
||||
// USE_SESSIONS
|
||||
if (mSession != null) {
|
||||
int pbState = PlaybackState.getStateFromRccState(state);
|
||||
mSessionPlaybackState.setState(pbState, hasPosition ?
|
||||
mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN,
|
||||
playbackSpeed);
|
||||
mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -704,6 +772,13 @@ public class RemoteControlClient
|
||||
|
||||
// send to remote control display if conditions are met
|
||||
sendTransportControlInfo_syncCacheLock(null);
|
||||
|
||||
// USE_SESSIONS
|
||||
if (mSession != null) {
|
||||
mSessionPlaybackState.setActions(PlaybackState
|
||||
.getActionsFromRccControlFlags(transportControlFlags));
|
||||
mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,6 +1112,16 @@ public class RemoteControlClient
|
||||
// TODO consider using a ref count for IRemoteControlDisplay requiring sync instead
|
||||
private boolean mNeedsPositionSync = false;
|
||||
|
||||
/**
|
||||
* Cache for the current playback state using Session APIs.
|
||||
*/
|
||||
private final PlaybackState mSessionPlaybackState = new PlaybackState();
|
||||
|
||||
/**
|
||||
* Cache for metadata using Session APIs. This is re-initialized in apply().
|
||||
*/
|
||||
private MediaMetadata mMediaMetadata;
|
||||
|
||||
/**
|
||||
* A class to encapsulate all the information about a remote control display.
|
||||
* A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
|
||||
@@ -1219,6 +1304,26 @@ public class RemoteControlClient
|
||||
return mRcseId;
|
||||
}
|
||||
|
||||
// USE_SESSIONS
|
||||
private TransportPerformer.Listener mTransportListener = new TransportPerformer.Listener() {
|
||||
|
||||
@Override
|
||||
public void onSeekTo(long pos) {
|
||||
RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRate(Rating rating) {
|
||||
if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) {
|
||||
if (mEventHandler != null) {
|
||||
mEventHandler.sendMessage(mEventHandler.obtainMessage(
|
||||
MSG_UPDATE_METADATA, mCurrentClientGenId,
|
||||
MetadataEditor.RATING_KEY_BY_USER, rating));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private EventHandler mEventHandler;
|
||||
private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
|
||||
private final static int MSG_REQUEST_METADATA = 2;
|
||||
@@ -1325,7 +1430,7 @@ public class RemoteControlClient
|
||||
// target == null implies all displays must be updated
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mEnabled) {
|
||||
try {
|
||||
di.mRcDisplay.setPlaybackState(mInternalClientGenId,
|
||||
@@ -1353,7 +1458,7 @@ public class RemoteControlClient
|
||||
// target == null implies all displays must be updated
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mEnabled) {
|
||||
try {
|
||||
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
|
||||
@@ -1381,7 +1486,7 @@ public class RemoteControlClient
|
||||
// target == null implies all displays must be updated
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mEnabled) {
|
||||
try {
|
||||
di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
|
||||
@@ -1407,7 +1512,7 @@ public class RemoteControlClient
|
||||
// target == null implies all displays must be updated
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
|
||||
if (!sendArtworkToDisplay(displayIterator.next())) {
|
||||
displayIterator.remove();
|
||||
}
|
||||
}
|
||||
@@ -1453,7 +1558,7 @@ public class RemoteControlClient
|
||||
// target == null implies all displays must be updated
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
try {
|
||||
if (di.mEnabled) {
|
||||
if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
|
||||
@@ -1537,7 +1642,7 @@ public class RemoteControlClient
|
||||
boolean displayKnown = false;
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext() && !displayKnown) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
|
||||
if (displayKnown) {
|
||||
// this display was known but the change in artwork size will cause the
|
||||
@@ -1562,7 +1667,7 @@ public class RemoteControlClient
|
||||
synchronized(mCacheLock) {
|
||||
Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||
displayIterator.remove();
|
||||
break;
|
||||
@@ -1573,7 +1678,7 @@ public class RemoteControlClient
|
||||
boolean newNeedsPositionSync = false;
|
||||
displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mWantsPositionSync) {
|
||||
newNeedsPositionSync = true;
|
||||
break;
|
||||
@@ -1592,7 +1697,7 @@ public class RemoteControlClient
|
||||
synchronized(mCacheLock) {
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
|
||||
((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
|
||||
di.mArtworkExpectedWidth = w;
|
||||
@@ -1617,7 +1722,7 @@ public class RemoteControlClient
|
||||
// go through the list of RCDs and for each entry, check both whether this is the RCD
|
||||
// that gets upated, and whether the list has one entry that wants position sync
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mEnabled) {
|
||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||
di.mWantsPositionSync = wantsSync;
|
||||
@@ -1640,7 +1745,7 @@ public class RemoteControlClient
|
||||
synchronized(mCacheLock) {
|
||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||
while (displayIterator.hasNext()) {
|
||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||
final DisplayInfoForClient di = displayIterator.next();
|
||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||
di.mEnabled = enable;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
package android.media.session;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadataEditor;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.Rating;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Contains metadata about an item, such as the title, artist, etc.
|
||||
@@ -40,7 +43,8 @@ public final class MediaMetadata implements Parcelable {
|
||||
public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
|
||||
|
||||
/**
|
||||
* The duration of the media in ms. A duration of 0 is the default.
|
||||
* The duration of the media in ms. A negative duration indicates that the
|
||||
* duration is unknown (or infinite).
|
||||
*/
|
||||
public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
|
||||
|
||||
@@ -64,13 +68,18 @@ public final class MediaMetadata implements Parcelable {
|
||||
*/
|
||||
public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
|
||||
|
||||
/**
|
||||
* The compilation status of the media.
|
||||
*/
|
||||
public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
|
||||
|
||||
/**
|
||||
* The date the media was created or published as TODO determine format.
|
||||
*/
|
||||
public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
|
||||
|
||||
/**
|
||||
* The year the media was created or published as a numeric String.
|
||||
* The year the media was created or published as a long.
|
||||
*/
|
||||
public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
|
||||
|
||||
@@ -151,8 +160,9 @@ public final class MediaMetadata implements Parcelable {
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_STRING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
|
||||
@@ -165,6 +175,36 @@ public final class MediaMetadata implements Parcelable {
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
|
||||
}
|
||||
|
||||
private static final SparseArray<String> EDITOR_KEY_MAPPING;
|
||||
|
||||
static {
|
||||
EDITOR_KEY_MAPPING = new SparseArray<String>();
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.BITMAP_KEY_ARTWORK, METADATA_KEY_ART);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_OTHERS, METADATA_KEY_RATING);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_USER, METADATA_KEY_USER_RATING);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_KEY_ALBUM);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
|
||||
METADATA_KEY_ALBUM_ARTIST);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_KEY_ARTIST);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_KEY_AUTHOR);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
|
||||
METADATA_KEY_TRACK_NUMBER);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_KEY_COMPOSER);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
|
||||
METADATA_KEY_COMPILATION);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_KEY_DATE);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
|
||||
METADATA_KEY_DISC_NUMBER);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_KEY_DURATION);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_KEY_GENRE);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
|
||||
METADATA_KEY_NUM_TRACKS);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_KEY_TITLE);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_KEY_WRITER);
|
||||
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_KEY_YEAR);
|
||||
}
|
||||
|
||||
private final Bundle mBundle;
|
||||
|
||||
private MediaMetadata(Bundle bundle) {
|
||||
@@ -175,6 +215,16 @@ public final class MediaMetadata implements Parcelable {
|
||||
mBundle = in.readBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given key is contained in the metadata
|
||||
*
|
||||
* @param key a String key
|
||||
* @return true if the key exists in this metadata, false otherwise
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return mBundle.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key, or null if no mapping of
|
||||
* the desired type exists for the given key or a null value is explicitly
|
||||
@@ -195,7 +245,7 @@ public final class MediaMetadata implements Parcelable {
|
||||
* @return a long value
|
||||
*/
|
||||
public long getLong(String key) {
|
||||
return mBundle.getLong(key);
|
||||
return mBundle.getLong(key, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,6 +294,18 @@ public final class MediaMetadata implements Parcelable {
|
||||
dest.writeBundle(mBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting the String key used by {@link MediaMetadata} from the
|
||||
* integer key that {@link MediaMetadataEditor} uses.
|
||||
*
|
||||
* @param editorKey The key used by the editor
|
||||
* @return The key used by this class or null if no mapping exists
|
||||
* @hide
|
||||
*/
|
||||
public static String getKeyFromMetadataEditorKey(int editorKey) {
|
||||
return EDITOR_KEY_MAPPING.get(editorKey, null);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MediaMetadata> CREATOR
|
||||
= new Parcelable.Creator<MediaMetadata>() {
|
||||
@Override
|
||||
@@ -295,10 +357,9 @@ public final class MediaMetadata implements Parcelable {
|
||||
* <li>{@link #METADATA_KEY_WRITER}</li>
|
||||
* <li>{@link #METADATA_KEY_COMPOSER}</li>
|
||||
* <li>{@link #METADATA_KEY_DATE}</li>
|
||||
* <li>{@link #METADATA_KEY_YEAR}</li>
|
||||
* <li>{@link #METADATA_KEY_GENRE}</li>
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>li>
|
||||
* <li>{@link #METADATA_KEY_ART_URI}</li>li>
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
|
||||
* <li>{@link #METADATA_KEY_ART_URI}</li>
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
|
||||
* </ul>
|
||||
*
|
||||
@@ -326,6 +387,7 @@ public final class MediaMetadata implements Parcelable {
|
||||
* <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
|
||||
* <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
|
||||
* <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
|
||||
* <li>{@link #METADATA_KEY_YEAR}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
|
||||
213
media/java/android/media/session/MediaSessionLegacyHelper.java
Normal file
213
media/java/android/media/session/MediaSessionLegacyHelper.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 android.media.session;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
/**
|
||||
* Helper for connecting existing APIs up to the new session APIs. This can be
|
||||
* used by RCC, AudioFocus, etc. to create a single session that translates to
|
||||
* all those components.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class MediaSessionLegacyHelper {
|
||||
private static final String TAG = "MediaSessionHelper";
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
private static MediaSessionLegacyHelper sInstance;
|
||||
|
||||
private SessionManager mSessionManager;
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
// The legacy APIs use PendingIntents to register/unregister media button
|
||||
// receivers and these are associated with RCC.
|
||||
private ArrayMap<PendingIntent, SessionHolder> mSessions = new ArrayMap<PendingIntent, SessionHolder>();
|
||||
|
||||
private MediaSessionLegacyHelper(Context context) {
|
||||
mSessionManager = (SessionManager) context
|
||||
.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
||||
}
|
||||
|
||||
public static MediaSessionLegacyHelper getHelper(Context context) {
|
||||
synchronized (sLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new MediaSessionLegacyHelper(context);
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public Session getSession(PendingIntent pi) {
|
||||
SessionHolder holder = mSessions.get(pi);
|
||||
return holder == null ? null : holder.mSession;
|
||||
}
|
||||
|
||||
public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) {
|
||||
|
||||
SessionHolder holder = getHolder(pi, true);
|
||||
TransportPerformer performer = holder.mSession.getTransportPerformer();
|
||||
if (holder.mRccListener != null) {
|
||||
if (holder.mRccListener == listener) {
|
||||
// This is already the registered listener, ignore
|
||||
return;
|
||||
}
|
||||
// Otherwise it changed so we need to switch to the new one
|
||||
performer.removeListener(holder.mRccListener);
|
||||
}
|
||||
performer.addListener(listener, mHandler);
|
||||
holder.mRccListener = listener;
|
||||
holder.update();
|
||||
}
|
||||
|
||||
public void removeRccListener(PendingIntent pi) {
|
||||
SessionHolder holder = getHolder(pi, false);
|
||||
if (holder != null && holder.mRccListener != null) {
|
||||
holder.mSession.getTransportPerformer().removeListener(holder.mRccListener);
|
||||
holder.mRccListener = null;
|
||||
holder.update();
|
||||
}
|
||||
}
|
||||
|
||||
public void addMediaButtonListener(PendingIntent pi,
|
||||
Context context) {
|
||||
SessionHolder holder = getHolder(pi, true);
|
||||
if (holder.mMediaButtonListener != null) {
|
||||
// Already have this listener registered
|
||||
return;
|
||||
}
|
||||
holder.mMediaButtonListener = new MediaButtonListener(pi, context);
|
||||
holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler);
|
||||
}
|
||||
|
||||
public void removeMediaButtonListener(PendingIntent pi) {
|
||||
SessionHolder holder = getHolder(pi, false);
|
||||
if (holder != null && holder.mMediaButtonListener != null) {
|
||||
holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener);
|
||||
holder.update();
|
||||
}
|
||||
}
|
||||
|
||||
private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
|
||||
SessionHolder holder = mSessions.get(pi);
|
||||
if (holder == null && createIfMissing) {
|
||||
Session session = mSessionManager.createSession(TAG);
|
||||
session.setTransportPerformerEnabled();
|
||||
session.publish();
|
||||
holder = new SessionHolder(session, pi);
|
||||
mSessions.put(pi, holder);
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
public static class MediaButtonListener extends TransportPerformer.Listener {
|
||||
private final PendingIntent mPendingIntent;
|
||||
private final Context mContext;
|
||||
|
||||
public MediaButtonListener(PendingIntent pi, Context context) {
|
||||
mPendingIntent = pi;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrevious() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
|
||||
}
|
||||
|
||||
private void sendKeyEvent(int keyCode) {
|
||||
KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
|
||||
try {
|
||||
mPendingIntent.send(mContext, 0, intent);
|
||||
} catch (CanceledException e) {
|
||||
Log.e(TAG, "Error sending media key down event:", e);
|
||||
// Don't bother sending up if down failed
|
||||
return;
|
||||
}
|
||||
|
||||
ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
|
||||
try {
|
||||
mPendingIntent.send(mContext, 0, intent);
|
||||
} catch (CanceledException e) {
|
||||
Log.e(TAG, "Error sending media key up event:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SessionHolder {
|
||||
public final Session mSession;
|
||||
public final PendingIntent mPi;
|
||||
public MediaButtonListener mMediaButtonListener;
|
||||
public TransportPerformer.Listener mRccListener;
|
||||
|
||||
public SessionHolder(Session session, PendingIntent pi) {
|
||||
mSession = session;
|
||||
mPi = pi;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (mMediaButtonListener == null && mRccListener == null) {
|
||||
mSession.release();
|
||||
mSessions.remove(mPi);
|
||||
} else if (mMediaButtonListener != null && mRccListener != null) {
|
||||
// TODO set session to active
|
||||
} else {
|
||||
// TODO set session to inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package android.media.session;
|
||||
|
||||
import android.media.RemoteControlClient;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* Playback state for a {@link Session}. This includes a state like
|
||||
@@ -87,6 +89,13 @@ public final class PlaybackState implements Parcelable {
|
||||
*/
|
||||
public static final long ACTION_SEEK_TO = 1 << 8;
|
||||
|
||||
/**
|
||||
* Indicates this performer supports the play/pause toggle command.
|
||||
*
|
||||
* @see #setActions
|
||||
*/
|
||||
public static final long ACTION_PLAY_PAUSE = 1 << 9;
|
||||
|
||||
/**
|
||||
* This is the default playback state and indicates that no media has been
|
||||
* added yet, or the performer has been reset and has no content to play.
|
||||
@@ -154,12 +163,33 @@ public final class PlaybackState implements Parcelable {
|
||||
*/
|
||||
public final static int PLAYSTATE_CONNECTING = 8;
|
||||
|
||||
/**
|
||||
* State indicating the player is currently skipping to the previous item.
|
||||
*
|
||||
* @see #setState
|
||||
*/
|
||||
public final static int PLAYSTATE_SKIPPING_BACKWARDS = 9;
|
||||
|
||||
/**
|
||||
* State indicating the player is currently skipping to the next item.
|
||||
*
|
||||
* @see #setState
|
||||
*/
|
||||
public final static int PLAYSTATE_SKIPPING_FORWARDS = 10;
|
||||
|
||||
/**
|
||||
* Set this value on {@link #setPosition(long)} to indicate the position is
|
||||
* not known for this item.
|
||||
*/
|
||||
public final static long PLAYBACK_POSITION_UNKNOWN = -1;
|
||||
|
||||
private int mState;
|
||||
private long mPosition;
|
||||
private long mBufferPosition;
|
||||
private float mSpeed;
|
||||
private long mCapabilities;
|
||||
private float mRate;
|
||||
private long mActions;
|
||||
private String mErrorMessage;
|
||||
private long mUpdateTime;
|
||||
|
||||
/**
|
||||
* Create an empty PlaybackState. At minimum a state and actions should be
|
||||
@@ -175,21 +205,24 @@ public final class PlaybackState implements Parcelable {
|
||||
* @param from The PlaybackState to duplicate
|
||||
*/
|
||||
public PlaybackState(PlaybackState from) {
|
||||
this.setState(from.getState());
|
||||
this.setPosition(from.getPosition());
|
||||
this.setBufferPosition(from.getBufferPosition());
|
||||
this.setSpeed(from.getSpeed());
|
||||
this.setActions(from.getActions());
|
||||
this.setErrorMessage(from.getErrorMessage());
|
||||
mState = from.mState;
|
||||
mPosition = from.mPosition;
|
||||
mRate = from.mRate;
|
||||
mUpdateTime = from.mUpdateTime;
|
||||
mBufferPosition = from.mBufferPosition;
|
||||
mActions = from.mActions;
|
||||
mErrorMessage = from.mErrorMessage;
|
||||
}
|
||||
|
||||
private PlaybackState(Parcel in) {
|
||||
this.setState(in.readInt());
|
||||
this.setPosition(in.readLong());
|
||||
this.setBufferPosition(in.readLong());
|
||||
this.setSpeed(in.readFloat());
|
||||
this.setActions(in.readLong());
|
||||
this.setErrorMessage(in.readString());
|
||||
mState = in.readInt();
|
||||
mPosition = in.readLong();
|
||||
mRate = in.readFloat();
|
||||
mUpdateTime = in.readLong();
|
||||
mBufferPosition = in.readLong();
|
||||
mActions = in.readLong();
|
||||
mErrorMessage = in.readString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -199,12 +232,13 @@ public final class PlaybackState implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(getState());
|
||||
dest.writeLong(getPosition());
|
||||
dest.writeLong(getBufferPosition());
|
||||
dest.writeFloat(getSpeed());
|
||||
dest.writeLong(getActions());
|
||||
dest.writeString(getErrorMessage());
|
||||
dest.writeInt(mState);
|
||||
dest.writeLong(mPosition);
|
||||
dest.writeFloat(mRate);
|
||||
dest.writeLong(mUpdateTime);
|
||||
dest.writeLong(mBufferPosition);
|
||||
dest.writeLong(mActions);
|
||||
dest.writeString(mErrorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +258,16 @@ public final class PlaybackState implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current state of playback. One of the following:
|
||||
* Set the current state of playback.
|
||||
* <p>
|
||||
* The position must be in ms and indicates the current playback position
|
||||
* within the track. If the position is unknown use
|
||||
* {@link #PLAYBACK_POSITION_UNKNOWN}.
|
||||
* <p>
|
||||
* The rate is a multiple of normal playback and should be 0 when paused and
|
||||
* negative when rewinding. Normal playback rate is 1.0.
|
||||
* <p>
|
||||
* The state must be one of the following:
|
||||
* <ul>
|
||||
* <li> {@link PlaybackState#PLAYSTATE_NONE}</li>
|
||||
* <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li>
|
||||
@@ -234,9 +277,18 @@ public final class PlaybackState implements Parcelable {
|
||||
* <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li>
|
||||
* <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li>
|
||||
* <li> {@link PlaybackState#PLAYSTATE_ERROR}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param state The current state of playback.
|
||||
* @param position The position in the current track in ms.
|
||||
* @param rate The current rate of playback as a multiple of normal
|
||||
* playback.
|
||||
*/
|
||||
public void setState(int mState) {
|
||||
this.mState = mState;
|
||||
public void setState(int state, long position, float rate) {
|
||||
this.mState = state;
|
||||
this.mPosition = position;
|
||||
this.mRate = rate;
|
||||
mUpdateTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,13 +298,6 @@ public final class PlaybackState implements Parcelable {
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current playback position in ms.
|
||||
*/
|
||||
public void setPosition(long position) {
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current buffer position in ms. This is the farthest playback
|
||||
* point that can be reached from the current position using only buffered
|
||||
@@ -272,21 +317,14 @@ public final class PlaybackState implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current playback speed as a multiple of normal playback. This
|
||||
* Get the current playback rate as a multiple of normal playback. This
|
||||
* should be negative when rewinding. A value of 1 means normal playback and
|
||||
* 0 means paused.
|
||||
*
|
||||
* @return The current rate of playback.
|
||||
*/
|
||||
public float getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current playback speed as a multiple of normal playback. This
|
||||
* should be negative when rewinding. A value of 1 means normal playback and
|
||||
* 0 means paused.
|
||||
*/
|
||||
public void setSpeed(float speed) {
|
||||
mSpeed = speed;
|
||||
public float getRate() {
|
||||
return mRate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,7 +343,7 @@ public final class PlaybackState implements Parcelable {
|
||||
* </ul>
|
||||
*/
|
||||
public long getActions() {
|
||||
return mCapabilities;
|
||||
return mActions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +362,7 @@ public final class PlaybackState implements Parcelable {
|
||||
* </ul>
|
||||
*/
|
||||
public void setActions(long capabilities) {
|
||||
mCapabilities = capabilities;
|
||||
mActions = capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,6 +373,17 @@ public final class PlaybackState implements Parcelable {
|
||||
return mErrorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elapsed real time at which position was last updated. If the
|
||||
* position has never been set this will return 0;
|
||||
*
|
||||
* @return The last time the position was updated.
|
||||
* @hide
|
||||
*/
|
||||
public long getLastPositionUpdateTime() {
|
||||
return mUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a user readable error message. This should be set when the state is
|
||||
* {@link PlaybackState#PLAYSTATE_ERROR}.
|
||||
@@ -343,6 +392,80 @@ public final class PlaybackState implements Parcelable {
|
||||
mErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link PlaybackState} state for the given
|
||||
* {@link RemoteControlClient} state.
|
||||
*
|
||||
* @param rccState The state used by {@link RemoteControlClient}.
|
||||
* @return The equivalent state used by {@link PlaybackState}.
|
||||
* @hide
|
||||
*/
|
||||
public static int getStateFromRccState(int rccState) {
|
||||
switch (rccState) {
|
||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||
return PLAYSTATE_BUFFERING;
|
||||
case RemoteControlClient.PLAYSTATE_ERROR:
|
||||
return PLAYSTATE_ERROR;
|
||||
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
|
||||
return PLAYSTATE_FAST_FORWARDING;
|
||||
case RemoteControlClient.PLAYSTATE_NONE:
|
||||
return PLAYSTATE_NONE;
|
||||
case RemoteControlClient.PLAYSTATE_PAUSED:
|
||||
return PLAYSTATE_PAUSED;
|
||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||
return PLAYSTATE_PLAYING;
|
||||
case RemoteControlClient.PLAYSTATE_REWINDING:
|
||||
return PLAYSTATE_REWINDING;
|
||||
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
|
||||
return PLAYSTATE_SKIPPING_BACKWARDS;
|
||||
case RemoteControlClient.PLAYSTATE_STOPPED:
|
||||
return PLAYSTATE_STOPPED;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static long getActionsFromRccControlFlags(int rccFlags) {
|
||||
long actions = 0;
|
||||
long flag = 1;
|
||||
while (flag <= rccFlags) {
|
||||
if ((flag & rccFlags) != 0) {
|
||||
actions |= getActionForRccFlag((int) flag);
|
||||
}
|
||||
flag = flag << 1;
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static long getActionForRccFlag(int flag) {
|
||||
switch (flag) {
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
|
||||
return ACTION_PREVIOUS_ITEM;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
|
||||
return ACTION_REWIND;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
|
||||
return ACTION_PLAY;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
|
||||
return ACTION_PLAY_PAUSE;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
|
||||
return ACTION_PAUSE;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
|
||||
return ACTION_STOP;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
|
||||
return ACTION_FASTFORWARD;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
|
||||
return ACTION_NEXT_ITEM;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
|
||||
return ACTION_SEEK_TO;
|
||||
case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
|
||||
return ACTION_RATING;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PlaybackState> CREATOR
|
||||
= new Parcelable.Creator<PlaybackState>() {
|
||||
@Override
|
||||
|
||||
@@ -280,18 +280,11 @@ public final class TransportPerformer {
|
||||
/**
|
||||
* Report that audio focus has changed on the app. This only happens if
|
||||
* you have indicated you have started playing with
|
||||
* {@link #setPlaybackState}. TODO figure out route focus apis/handling.
|
||||
* {@link #setPlaybackState}.
|
||||
*
|
||||
* @param focusChange The type of focus change, TBD. The default
|
||||
* implementation will deliver a call to {@link #onPause}
|
||||
* when focus is lost.
|
||||
* @param focusChange The type of focus change, TBD.
|
||||
*/
|
||||
public void onRouteFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
onPause();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -323,6 +324,34 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
}
|
||||
}
|
||||
|
||||
private PlaybackState getStateWithUpdatedPosition() {
|
||||
PlaybackState state = mPlaybackState;
|
||||
long duration = -1;
|
||||
if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
|
||||
duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
|
||||
}
|
||||
PlaybackState result = null;
|
||||
if (state != null) {
|
||||
if (state.getState() == PlaybackState.PLAYSTATE_PLAYING
|
||||
|| state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING
|
||||
|| state.getState() == PlaybackState.PLAYSTATE_REWINDING) {
|
||||
long updateTime = state.getLastPositionUpdateTime();
|
||||
if (updateTime > 0) {
|
||||
long position = (long) (state.getRate()
|
||||
* (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
|
||||
if (duration >= 0 && position > duration) {
|
||||
position = duration;
|
||||
} else if (position < 0) {
|
||||
position = 0;
|
||||
}
|
||||
result = new PlaybackState(state);
|
||||
result.setState(state.getState(), position, state.getRate());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result == null ? state : result;
|
||||
}
|
||||
|
||||
private final RouteConnectionRecord.Listener mConnectionListener
|
||||
= new RouteConnectionRecord.Listener() {
|
||||
@Override
|
||||
@@ -625,7 +654,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public PlaybackState getPlaybackState() {
|
||||
return mPlaybackState;
|
||||
return getStateWithUpdatedPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -119,7 +119,9 @@ public class PlayerSession {
|
||||
}
|
||||
|
||||
private void updateState(int newState) {
|
||||
mPlaybackState.setState(newState);
|
||||
float rate = newState == PlaybackState.PLAYSTATE_PLAYING ? 1 : 0;
|
||||
long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
|
||||
mPlaybackState.setState(newState, position, rate);
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ public class PlayerSession {
|
||||
@Override
|
||||
public void onError(int type, int extra, Bundle extras, Throwable error) {
|
||||
Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, -1, 0);
|
||||
if (error != null) {
|
||||
mPlaybackState.setErrorMessage(error.getLocalizedMessage());
|
||||
}
|
||||
@@ -147,34 +149,33 @@ public class PlayerSession {
|
||||
if (newState != Renderer.STATE_ERROR) {
|
||||
mPlaybackState.setErrorMessage(null);
|
||||
}
|
||||
long position = -1;
|
||||
if (mRenderer != null) {
|
||||
position = mRenderer.getSeekPosition();
|
||||
}
|
||||
switch (newState) {
|
||||
case Renderer.STATE_ENDED:
|
||||
case Renderer.STATE_STOPPED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_INIT:
|
||||
case Renderer.STATE_PREPARING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_ERROR:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_PAUSED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_PLAYING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1);
|
||||
break;
|
||||
default:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
|
||||
mPlaybackState.setErrorMessage("unkown state");
|
||||
break;
|
||||
}
|
||||
if (mRenderer != null) {
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
} else {
|
||||
mPlaybackState.setPosition(-1);
|
||||
}
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
if (mListener != null) {
|
||||
mListener.onPlayStateChanged(mPlaybackState);
|
||||
@@ -188,8 +189,8 @@ public class PlayerSession {
|
||||
@Override
|
||||
public void onFocusLost() {
|
||||
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
|
||||
mPerformer.setPlaybackState(mPlaybackState);
|
||||
if (mListener != null) {
|
||||
mListener.onPlayStateChanged(mPlaybackState);
|
||||
|
||||
@@ -158,30 +158,33 @@ public class OneMediaRouteProvider extends RouteProviderService {
|
||||
if (newState != Renderer.STATE_ERROR) {
|
||||
mPlaybackState.setErrorMessage(null);
|
||||
}
|
||||
long position = -1;
|
||||
if (mRenderer != null) {
|
||||
position = mRenderer.getSeekPosition();
|
||||
}
|
||||
switch (newState) {
|
||||
case Renderer.STATE_ENDED:
|
||||
case Renderer.STATE_STOPPED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_INIT:
|
||||
case Renderer.STATE_PREPARING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_ERROR:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_PAUSED:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
|
||||
break;
|
||||
case Renderer.STATE_PLAYING:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1);
|
||||
break;
|
||||
default:
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
|
||||
mPlaybackState.setErrorMessage("unkown state");
|
||||
break;
|
||||
}
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
|
||||
mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
|
||||
}
|
||||
@@ -193,8 +196,8 @@ public class OneMediaRouteProvider extends RouteProviderService {
|
||||
@Override
|
||||
public void onFocusLost() {
|
||||
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
|
||||
mPlaybackState.setPosition(mRenderer.getSeekPosition());
|
||||
mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, mRenderer.getSeekPosition(), 0);
|
||||
mRenderer.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user