Simon answer above is correct. I had a similar problem when I had fragments with a music player, and I needed to return to this interface with a click of a button. Your case is similar, but instead of returning to the user interface, you want to control playback. Here is what I did for my application. This enables playback of an audio list, including random and repeat functions. It also helps to display media controls in the notification bar.
- Create a
MusicPlayerService with the following code:
public class MediaPlayerService extends Service implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener, AudioManager.OnAudioFocusChangeListener { public static final String ACTION_PLAY = "pkg_name.ACTION_PLAY"; public static final String ACTION_PAUSE = "pkg_name.ACTION_PAUSE"; public static final String ACTION_PREVIOUS = "pkg_name.ACTION_PREVIOUS"; public static final String ACTION_NEXT = "pkg_name.ACTION_NEXT"; public static final String ACTION_STOP = "pkg_name.ACTION_STOP"; private MediaPlayer mediaPlayer; //MediaSession private MediaSessionManager mediaSessionManager; private MediaSessionCompat mediaSession; private MediaControllerCompat.TransportControls transportControls; //AudioPlayer notification ID private static final int NOTIFICATION_ID = 101; //Used to pause/resume MediaPlayer private int resumePosition; // Binder given to clients private final IBinder iBinder = new LocalBinder(); //List of available Audio files private ArrayList audioList; private int audioIndex = -1; //Handle incoming phone calls private boolean ongoingCall = false; private PhoneStateListener phoneStateListener; private TelephonyManager telephonyManager; private Bitmap albumArtBitmap; private boolean shuffle = false; private boolean repeat = false; private Random rand; /** * Service lifecycle methods */ @Override public IBinder onBind(Intent intent) { return iBinder; } @Override public void onCreate() { super.onCreate(); // Perform one-time setup procedures // Manage incoming phone calls during playback. // Pause MediaPlayer on incoming call, // Resume on hangup. callStateListener(); //ACTION_AUDIO_BECOMING_NOISY -- change in audio outputs -- BroadcastReceiver registerBecomingNoisyReceiver(); //Listen for new Audio to play -- BroadcastReceiver register_playNewAudio(); rand = new Random(); StorageUtil storage = new StorageUtil(getApplicationContext()); shuffle = storage.loadShuffleRepeat("Shuffle"); repeat = storage.loadShuffleRepeat("Repeat"); } //The system calls this method when an activity, requests the service be started @Override public int onStartCommand(Intent intent, int flags, int startId) { try { //Load data from SharedPreferences StorageUtil storage = new StorageUtil(getApplicationContext()); audioList = storage.loadAudio(); audioIndex = storage.loadAudioIndex(); if (audioIndex != -1 && audioIndex ready to receive media commands mediaSession.setActive(true); //indicate that the MediaSession handles transport control commands // through its MediaSessionCompat.Callback. mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); //Set mediaSession MetaData updateMetaData(); // Attach Callback to receive MediaSession updates mediaSession.setCallback(new MediaSessionCompat.Callback() { // Implement callbacks @Override public void onPlay() { super.onPlay(); resumeMedia(); } @Override public void onPause() { super.onPause(); pauseMedia(); } }); } private void updateMetaData() { fetchBitmapOfAlbum(); // Update the current metadata mediaSession.setMetadata(new MediaMetadataCompat.Builder() .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArtBitmap) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "") .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, activeAudio.getAlbumName()) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, activeAudio.getTrackName()) .build()); } private Target target = new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { albumArtBitmap = bitmap; } @Override public void onBitmapFailed(Exception e, Drawable errorDrawable) { } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } }; private void fetchBitmapOfAlbum() { Picasso.get().load(activeAudio.getAlbumArt()) .placeholder(R.drawable.rotate_animation) .error(R.drawable.ic_blank) .into(target); } private void buildNotification(PlaybackStatus playbackStatus) { int notificationAction = android.R.drawable.ic_media_pause;//needs to be initialized PendingIntent play_pauseAction = null; //Build a new notification according to the current state of the MediaPlayer if (playbackStatus == PlaybackStatus.PLAYING) { notificationAction = android.R.drawable.ic_media_pause; //create the pause action play_pauseAction = playbackAction(1); } else if (playbackStatus == PlaybackStatus.PAUSED) { notificationAction = android.R.drawable.ic_media_play; //create the play action play_pauseAction = playbackAction(0); } fetchBitmapOfAlbum(); //replace with your own image String channelId = ""; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { channelId = "APP_MUSIC"; } // Create a new Notification NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId) // Hide the timestamp .setShowWhen(false) // Set the Notification style .setStyle(new androidx.media.app.NotificationCompat.MediaStyle() // Attach our MediaSession token .setMediaSession(mediaSession.getSessionToken()) // Show our playback controls in the compat view .setShowActionsInCompactView(0, 1, 2)) // Set the Notification color .setColor(ContextCompat.getColor(this.getApplicationContext(), R.color.colorAccent)) // Set the large and small icons .setLargeIcon(albumArtBitmap) .setSmallIcon(R.drawable.ic_stat_notifications) // Set Notification content information .setContentText(activeAudio.getTrackName()) .setTicker(activeAudio.getAlbumName() + "-" + activeAudio.getTrackName()) .setOngoing(true) .setContentTitle(activeAudio.getAlbumName()) .setContentInfo(activeAudio.getTrackName()) // Add playback actions .addAction(notificationAction, "pause", play_pauseAction) ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notificationBuilder.build()); } private PendingIntent playbackAction(int actionNumber) { Intent playbackAction = new Intent(this, MediaPlayerService.class); switch (actionNumber) { case 0: // Play playbackAction.setAction(ACTION_PLAY); return PendingIntent.getService(this, actionNumber, playbackAction, 0); case 1: // Pause playbackAction.setAction(ACTION_PAUSE); return PendingIntent.getService(this, actionNumber, playbackAction, 0); default: break; } return null; } private void removeNotification() { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(NOTIFICATION_ID); } private void handleIncomingActions(Intent playbackAction) { if (playbackAction == null || playbackAction.getAction() == null) return; String actionString = playbackAction.getAction(); if (actionString.equalsIgnoreCase(ACTION_PLAY)) { transportControls.play(); } else if (actionString.equalsIgnoreCase(ACTION_PAUSE)) { transportControls.pause(); } } /** * Play new Audio */ private BroadcastReceiver playNewAudio = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //Get the new media index form SharedPreferences audioIndex = new StorageUtil(getApplicationContext()).loadAudioIndex(); if (audioIndex != -1 && audioIndex
- Add the service to your manifest
<service android:name=".service.MediaPlayerService" /> - Bind the service in MainActivity and declare methods to call the service
public class MainActivity {
private MediaPlayerService player;
boolean serviceBound = false;
public static final String Broadcast_PLAY_NEW_AUDIO = "pkg_name.PlayNewAudio";
// Binding this Client to the AudioPlayer Service
private ServiceConnection serviceConnection = new ServiceConnection () {
@Override
public void onServiceConnected (ComponentName name, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
MediaPlayerService.LocalBinder binder = (MediaPlayerService.LocalBinder) service;
player = binder.getService ();
serviceBound = true;
}
@Override
public void onServiceDisconnected (ComponentName name) {
serviceBound = false;
}
};
// Call this method to play track
public void playAudio (int audioIndex, ArrayList updatedList) {
// Check is service is active
audioList = updatedList;
if (! serviceBound) {
Intent playerIntent = new Intent (this, MediaPlayerService.class);
startService (playerIntent);
bindService (playerIntent, serviceConnection, Context.BIND_AUTO_CREATE);
} else {
// Service is active
// Send a broadcast to the service -> PLAY_NEW_AUDIO
Intent broadcastIntent = new Intent (Broadcast_PLAY_NEW_AUDIO);
sendBroadcast (broadcastIntent);
}
}
// Additional methods for control
public void start () {
player.playMedia ();
}
public void pause () {
player.pauseMedia ();
}
public boolean isPlaying () {
if (player! = null && serviceBound) {
return player.isPlaying ();
}
return false;
}
}