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:
- Call
prepareAsync()
rather thanprepare()
, and - Implement a
MediaPlayer.OnPreparedListener
in order to be notified when the preparation is complete and you can start playing.
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: