user9924189
user9924189

Reputation: 101

Stop MediaPlayer service when app goes in Background

I have a media player service that plays music in the background of my app throughout activities like :

public class Music extends Service {
MediaPlayer player;

@Override
public IBinder onBind(Intent intent) {
    return null;
}

public void onCreate() {
    player = MediaPlayer.create(this, R.raw.music);
    player.setLooping(true);
}

public int onStartCommand(Intent intent, int flags, int startId) {
    player.start();
    return Service.START_NOT_STICKY;
}


public void onDestroy() {
    player.stop();
    player.release();
    stopSelf();
    super.onDestroy();
}

The problem is that when the user changes app or goes to home screen of phone(app goes in the background) the music is still playing.

I have tried to stop it in onStop and onDestroy method but this stops the music when I change activities which is something that I don't want(I want the music to keep playing when user navigates through activities).

Update

I tried with broadcast:

I added

LocalBroadcastManager.getInstance(this).registerReceiver(StateReceiver, new IntentFilter("status"));

in onCreate of music Service and a method to receive events :

private BroadcastReceiver StateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String status = intent.getStringExtra("status");
        if (parseInt(String.valueOf(status)) == 0) {
            player.stop();
        }else{player.start();}

    }
};

And in Application class I did this:

public class app_class extends Application implements Application.ActivityLifecycleCallbacks {
private static int resumed;
private static int paused;

private static String currentActivity;

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    currentActivity = activity.getClass().getSimpleName();
}
public static String getCurrentActivity() {
    return currentActivity;
}
@Override
public void onActivityStarted(Activity activity) {
}
 @Override
public void onActivityResumed(Activity activity) {
    send_status(1);
}

@Override
public void onActivityPaused(Activity activity) {
    send_status(0);
}  
 private void send_status(int status_counter) {
    Intent intent = new Intent("status");
    intent.putExtra("status", String.valueOf(status_counter));
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
@Override
public void onActivityStopped(Activity activity) {      

}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}
@Override
public void onActivityDestroyed(Activity activity) {

}   

But the music wont resume

Upvotes: 2

Views: 3380

Answers (5)

Andrey Rankov
Andrey Rankov

Reputation: 2090

Can be less boilerplate with LifecycleObserver:

class App : Application(), LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onAppInBackground() {
        sendStatus(0)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onAppInForeground() {
        sendStatus(1)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onAppDestroyed() {
        sendStatus(2)
    }

    private fun sendStatus(statusCounter: Int) {
        val intent = Intent("status")
        intent.putExtra("status", statusCounter)
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
    }

    override fun onCreate() {
        super.onCreate()
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
    }
}

Add to build.gradle (app)

implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.1.0"

Upvotes: 1

María Teresa
María Teresa

Reputation: 37

Besides using the broadcast receiver as RoyalGriffin answered, the problem with the little interruption of the music can be solve by implementing LifecycleObserver interface. You can know when the app is in foreground or background, and that way, there is no need of handled the states of every activity.

Implement Application.ActivityLifecycleCallbacks in your app with his methods @OnLifecycleEvent(Lifecycle.Event.ON_STOP) and @OnLifecycleEvent(Lifecycle.Event.ON_START)

then add this line to the onCreate()

ProcessLifecycleOwner.get().getLifecycle().addObserver(this);

Your app_class would be something like this:

    public class app_class extends Application implements LifecycleObserver, 
    Application.ActivityLifecycleCallbacks{
    private static int resumed;
    private static int paused;

    private static String currentActivity;

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
        ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
    }

