Completely switched to MediaSession

This commit is contained in:
Karim Abou Zeid 2017-03-17 22:08:05 +01:00
commit 826d874758
3 changed files with 120 additions and 147 deletions

View file

@ -98,12 +98,6 @@
android:name=".service.MusicService"
android:enabled="true" />
<receiver android:name=".service.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<meta-data
android:name="com.crashlytics.ApiKey"
android:value="b23725bd3d266aa65c5a3dd1816b2f801524a189" />

View file

@ -14,9 +14,8 @@ import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
import android.media.session.MediaSession;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@ -30,6 +29,10 @@ import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.widget.Toast;
import com.bumptech.glide.BitmapRequestBuilder;
@ -50,7 +53,7 @@ import com.kabouzeid.gramophone.provider.MusicPlaybackQueueStore;
import com.kabouzeid.gramophone.provider.SongPlayCountStore;
import com.kabouzeid.gramophone.service.notification.PlayingNotification;
import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl;
import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl21;
import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl24;
import com.kabouzeid.gramophone.service.playback.Playback;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil;
@ -129,11 +132,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private int repeatMode;
private boolean queuesRestored;
private boolean pausedByTransientLossOfFocus;
private boolean receiversAndRemoteControlClientRegistered;
private PlayingNotification playingNotification;
private AudioManager audioManager;
@SuppressWarnings("deprecation")
private RemoteControlClient remoteControlClient;
private MediaSessionCompat mediaSession;
private MediaButtonIntentReceiver mediaButtonIntentReceiver = new MediaButtonIntentReceiver();
private PowerManager.WakeLock wakeLock;
private PlaybackHandler playerHandler;
private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
@ -147,6 +150,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private HandlerThread queueSaveHandlerThread;
private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper();
private ThrottledSeekHandler throttledSeekHandler;
private boolean becomingNoisyReceiverRegistered;
private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, @NonNull Intent intent) {
@ -180,6 +185,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
playback = new MultiPlayer(this);
playback.setCallbacks(this);
setupMediaSession();
// queue saving needs to run on a separate thread so that it doesn't block the playback handler events
queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND);
queueSaveHandlerThread.start();
@ -187,12 +194,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
uiThreadHandler = new Handler();
registerReceiversAndRemoteControlClient();
registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
playingNotification = new PlayingNotificationImpl21();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
playingNotification = new PlayingNotificationImpl24();
} else {
playingNotification = new PlayingNotificationImpl();
}
@ -209,16 +214,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
restoreState();
sendBroadcast(new Intent("com.kabouzeid.gramophone.PHONOGRAPH_MUSIC_SERVICE_CREATED"));
}
mediaSession.setActive(true);
private void registerReceiversAndRemoteControlClient() {
if (!receiversAndRemoteControlClientRegistered) {
registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
initRemoteControlClient();
receiversAndRemoteControlClientRegistered = true;
}
sendBroadcast(new Intent("com.kabouzeid.gramophone.PHONOGRAPH_MUSIC_SERVICE_CREATED"));
}
private AudioManager getAudioManager() {
@ -228,21 +226,62 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return audioManager;
}
@SuppressWarnings("deprecation")
private void initRemoteControlClient() {
private void setupMediaSession() {
ComponentName mediaButtonReceiverComponentName = new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
remoteControlClient = new RemoteControlClient(PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0));
remoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
getAudioManager().registerRemoteControlClient(remoteControlClient);
mediaButtonIntent.setComponent(mediaButtonReceiverComponentName);
PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
mediaSession = new MediaSessionCompat(this, "Phonograph", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent);
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
play();
}
@Override
public void onPause() {
pause();
}
@Override
public void onSkipToNext() {
playNextSong(true);
}
@Override
public void onSkipToPrevious() {
back(true);
}
@Override
public void onStop() {
quit();
}
@Override
public void onSeekTo(long pos) {
seek((int) pos);
}
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
mediaButtonIntentReceiver.onReceive(MusicService.this, mediaButtonEvent);
return true;
}
});
mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
| MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
MediaButtonReceiver.handleIntent(mediaSession, intent);
if (intent != null) {
if (intent.getAction() != null) {
restoreQueuesAndPositionIfNecessary();
@ -268,8 +307,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
playNextSong(true);
break;
case ACTION_STOP:
stop();
break;
case ACTION_QUIT:
return quit();
}
@ -282,6 +319,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
@Override
public void onDestroy() {
unregisterReceiver(widgetIntentReceiver);
if (becomingNoisyReceiverRegistered) {
unregisterReceiver(becomingNoisyReceiver);
becomingNoisyReceiverRegistered = false;
}
mediaSession.setActive(false);
quit();
releaseResources();
getContentResolver().unregisterContentObserver(mediaStoreObserver);
@ -311,15 +353,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return true;
}
private void unregisterReceiversAndRemoteControlClient() {
if (receiversAndRemoteControlClientRegistered) {
unregisterReceiver(becomingNoisyReceiver);
getAudioManager().unregisterRemoteControlClient(remoteControlClient);
getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
receiversAndRemoteControlClientRegistered = false;
}
}
private static final class QueueSaveHandler extends Handler {
@NonNull
private final WeakReference<MusicService> mService;
@ -399,7 +432,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
private int quit() {
unregisterReceiversAndRemoteControlClient();
pause();
playingNotification.stop();
@ -407,6 +439,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return START_STICKY;
} else {
closeAudioEffectSession();
getAudioManager().abandonAudioFocus(audioFocusListener);
stopSelf();
return START_NOT_STICKY;
}
@ -427,14 +460,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
playback.release();
playback = null;
}
public void stop() {
pausedByTransientLossOfFocus = false;
savePositionInTrack();
playback.stop();
notifyChange(PLAY_STATE_CHANGED);
getAudioManager().abandonAudioFocus(audioFocusListener);
mediaSession.release();
}
public boolean isPlaying() {
@ -499,31 +525,29 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
private void updateRemoteControlClient() {
updateRemoteControlClient(PreferenceUtil.getInstance(this).albumArtOnLockscreen(), PreferenceUtil.getInstance(this).blurredAlbumArt());
}
private void updateRemoteControlClient(boolean showAlbumArt, final boolean blurAlbumArt) {
private void updateMediaSession() {
final Song song = getCurrentSong();
remoteControlClient
.editMetadata(!showAlbumArt)
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title)
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, song.albumName)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artistName)
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, song.artistName)
final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artistName)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1)
.putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year)
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null);
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, song.duration)
.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, song.trackNumber)
// .putLong(MediaMetadataRetriever.METADATA_KEY_YEAR, song.year) // throws java.lang.IllegalArgumentException: Invalid type 'long' for key 8 on some Android versions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size());
}
.apply();
if (showAlbumArt) {
if (PreferenceUtil.getInstance(this).albumArtOnLockscreen()) {
final Point screenSize = Util.getScreenSize(MusicService.this);
final BitmapRequestBuilder<?, Bitmap> request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song)
.checkIgnoreMediaStore(MusicService.this)
.asBitmap().build();
if (blurAlbumArt) {
if (PreferenceUtil.getInstance(this).blurredAlbumArt()) {
request.transform(new BlurTransformation.Builder(MusicService.this).build());
}
runOnUiThread(new Runnable() {
@ -533,28 +557,21 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
super.onLoadFailed(e, errorDrawable);
updateRemoteControlClientBitmap(null);
mediaSession.setMetadata(metaData.build());
}
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
updateRemoteControlClientBitmap(copy(resource));
metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource));
mediaSession.setMetadata(metaData.build());
}
});
}
});
} else {
updateRemoteControlClientBitmap(null);
mediaSession.setMetadata(metaData.build());
}
}
private void updateRemoteControlClientBitmap(final Bitmap albumArt) {
//noinspection deprecation
remoteControlClient
.editMetadata(false)
.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt)
.apply();
}
private static Bitmap copy(Bitmap bitmap) {
@ -787,8 +804,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
if (!playback.isInitialized()) {
playSongAt(getPosition());
} else {
registerReceiversAndRemoteControlClient();
playback.start();
if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true;
}
if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
@ -957,13 +977,25 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
appWidgetSmall.notifyChange(this, what);
}
private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_SEEK_TO;
private void handleChangeInternal(@NonNull final String what) {
switch (what) {
case PLAY_STATE_CHANGED:
final boolean isPlaying = isPlaying();
playingNotification.update();
//noinspection deprecation
remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
mediaSession.setPlaybackState(
new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(isPlaying ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
getPosition(), 1)
.build());
if (!isPlaying && getSongProgressMillis() > 0) {
savePositionInTrack();
}
@ -971,7 +1003,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
break;
case META_CHANGED:
playingNotification.update();
updateRemoteControlClient();
updateMediaSession();
savePosition();
savePositionInTrack();
final Song currentSong = getCurrentSong();
@ -982,6 +1014,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
songPlayCountHelper.notifySongChanged(currentSong);
break;
case QUEUE_CHANGED:
updateMediaSession();
saveState();
if (playingQueue.size() > 0) {
prepareNext();
@ -996,6 +1029,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return playback.getAudioSessionId();
}
public MediaSessionCompat getMediaSession() {
return mediaSession;
}
public void releaseWakeLock() {
if (wakeLock.isHeld()) {
wakeLock.release();
@ -1018,7 +1055,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
break;
case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN:
case PreferenceUtil.BLURRED_ALBUM_ART:
updateRemoteControlClient();
updateMediaSession();
break;
case PreferenceUtil.COLORED_NOTIFICATION:
playingNotification.update();
@ -1128,7 +1165,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
case FOCUS_CHANGE:
switch (msg.arg1) {
case AudioManager.AUDIOFOCUS_GAIN:
service.registerReceiversAndRemoteControlClient();
if (!service.isPlaying() && service.pausedByTransientLossOfFocus) {
service.play();
service.pausedByTransientLossOfFocus = false;
@ -1140,7 +1176,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media playback
service.pause();
service.unregisterReceiversAndRemoteControlClient();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

View file

@ -9,9 +9,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.session.MediaSession;
import android.os.Build;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v7.app.NotificationCompat;
import android.support.v7.graphics.Palette;
import android.text.TextUtils;
@ -23,13 +21,10 @@ import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.glide.SongGlideRequest;
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MediaButtonIntentReceiver;
import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.ui.activities.MainActivity;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import hugo.weaving.DebugLog;
import static android.content.Context.NOTIFICATION_SERVICE;
import static com.kabouzeid.gramophone.service.MusicService.ACTION_REWIND;
import static com.kabouzeid.gramophone.service.MusicService.ACTION_SKIP;
@ -39,13 +34,12 @@ import static com.kabouzeid.gramophone.service.MusicService.ACTION_TOGGLE_PAUSE;
* @author Karim Abou Zeid (kabouzeid)
*/
public class PlayingNotificationImpl21 implements PlayingNotification {
public class PlayingNotificationImpl24 implements PlayingNotification {
private static final int NOTIFY_MODE_FOREGROUND = 1;
private static final int NOTIFY_MODE_BACKGROUND = 0;
private MusicService service;
private MediaSessionCompat mediaSession;
private NotificationManager notificationManager;
private int notifyMode = NOTIFY_MODE_BACKGROUND;
@ -56,53 +50,6 @@ public class PlayingNotificationImpl21 implements PlayingNotification {
public synchronized void init(MusicService service) {
this.service = service;
notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
setupMediaSession();
}
@DebugLog
private void setupMediaSession() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mediaSession = new MediaSessionCompat(service, "Phonograph");
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
service.play();
}
@Override
public void onPause() {
service.pause();
}
@Override
public void onSkipToNext() {
service.playNextSong(true);
}
@Override
public void onSkipToPrevious() {
service.back(true);
}
@Override
public void onStop() {
service.stop();
}
@Override
public void onSeekTo(long pos) {
service.seek((int) pos);
}
});
PendingIntent pi = PendingIntent.getBroadcast(service, 0,
new Intent(service, MediaButtonIntentReceiver.class),
PendingIntent.FLAG_UPDATE_CURRENT);
mediaSession.setMediaButtonReceiver(pi);
mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
| MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
}
}
@Override
@ -174,7 +121,7 @@ public class PlayingNotificationImpl21 implements PlayingNotification {
.addAction(nextAction);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2))
builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
if (PreferenceUtil.getInstance(service).coloredNotification())
builder.setColor(color);
@ -184,9 +131,7 @@ public class PlayingNotificationImpl21 implements PlayingNotification {
return; // notification has been stopped before loading was finished
updateNotifyModeAndPostNotification(builder.build());
}
});
}
});
}
@ -224,6 +169,5 @@ public class PlayingNotificationImpl21 implements PlayingNotification {
public synchronized void stop() {
stopped = true;
service.stopForeground(true);
mediaSession.release();
}
}