Mastering Android MediaSession: Build Robust Background Music Playback
This article explains how to implement a reliable Android music app by leveraging MediaSession for UI‑service synchronization, handling background playback, MediaButton events, AudioFocus, Wi‑Fi and WakeLocks, custom notifications, and compatibility across Android versions.
Introduction
Implementing a daily‑listening feature in an Android app is equivalent to building a generic music playback app. The core requirements are UI‑to‑background synchronization, background playback, audio focus handling, hardware button support, power management, notification handling, progress tracking, and compatibility with older Android versions.
MediaSession Framework Overview
MediaSession (API 21+) provides a bridge between UI and a background playback Service. It consists of:
MediaSession – holds a MediaSession.Token for pairing with a MediaController and a MediaSession.Callback to receive commands.
MediaController – receives state updates via MediaController.Callback and sends commands via MediaController.TransportControls.
Typical flow: UI → TransportControls → MediaSession.Callback → player action → update PlaybackState → MediaController.Callback → UI refresh.
Background Playback Service
Use a Service that extends MediaBrowserService (or MediaBrowserServiceCompat) and declare the service intent filter in AndroidManifest.xml:
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>Typical Service implementation:
public class MediaPlaybackService extends MediaBrowserService {
@Override
public void onCreate() {
super.onCreate();
// 1. Initialize MediaSession
// 2. Set MediaSession.Callback
// 3. Enable MediaButton & TransportControls
// 4. Initialize PlaybackState
// 5. Set SessionToken
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
// Perform client‑side access checks
}
@Override
public void onLoadChildren(String parentMediaId, Result<List<MediaItem>> result) {
// Return media items based on permissions
}
}The UI binds to the Service via MediaBrowserCompat to obtain the session token and create a MediaControllerCompat:
public class MediaPlayerActivity extends AppCompatActivity {
private MediaBrowserCompat mMediaBrowser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, MediaPlaybackService.class),
mConnectionCallbacks, null);
}
@Override
public void onStart() {
super.onStart();
mMediaBrowser.connect();
}
@Override
public void onStop() {
super.onStop();
if (MediaControllerCompat.getMediaController(this) != null) {
MediaControllerCompat.getMediaController(this).unregisterCallback(controllerCallback);
}
mMediaBrowser.disconnect();
}
}To keep the Service alive while audio plays, start it with startService when playback begins and bind to it for UI interaction. The Service is destroyed only when both the start flag and bind count are cleared.
MediaButton Handling
Register MediaButtonReceiver in the manifest to receive hardware button events:
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>For API 21+ the system forwards events directly to MediaSession.Callback.onMediaButtonEvent. For pre‑21 devices forward the intent manually:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent);
return super.onStartCommand(intent, flags, startId);
}Handling ACTION_AUDIO_BECOMING_NOISY
Register a BroadcastReceiver for AudioManager.ACTION_AUDIO_BECOMING_NOISY and pause playback when headphones are unplugged:
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private BecomingNoisyReceiver noisyReceiver = new BecomingNoisyReceiver();
MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
registerReceiver(noisyReceiver, intentFilter);
}
@Override
public void onStop() {
unregisterReceiver(noisyReceiver);
}
};WifiLock & WakeLock
Acquire a WifiLock during streaming to prevent Wi‑Fi from sleeping, and release it on pause/stop:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
@Override
public void onPlay() { wifiLock.acquire(); }
@Override
public void onPause() { wifiLock.release(); }
};Keep the CPU awake by setting the MediaPlayer wake mode or using a PowerManager.WakeLock:
MediaPlayer player = new MediaPlayer();
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MediaPlayer.class.getName());
wakeLock.setReferenceCounted(false);
wakeLock.acquire();Foreground Notification
Run the playback Service as a foreground Service with a non‑dismissable notification. Two approaches are common:
Use the built‑in MediaStyle notification (API 21+), which provides expanded controls.
Implement a custom layout when device manufacturers modify the default style.
Example using MediaStyle:
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat metadata = controller.getMetadata();
MediaDescriptionCompat desc = metadata.getDescription();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContentTitle(desc.getTitle())
.setContentText(desc.getSubtitle())
.setSubText(desc.getDescription())
.setLargeIcon(desc.getIconBitmap())
.setContentIntent(controller.getSessionActivity())
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_STOP))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(new NotificationCompat.Action(R.drawable.pause, getString(R.string.pause),
MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE)))
.setStyle(new NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(0)
.setShowCancelButton(true)
.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_STOP)));
startForeground(NOTIFICATION_ID, builder.build());AudioFocus Management
Request audio focus before playback and handle focus change callbacks: AudioManager.STREAM_MUSIC – typical stream type for music. AUDIOFOCUS_GAIN / AUDIOFOCUS_LOSS – long‑term focus for continuous playback. AUDIOFOCUS_GAIN_TRANSIENT / AUDIOFOCUS_LOSS_TRANSIENT – short‑term focus (e.g., pronunciation clips). AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK / AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK – brief focus where other audio should lower volume.
Implement AudioManager.OnAudioFocusChangeListener to pause, duck, or stop playback based on the focus change.
Playback Progress Updates
Because UI does not have direct access to the player, update progress via PlaybackState. Set state with:
playbackStateBuilder.setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());UI can compute the current position:
long currentPosition = ((SystemClock.elapsedRealtime() - playbackState.getLastPositionUpdateTime())
* playbackState.getPlaybackSpeed()) + playbackState.getPosition();Compatibility
Use the support library for backward compatibility:
com.android.support:support-media-compat:24.2.0 MediaBrowserCompatworks with MediaBrowserService and MediaBrowserServiceCompat, allowing the same codebase to run on API 21+ and older devices.
References
https://github.com/googlesamples/android-UniversalMusicPlayer
https://developer.android.com/guide/topics/media-apps/index.html
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