   @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    private void onAppBackgrounded() {
        Log.d(TAG, "App in background");
        send_status(0, this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    private void onAppForegrounded() {
        Log.d(TAG, "App in foreground");
        send_status(1, this);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle 
    savedInstanceState) {
        currentActivity = activity.getClass().getSimpleName();
    }
    public static String getCurrentActivity() {
        return currentActivity;
    }
    @Override
    public void onActivityStarted(Activity activity) {
    }
    @Override
    public void onActivityResumed(Activity activity) {
        //send_status(1);
    }

    @Override
    public void onActivityPaused(Activity activity) {
        //send_status(0);
    }
    private void send_status(int status_counter) {
        Intent intent = new Intent("status");
        intent.putExtra("status", String.valueOf(status_counter));
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
    @Override
    public void onActivityStopped(Activity activity) {

    }
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle 
    outState) {

    }
    @Override
    public void onActivityDestroyed(Activity activity) {

    }
}

Upvotes: 0

RoyalGriffin
RoyalGriffin

Reputation: 2007

You need to prepare the player again after stopping it. If you want to stop the playback when your app goes in background, and start the media from the beginning when your app comes into foreground. Just modify your BroadcastReceiver code to this:

private BroadcastReceiver StateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String status = intent.getStringExtra("status");
            if (parseInt(String.valueOf(status)) == 0) {
                player.stop();
            } else {
                player = MediaPlayer.create(this, R.raw.music);
                player.setLooping(true);
                player.start();
            }

        }
    };

However, if you want to pause the playback and resume it from where you left, then make the following changes: In your Application class, in onActivityDestroyed():

@Override
public void onActivityDestroyed(Activity activity) {
    send_status(2);
}

and in the BroadcastReceiver:

private BroadcastReceiver StateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String status = intent.getStringExtra("status");
            if (parseInt(String.valueOf(status)) == 0) {
                player.pause();  //When app is in background and not killed, we just want to pause the player and not want to lose the current state.
            } else if (parseInt(String.valueOf(status)) == 1) {
                if (player != null)
                    player.start();  // If the player was created and wasn't stopped, it won't be null, and the playback will be resumed from where we left of.
                else {
                    // If the player was stopped, we need to prepare it once again.
                    player = MediaPlayer.create(this, R.raw.music);
                    player.setLooping(true);
                    player.start();
                }
            } else if(player != null){
                player.stop();
                player.release();
            }

        }
    };

Also, have a look at the MediaPlayer states.

Upvotes: 4

Abhijeet Kumar
Abhijeet Kumar

Reputation: 343

You could add this in your application class to check whether your app is in the foreground or not.

public class MyLifecycleHandler implements Application.ActivityLifecycleCallbacks {
    private static int resumed;
    private static int paused;
    private static int started;
    private static int stopped;

    private static String currentActivity;

    public static String getCurrentActivity() {
        return currentActivity;
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        currentActivity = activity.getClass().getSimpleName();
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
        ++resumed;
    }

    @Override
    public void onActivityPaused(Activity activity) {
        ++paused;
        android.util.Log.w("test", "application is in foreground: " + (resumed > paused));

        // send broadcast from here to the service
        sendBroadcast()
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
        ++started;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        ++stopped;
        android.util.Log.w("test", "application is visible: " + (started > stopped));
    }

    public static boolean isApplicationVisible() {
        return started > stopped;
    }

    public static boolean isApplicationInForeground() {
        return resumed > paused;
    }
}

Add this to your application like this :

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        registerActivityLifecycleCallbacks(new MyLifecycleHandler());
    }
}

On receiving the broadcast in service you can stop mediaplayer.

Edit

You need to change your application class to this :

public class app_class extends Application implements Application.ActivityLifecycleCallbacks {
    private static int resumed;
    private static int paused;

    private static String currentActivity;

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        currentActivity = activity.getClass().getSimpleName();
    }
    public static String getCurrentActivity() {
        return currentActivity;
    }
    @Override
    public void onActivityStarted(Activity activity) {
    }
    @Override
    public void onActivityResumed(Activity activity) {
        send_status(1);
    }

    @Override
    public void onActivityPaused(Activity activity) {
        send_status(0);
    }
    private void send_status(int status_counter) {
        Intent intent = new Intent("status");
        intent.putExtra("status", String.valueOf(status_counter));
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
    @Override
    public void onActivityStopped(Activity activity) {

    }
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }
    @Override
    public void onActivityDestroyed(Activity activity) {

    }
}

Upvotes: 1

user7616371
user7616371

Reputation:

Have you implemented onDestroy()? If not, I believe that might be the solution - and you stop your Player 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().

Upvotes: 0

Related Questions