Mobile Development 14 min read

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.

Liulishuo Tech Team
Liulishuo Tech Team
Liulishuo Tech Team
Mastering Android MediaSession: Build Robust Background Music Playback

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 → TransportControlsMediaSession.Callback → player action → update PlaybackStateMediaController.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
MediaBrowserCompat

works 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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidMediaSessionBackground PlaybackNotificationAudioFocusWifiLock
Liulishuo Tech Team
Written by

Liulishuo Tech Team

Help everyone become a global citizen!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.