diff options
-rw-r--r-- | api/current.txt | 12 | ||||
-rw-r--r-- | media/java/android/media/AudioManager.java | 23 | ||||
-rw-r--r-- | media/java/android/media/MediaMetadataEditor.java | 6 | ||||
-rw-r--r-- | media/java/android/media/RemoteControlClient.java | 129 | ||||
-rw-r--r-- | media/java/android/media/session/MediaMetadata.java | 76 | ||||
-rw-r--r-- | media/java/android/media/session/MediaSessionLegacyHelper.java | 213 | ||||
-rw-r--r-- | media/java/android/media/session/PlaybackState.java | 213 | ||||
-rw-r--r-- | media/java/android/media/session/TransportPerformer.java | 11 | ||||
-rw-r--r-- | services/core/java/com/android/server/media/MediaSessionRecord.java | 31 | ||||
-rw-r--r-- | tests/OneMedia/src/com/android/onemedia/PlayerSession.java | 31 | ||||
-rw-r--r-- | tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java | 21 |
11 files changed, 664 insertions, 102 deletions
diff --git a/api/current.txt b/api/current.txt index 7615d4f..0794685 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8ae06e0..1dcfcb8 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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); + } } /** diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java index 3bfdb5a..1a4e8da 100644 --- a/media/java/android/media/MediaMetadataEditor.java +++ b/media/java/android/media/MediaMetadataEditor.java @@ -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(); } /** diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index c2c61d3..8368df9 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -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 @@ -385,6 +392,22 @@ public class RemoteControlClient } /** + * @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, * on which you set the metadata for the RemoteControlClient instance. Once all the information @@ -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); + } } } @@ -1038,6 +1113,16 @@ public class RemoteControlClient 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; } diff --git a/media/java/android/media/session/MediaMetadata.java b/media/java/android/media/session/MediaMetadata.java index e2330f7..56bdf68 100644 --- a/media/java/android/media/session/MediaMetadata.java +++ b/media/java/android/media/session/MediaMetadata.java @@ -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"; @@ -65,12 +69,17 @@ 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) { @@ -176,6 +216,16 @@ public final class MediaMetadata implements Parcelable { } /** + * 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 * associated with the key. @@ -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 diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java new file mode 100644 index 0000000..4ee67d1 --- /dev/null +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -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 + } + } + } +} diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 14d9fb1..9e58ea8 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -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 @@ -88,6 +90,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> - */ - public void setState(int mState) { - this.mState = mState; + * </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 state, long position, float rate) { + this.mState = state; + this.mPosition = position; + this.mRate = rate; + mUpdateTime = SystemClock.elapsedRealtime(); } /** @@ -247,13 +299,6 @@ public final class PlaybackState implements Parcelable { } /** - * 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 * content. @@ -272,21 +317,14 @@ public final class PlaybackState implements Parcelable { } /** - * Get 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 float getSpeed() { - return mSpeed; - } - - /** - * Set 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 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; } /** @@ -336,6 +374,17 @@ public final class PlaybackState implements Parcelable { } /** + * 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 diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java index eddffd1..187f48d 100644 --- a/media/java/android/media/session/TransportPerformer.java +++ b/media/java/android/media/session/TransportPerformer.java @@ -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; - } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 2a1e2c3..ab2a723 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -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 diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 5dc3904..2e029f0 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -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); diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java index 6edcd7d..6537d49 100644 --- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java +++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java @@ -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 |