unify media sessions.
Android doesn't like having 2 media sessions sticking around, hence we move the button handling code into MediaSessionTracker on lollipop.
This commit is contained in:
parent
1a13ca753a
commit
0f5a229df3
@ -38,6 +38,9 @@ import android.view.KeyEvent;
|
||||
/**
|
||||
* Receives media button events and calls to PlaybackService to respond
|
||||
* appropriately.
|
||||
*
|
||||
* Most of this logic is only needed for RemoteControlImplICS (like double-click)
|
||||
* as >= LP devices handle this using the MediaSessionCompat.
|
||||
*/
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
/**
|
||||
@ -202,6 +205,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
/**
|
||||
* Runable to run a delayed action
|
||||
* Inspects sLastClickTime and sDelayedClicks to guess what to do
|
||||
* Note: this is only used on pre-lolipop devices.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param serial the value of sLastClickTime during creation, used to identify stale events
|
||||
|
@ -22,33 +22,57 @@
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
|
||||
// Helper class used to show the notification seekbar.
|
||||
/**
|
||||
* Manages our active media session which is responsible for keeping
|
||||
* the notification seek bar up to date and handling key events.
|
||||
*/
|
||||
public class MediaSessionTracker {
|
||||
/**
|
||||
* Instance of Vanillas PlaybackService
|
||||
* Context we are working with.
|
||||
*/
|
||||
private PlaybackService mPlaybackService;
|
||||
private Context mContext;
|
||||
/**
|
||||
* Our generic MediaSession
|
||||
*/
|
||||
private MediaSessionCompat mMediaSession;
|
||||
|
||||
|
||||
MediaSessionTracker(PlaybackService service) {
|
||||
mPlaybackService = service;
|
||||
|
||||
mMediaSession = new MediaSessionCompat(service, "Vanilla Music Media Session");
|
||||
MediaSessionTracker(Context context) {
|
||||
mContext = context;
|
||||
mMediaSession = new MediaSessionCompat(mContext, "Vanilla Music Media Session");
|
||||
mMediaSession.setCallback(new MediaSessionCompat.Callback() {
|
||||
@Override
|
||||
public void onSeekTo(long pos) {
|
||||
mPlaybackService.seekToPosition((int)pos);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onPause() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
|
||||
}
|
||||
@Override
|
||||
public void onPlay() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
|
||||
}
|
||||
@Override
|
||||
public void onSkipToNext() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
|
||||
}
|
||||
@Override
|
||||
public void onSkipToPrevious() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
|
||||
}
|
||||
@Override
|
||||
public void onStop() {
|
||||
// We will behave the same as Google Play Music: for "Stop" we unconditionally Pause instead
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
|
||||
}
|
||||
@Override
|
||||
public void onSeekTo(long pos) {
|
||||
PlaybackService.get(mContext).seekToPosition((int)pos);
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,20 +91,43 @@ public class MediaSessionTracker {
|
||||
|
||||
/**
|
||||
* Populates the active media session with new info.
|
||||
*
|
||||
* @param song The song containing the new metadata.
|
||||
* @param state PlaybackService state, used to determine playback state.
|
||||
*/
|
||||
public void updateSession(Song song, int state) {
|
||||
boolean playing = (state & PlaybackService.FLAG_PLAYING) != 0;
|
||||
final boolean playing = (state & PlaybackService.FLAG_PLAYING) != 0;
|
||||
final PlaybackService service = PlaybackService.get(mContext);
|
||||
|
||||
PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
|
||||
.setActions(PlaybackStateCompat.ACTION_SEEK_TO)
|
||||
.setState(playing ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
|
||||
mPlaybackService.getPosition(), 1.0f)
|
||||
service.getPosition(), 1.0f)
|
||||
.setActions(PlaybackStateCompat.ACTION_PLAY |
|
||||
PlaybackStateCompat.ACTION_STOP |
|
||||
PlaybackStateCompat.ACTION_PAUSE |
|
||||
PlaybackStateCompat.ACTION_PLAY_PAUSE |
|
||||
PlaybackStateCompat.ACTION_SEEK_TO |
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
|
||||
.build();
|
||||
mMediaSession.setPlaybackState(playbackState);
|
||||
|
||||
if (song != null) {
|
||||
mMediaSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
|
||||
.build());
|
||||
final Bitmap cover = song.getCover(mContext);
|
||||
MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover);
|
||||
// logic copied from FullPlaybackActivity.updateQueuePosition()
|
||||
if (PlaybackService.finishAction(service.getState()) != SongTimeline.FINISH_RANDOM) {
|
||||
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, service.getTimelinePosition() + 1);
|
||||
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, service.getTimelineLength());
|
||||
}
|
||||
mMediaSession.setMetadata(metadataBuilder.build());
|
||||
}
|
||||
|
||||
mMediaSession.setPlaybackState(playbackState);
|
||||
mMediaSession.setActive(true);
|
||||
}
|
||||
}
|
||||
|
@ -1965,6 +1965,7 @@ public final class PlaybackService extends Service
|
||||
for (int i = list.size(); --i != -1; )
|
||||
list.get(i).onPositionInfoChanged();
|
||||
mRemoteControlClient.updateRemote(mCurrentSong, mState, mForceNotificationVisible);
|
||||
mMediaSessionTracker.updateSession(mCurrentSong, mState);
|
||||
}
|
||||
|
||||
private final LibraryObserver mObserver = new LibraryObserver() {
|
||||
|
@ -18,156 +18,25 @@
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
@TargetApi(21)
|
||||
public class RemoteControlImplLp implements RemoteControl.Client {
|
||||
/**
|
||||
* Context of this instance
|
||||
*/
|
||||
private final Context mContext;
|
||||
/**
|
||||
* Objects MediaSession handle
|
||||
*/
|
||||
private MediaSession mMediaSession;
|
||||
/**
|
||||
* Whether the cover should be shown. 1 for yes, 0 for no, -1 for
|
||||
* uninitialized.
|
||||
*/
|
||||
private int mShowCover = -1;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param context The context to use
|
||||
* This is just a placeholder implementation: On API 21, media buttons are handled in MediaSessionTracker.
|
||||
*/
|
||||
public RemoteControlImplLp(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new MediaSession on the device
|
||||
*/
|
||||
public void initializeRemote() {
|
||||
// make sure there is only one registered remote
|
||||
unregisterRemote();
|
||||
if (MediaButtonReceiver.useHeadsetControls(mContext) == false)
|
||||
return;
|
||||
|
||||
mMediaSession = new MediaSession(mContext, "Vanilla Music");
|
||||
|
||||
mMediaSession.setCallback(new MediaSession.Callback() {
|
||||
@Override
|
||||
public void onPause() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
|
||||
}
|
||||
@Override
|
||||
public void onPlay() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
|
||||
}
|
||||
@Override
|
||||
public void onSkipToNext() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
|
||||
}
|
||||
@Override
|
||||
public void onSkipToPrevious() {
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
|
||||
}
|
||||
@Override
|
||||
public void onStop() {
|
||||
// We will behave the same as Google Play Music: for "Stop" we unconditionally Pause instead
|
||||
MediaButtonReceiver.processKey(mContext, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(mContext.getPackageName(), MediaButtonReceiver.class.getName()));
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
|
||||
// This Seems to overwrite our MEDIA_BUTTON intent filter and there seems to be no way to unregister it
|
||||
// Well: We intent to keep this around as long as possible anyway. But WHY ANDROID?!
|
||||
mMediaSession.setMediaButtonReceiver(pendingIntent);
|
||||
mMediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a registered media session
|
||||
*/
|
||||
public void unregisterRemote() {
|
||||
if (mMediaSession != null) {
|
||||
mMediaSession.setActive(false);
|
||||
mMediaSession.release();
|
||||
mMediaSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninitializes our cached preferences, forcing a reload
|
||||
*/
|
||||
public void reloadPreference() {
|
||||
mShowCover = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the remote with new metadata.
|
||||
* {@link #initializeRemote()} must have been called
|
||||
* first.
|
||||
*
|
||||
* @param song The song containing the new metadata.
|
||||
* @param state PlaybackService state, used to determine playback state.
|
||||
* @param keepPaused whether or not to keep the remote updated in paused mode
|
||||
*/
|
||||
public void updateRemote(Song song, int state, boolean keepPaused) {
|
||||
MediaSession session = mMediaSession;
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
boolean isPlaying = ((state & PlaybackService.FLAG_PLAYING) != 0);
|
||||
|
||||
if (mShowCover == -1) {
|
||||
SharedPreferences settings = SharedPrefHelper.getSettings(mContext);
|
||||
mShowCover = settings.getBoolean(PrefKeys.COVER_ON_LOCKSCREEN, PrefDefaults.COVER_ON_LOCKSCREEN) ? 1 : 0;
|
||||
}
|
||||
|
||||
PlaybackService service = PlaybackService.get(mContext);
|
||||
|
||||
if (song != null) {
|
||||
Bitmap bitmap = null;
|
||||
if (mShowCover == 1 && (isPlaying || keepPaused)) {
|
||||
bitmap = song.getCover(mContext);
|
||||
}
|
||||
|
||||
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder()
|
||||
.putString(MediaMetadata.METADATA_KEY_ARTIST, song.artist)
|
||||
.putString(MediaMetadata.METADATA_KEY_ALBUM, song.album)
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, song.title)
|
||||
.putLong(MediaMetadata.METADATA_KEY_DURATION, song.duration)
|
||||
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap);
|
||||
|
||||
// logic copied from FullPlaybackActivity.updateQueuePosition()
|
||||
if (PlaybackService.finishAction(service.getState()) != SongTimeline.FINISH_RANDOM) {
|
||||
metadataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, service.getTimelinePosition() + 1);
|
||||
metadataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, service.getTimelineLength());
|
||||
}
|
||||
|
||||
session.setMetadata(metadataBuilder.build());
|
||||
}
|
||||
|
||||
int playbackState = (isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED);
|
||||
|
||||
session.setPlaybackState(new PlaybackState.Builder()
|
||||
.setState(playbackState, service.getPosition(), 1.0f)
|
||||
.setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE |
|
||||
PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS)
|
||||
.build());
|
||||
mMediaSession.setActive(true);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user