Play media in the background

You can play media in the background even when your application is not on screen, for example, while the user is interacting with other applications.

To do so, you embed the MediaPlayer in a MediaBrowserServiceCompat service and have it interact with a MediaBrowserCompat in another activity.

Be cautious with implementing this client and server setup. There are expectations about how a player running in a background service interacts with the rest of the system. If your application does not fulfill those expectations, the user may have a bad experience. See Building an Audio App for details.

This page describes special instructions for managing a MediaPlayer when it you implement it inside a service.

Run asynchronously

Like an Activity, all work in a Service is done in a single thread by default. In fact, when you run an activity and a service from the same application, they use the same thread (the "main thread") by default.

Services must process incoming intents quickly and never perform lengthy computations when responding to them. You must perform any heavy work or blocking calls asynchronously: either from another thread you implement yourself, or using the framework's many facilities for asynchronous processing.

For example, when you use MediaPlayer from your main thread, you should:

For example:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

Handle asynchronous errors

On synchronous operations, errors are signaled with an exception or an error code. When you use asynchronous resources, however, you should notify your application of errors appropriately. In the case of a MediaPlayer, you implement a MediaPlayer.OnErrorListener and set it in your MediaPlayer instance:

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

When an error occurs, the MediaPlayer moves to the Error state. must reset it before you can use it again. For details, see the full state diagram for the MediaPlayer class.

Use wake locks

When you play or stream music in the background, you must use wake locks to prevent the system from interfering with your playback, for example, by having the device go to sleep.

A wake lock is a signal to the system that your application is using features that should stay available even when the phone is idle.

To ensure that the CPU continues running while your MediaPlayer is playing, call the setWakeMode() method when you initialize your MediaPlayer. The MediaPlayer holds the specified lock while playing and releases the lock when paused or stopped:

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

However, the wake lock acquired in this example ensures only that the CPU remains awake. If you are streaming media over the network and you are using Wi-Fi, you probably want to hold a WifiLock as well, which you must acquire and release manually. So, when you start preparing the MediaPlayer with the remote URL, you should create and acquire the Wi-Fi lock.

For example:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

When you pause or stop your media, or when you no longer need the network, you should release the lock:

Kotlin

wifiLock.release()

Java

wifiLock.release();

Perform cleanup

As mentioned earlier, a MediaPlayer object can consume a significant amount of system resources, so you should keep it only for as long as you need and call release() when you are done with it. It's important to call this cleanup method explicitly rather than rely on system garbage collection because it might take some time before the garbage collector reclaims the MediaPlayer, as it's only sensitive to memory needs and not to shortage of other media-related resources. So, in the case when you're using a service, you should always override the onDestroy() method to make sure you are releasing the MediaPlayer:

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

You should always look for other opportunities to release your MediaPlayer as well, apart from releasing it when being shut down. For example, if you expect not to be able to play media for an extended period of time (after losing audio focus, for example), you should definitely release your existing MediaPlayer and create it again later. On the other hand, if you only expect to stop playback for a very short time, you should probably hold on to your MediaPlayer to avoid the overhead of creating and preparing it again.

Learn more

Jetpack Media3 is the recommended solution for media playback in your app. Read more about it.

These pages cover topics relating to recording, storing, and playing back audio and video: