I have a MediaPlayerService, currently started when the Play/Pause Button on a ListView item is clicked. See code below (CustomListAdapter):
Intent intent = new Intent(v.getContext(),MediaPlayerService.class);
intent.putExtra("StreamLink",audio);
activity.startService(intent);
When this service is started by the code above I want to create a Notification with a Play/Stop button. The user should be able to get out of the app, be able to stop Media Playback e.g. player.stop() and start player.start(). Also when the Notification is clicked it should return the user to the MainActivity.
The code for my MediaPlayerService.java:
public class MediaPlayerService extends Service implements MediaPlayer.OnPreparedListener {
MediaPlayer mMediaPlayer = null;
public String audioStreamLink;
public int onStartCommand(Intent intent, int flags, int startId) {
// Get the Audio Streaming Link from the parsed JSON in the Main Activity
audioStreamLink = intent.getStringExtra("StreamLink");
// Instantiate MediaPlayer, set the Audio Type and acquire a wakelock, set the Media Player Data Source and Prepare.
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mMediaPlayer.setDataSource(audioStreamLink);
} catch (IOException e) {
e.printStackTrace();
}
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync();
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
/** Called when MediaPlayer is ready */
#Override
public void onPrepared(MediaPlayer player) {
player.start();
}
#Override
public void onDestroy() {
mMediaPlayer.stop();
mMediaPlayer.reset();
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
The process of getting a notification is completely documented down there in the documents, for example, see this.
To help you go through all this huge documentation these are the points :
You need to create the custom notification using a NotificationCompat.Builder
A typical music player service would start the notification using startForeground()
To add clickable buttons to the notification use addAction() when building the notifications.
Actions in Notifications are defined by PendingIntent, its a kind of normal Intent when it comes to responding to it.
When a button on the notification is clicked, the onStartCommand() is triggered with the intent you specified for that button (if you configure the intent correctly).
Each button's intent should have a different action so that you can identify the intent when it is received.
Inside the onStartCommand() you can play/pause and do other operations based on this intent's action.
Some reference I would suggest you to read :
https://developer.android.com/guide/topics/ui/notifiers/notifications.html
https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Updating
https://developer.android.com/reference/android/app/Service.html#startForeground(int,android.app.Notification)
Things would have been a bit different if you were using a MediaSession to play the media.
I have a Service using the new MediaBrowserServiceCompat to interact with my Player implementation.
Here's my service's onCreate()
#Override
public void onCreate() {
super.onCreate();
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
initMediaSession();
initMediaPlayer();
}
and my initMediaSession()
private void initMediaSession() {
Log.d(TAG, "initMediaSession: ");
mediaSessionCompat = new MediaSessionCompat(getApplicationContext(), TAG);
mediaSessionCompat.setCallback(new MediaSessionCompat.Callback() {
// I override the methods here
});
mediaSessionCompat.setActive(true);
// Method from MediaBrowserServiceCompat
setSessionToken(mediaSessionCompat.getSessionToken());
}
I then access my MediaBrowserCompat in my Activity:
#Override
public void onCreate() {
mediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, MediaPlaybackService.class), mediaBrowserConnectionCallback, null);
mediaBrowser.connect();
}
// I do stuff here
#Override
protected void onDestroy() {
super.onDestroy();
if (mediaBrowser != null) {
mediaBrowser.disconnect();
}
}
Every time mediaBrowser.connect() is called my service is created and killed when mediaBrowser.disconnect() is called.
The problem is when the MediaSessionCompat is recreated I lose any metadata I created or playback state I might have saved.
Is it the way it is supposed to be or mm I doing it wrong ?
Also If it is supposed to be that way does it mean I have to save my metadata somewhere and recall MediaSessionCompat.setMetadata() every time I create a new MediaSessionCompat object? or is there a more efficient solution ?
Thank you
As mentioned in the MediaBrowserService and the modern media playback app blog:
this wraps the API for bound services, which makes sense since we’re trying to connect to a Service.
The lifecycle of a bound service is tied directly to who binds to the service:
When the last client unbinds from the service, the system destroys the service (unless the service was also started by startService()).
In your case, the service will no longer have anyone bound to it between onDestroy() and onCreate().
The example of Universal Android Music Player's MusicService is to:
Call startService(new Intent(this, MediaPlaybackService.class) when playback begins
Call stopSelf() when playback is stopped
This ensures that changes in the bound clients do not cause the Service to be destroyed mid-playback.
I'm using bound service so that I am able to communicate between an activity and a service.
I'm binding to a service in onStart:
#Override
protected void onStart() {
super.onStart();
Intent bindIntent = new Intent(this, MusicService.class);
bindService(bindIntent, this, BIND_AUTO_CREATE);
}
waiting for service to bind:
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMusicService = ((MusicService.LocalBinder) service).getService();
mMusicService.setCallback(this);
}
handling disconnect from service:
#Override
public void onServiceDisconnected(ComponentName name) {
mMusicService = null;
}
unbinding from service in onDestroy:
#Override
protected void onDestroy() {
super.onDestroy();
if (mMusicService != null && isFinishing()) {
mMusicService.setCallback(null);
unbindService(this);
}
}
My problem is that when app is minimized, onDestroy gets called immediately and then onUnbind in Service gets called and music is stopped.
Here is onUnbind method (where mPlayer is MediaPlayer):
#Override
public boolean onUnbind(Intent intent) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
return super.onUnbind(intent);
}
If I don't implement onUnbind music continues to play (sometimes and sometimes it stops after some time) and when I open the app again (from minimized applications) I am able to play another song and then those two song play at same time.
I've red couple articles about music players and services on android and I thought that this was correct approach (thinking that onDestroy will be called when OS is out of memory).
Any ideas how I can re-implement my app workflow so that I will work as expected?
EDIT
At first I thought that "Don't keep activities" under developer options is a problem, but problem is still there even if I uncheck it.
And if some code from my service is needed please say I will edit my question (there's a lot of code and I'm not sure which part is important for this issue)
SOLUTION:
startForeground(<notification id>, <notification>);
to run service even if app gets killed. And when user dismisses the notification:
stopForeground(true);
stopSelf();
More about startForeground here.
It's not obvious, but you should start a Thread that runs in background and use the service to control it's state.
A service isn't a thread that hold some run state like a thread. Unless it's an IntentService. (Correct me if I'm wrong)
While activity can (and probably will) get destroyed, your app will keep running.
Activity:
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, BackgroundService.class);
startService(intent);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
finishOnPause = true;
}
#Override
protected void onStop() {
super.onStop();
unbindService(mServiceConnection);
}
Service:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
ensureServiceThread();
if (intent != null) {
}
return START_NOT_STICKY;
}
private void ensureServiceThread() {
if (service_thread == null) {
service_thread = new ServiceThread(this);
service_thread.start();
}
}
#Override
public void onDestroy() {
stopServiceThread();
super.onDestroy();
}
private void stopServiceThread() {
if (service_thread != null) {
service_thread.interrupt();
service_thread = null;
}
}
And you should do your work inside the Thread.
If you need context, it's your Service.
If you need to do something on Main thread - create a Handler in Service.OnCreate and do a handler.postRunnable inside a worker thread safely.
What I would do is:
- Create a service
- Create a thread
- Create a media player inside a thread (if possible, otherwise on Service creation and pass it to thread)
- Inside a thread - continuously poll media player state
- On song finished send an intent to service that change track
- stop thread/service if needed.
Please take a look at my simple three-methods Service class that streams audio and play it directly.
public class StreamService extends Service {
private static final String TAG = "MyService";
String url;
MediaPlayer mp;
#Override
public void onCreate() {
Toast.makeText(this, "My Service Created", Toast.LENGTH_LONG).show();
Log.d(TAG, "onCreate");
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
#Override
public void onDestroy() {
Toast.makeText(this, "My Service Stopped", Toast.LENGTH_LONG).show();
Log.d(TAG, "onDestroy");
mp.stop();
}
#Override
public int onStartCommand(Intent intent, int flags, int startid) {
Toast.makeText(this, "My Service Started", Toast.LENGTH_LONG).show();
Log.d(TAG, "onStart");
url = intent.getExtras().getString("url");
try {
mp.setDataSource(url);
mp.prepare();
mp.start();
} catch(Exception e){}
return START_STICKY;
}
}
In my activity, I have two buttons to play/stop the media file:
The playButton execute this:
Intent i = new Intent(this, StreamService.class);
i.putExtra("my_mp3_url_string");
startService(i);
The stopButton execute this:
stopService(new Intent(this, StreamService.class));
Now, I have some questions:
how I can implement the pauseButton? I want to pause the media running in the Service
Does my way of playing/stopping the media/Service correct ? Is there any better way?
How I can (periodically) update my Activity's UI from my Service? do I need to add something?
I would recommend not using the lifetime of the Service as a way to start and stop playback. Using that approach will mean that every time you want to start a new stream, the code will be slowed down even more by having to bring up a new Service. You can save some time by just having the same Service play everything. Though that doesn't mean it should remain running all the time.
To accomplish that (and to be able to pause), you'll need to bind to the Service after it is started. With the bound Service, you'll be able to make calls to it - such as pause, play, stop, etc.
Here are some links that should help you with what you're looking for:
Using a Service with MediaPlayer
Binding to a Service
i have a background service on my android APP that is getting my GPS position and sending it to a remote db. It work's fine.
The problem is when i want to stop the service.... it doesn't stops :S. Also no exception or errors on logcat have appeared... it simply doesn't stops.
this is the code to start my srvice (with a button):
startService(new Intent(GPSLoc.this, MyService.class)); //enciendo el service
this is the code where I stop it (on the onactivityresult method):
stopService(new Intent(GPSLoc.this, MyService.class));
I have been debugged the app, and i checked that the stopService codeline has been called every time that i debugged it, but it doesn't stops......
i am sure that it's not stopped cause on my database i still recive gps positions from the emulator when i have press the button to stop the service.
what i am doing bad?
Have you implemented onDestroy()? If not, I believe that might be the solution - and you stop your Timer or whatever you're using to run the service within onDestroy().
A service can be stopped by calling its stopSelf() method, or by calling Context.stopService().
See this link for some more information.
i am sure that it's not stopped cause on my database i still recive gps positions from the emulator when i have press the button to stop the service.
You probably are not unregistering your LocationListener.
I had the same problem. I found that if the service has GoogleApiClient connected and still get location update, the stopService() has totally no effect, the service's industry() was not called.
To fix the problem, I created a function to stop the location service in the service code. Call the stopLocationService() from the activity, and then call stopService. Here is the code example:
public class myLocationService extends Service{
...
public void stopLocationUpdates() {
LocationService.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,this);
mGoogleApiClient.disconnect();
}
...
}
In activity,
{
...
if(mService != null && isBound) {
mService.stopLocationUpdates();
doUnbindService();
stopService(new Intent(this, myLocationService.class));
}
...
}
It's very common this situation where I need to stop my service before to finish the process. In some case is not enough with stopService(intent). You should have in mind the onDestroy() implement in my service. Example:
public class MyIntentService extends IntentService {
// Defines and instantiates an object for handling status updates.
private BroadcastNotifier mBroadcaster = null;
private int progress = 0; //THIS IS MY COUNTER FOR EXAMPLE!!!
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
progress = 0;
int tiempo_disponible = intent.getIntExtra("minutos_disponible", 0);
if (mBroadcaster == null){
mBroadcaster = new BroadcastNotifier(this);
}
// Broadcasts an Intent indicating that processing has started.
mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_STARTED);
mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_RUNNING);
while (progress < tiempo_disponible) {
progress++;
try {
Log.i(Constants.TAG, "Procesing " + progress);
mBroadcaster.notifyProgress(progress);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Reports that the feed retrieval is complete.
mBroadcaster.broadcastIntentWithState(Constants.STATE_ACTION_COMPLETE);
}
#Override
public void onDestroy() {
progress = 1000000; // WHITH THAT YOU FINISH THE CICLE IF tiempo_disponible NEVER IS MAYOR THAT 1000000, YOU CAN USE OTHER CONDITIONAL!!!!!!
super.onDestroy();
}
}
In this way, when you have stopped the service using stopService method also you will have stopped the process o counter.
public void stopService(){
context.stopService(intent);
LocalBroadcastManager.getInstance(context).unregisterReceiver(responseReceiver);
responseReceiver = null;
intent = null;
}
Take care!
#yaircarreno
If you are tracking GPS location, you probably used GoogleApiClient.
The concept is that the Service WILL NOT stop,
if an GoogleApiClient instance is still connected within it.
(Or any other issue that need to be destroyed / unregistered first)
So to make it works, implement onDestroy() within your service:
#Override
public void onDestroy()
{
// Unregistered or disconnect what you need to
// For example: mGoogleApiClient.disconnect();
super.onDestroy();
}
I have found the best way to stop a service is to make stop itself. This way you are sure it actually will stop and preserve data integrity. If you want to do it from outside (activity) I usually use a global static attribute.
Per example (Kotlin) if I have MyService, MyActivity and MyObject
My Object
object MyObject{
abort = false
}
MyService
override fun onHandleIntent(intent: Intent?) {
startForeground(id,notification)
for (i in range){
if (MyObject.abort) break
// RUN SOME CODE HERE
}
stopForeground(true)
stopSelf()
}
MyActivity
fun startService() {
startForegroundService(Intent(this, OptimizationService::class.java))
}
fun stopService() {
MyObject.abort = true
}
it could be perhaps that you are creating a new Intent everytime you call the stop service.
stopService(new Intent(GPSLoc.this, MyService.class));
perhaps try :
Intent intnet = new Intent(GPSLoc.this, MyService.class); // create el service
startService(intenet);
stopService(intent);
For those who want to send a request to server periodically, this is my solution. You should have this in your Activity or Fragment Activity
{
private static final Long UPDATE_LOCATION_TIME = 30 * 60 * 1000l; // 30 minute
private AlarmManager alarm;
private PendingIntent pIntent;
...
#Override
protected void onResume() {
super.onResume();
// Run background service in order to update users location
startUserLocationService();
Log.e(TAG, "onResume");
}
#Override
protected void onStop() {
super.onStop();
stopUserLocationService();
Log.e(TAG, "onStop");
}
private void startUserLocationService() {
Log.i(TAG, "Starting service...");
Intent intent = new Intent(MainFragmentHolder.this, ServiceUserLocation.class);
pIntent = PendingIntent.getService(this, 0, intent, 0);
alarm = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Calendar cal = Calendar.getInstance();
alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), UPDATE_LOCATION_TIME, pIntent);
}
private void stopUserLocationService() {
alarm.cancel(pIntent);
Intent intent = new Intent(MainFragmentHolder.this, ServiceUserLocation.class);
stopService(intent);
}
}
my problem solved by removing the added views to WindowManager ondestroy
public void onDestroy() {
isRunning = false;
super.onDestroy();
if (checkBox!=null) {
windowManager.removeView(getlayoutparm(fabsetting,fabrateus,fabexit,true));
windowManager.removeView(checkBox);
}
}
In my case the stopService is called with startService almost simultaneously so no service is there to be stopped. Try delay stopService for a few seconds. :)
#Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
if (mLocationManager != null) {
for (int i = 0; i < mLocationListeners.length; i++) {
try {
mLocationManager.removeUpdates(mLocationListeners[i]);
} catch (Exception ex) {
Log.d(TAG, "fail to remove location listners, ignore", ex);
}
}
}
}