MediaSession is an Android framework component for managing media content playback and integrating with external devices. It provides a unified interface for interacting with Bluetooth headsets, headphones, Android Auto, and the system media center. According to Android Developers Guide, 2026, MediaSession replaces the deprecated RemoteControlClient and is required for players that sync with the system.
Key Takeaways
MediaSession is a system component in Android that allows an app to declare its media activity and receive control commands from external sources. When a user presses the Play button on a Bluetooth headset, the system passes this event to the active MediaSession, and the app responds through its Callback.
Before Android 5.0, RemoteControlClient was used for this purpose, but it did not provide sufficient flexibility and did not support modern scenarios — Android Auto, smartwatches, smart speakers. MediaSession was introduced in API 21 and became the de facto standard for all Android apps with audio and video playback.
According to Android Developer Documentation (2026), an app should create one MediaSession for each independent playback source. At any given time, only one session can be active — any new session automatically deactivates the previous one.
The combination of MediaSession with MediaBrowserService provides a complete media management cycle: the service provides a content tree (playlists, catalogs), and the session accepts navigation and playback commands. This is the architecture recommended by Google for music players, podcasts, and audiobooks.
MediaBrowserService runs as a foreground service with a notification, ensuring the app continues working even when the Activity is killed. This is critical for audio players that must keep playing when the app is minimized.
The session supports two types of interaction: it receives commands from MediaController (the client side) and broadcasts state through PlaybackState. MediaController can be in the same process or in a separate app — the system routes requests through SessionToken.
When a user calls the Android voice assistant and says “Play the next track”, the system finds the active MediaSession through its connection to MediaBrowserService and sends the ACTION_SKIP_TO_NEXT command. The app’s Callback receives the onSkipToNext() call and updates the PlaybackState.
Updating PlaybackState via setPlaybackState() immediately notifies all connected MediaController instances. The system media center, Bluetooth device, and Android Auto receive the update simultaneously — the delay does not exceed 50 ms under normal conditions.
PlaybackState contains key state flags: isPlaying, position, speed, available actions (play, pause, seek, stop). Without a properly populated PlaybackState, the system does not know which commands the app supports and does not send corresponding events.
Metadata (MediaMetadata) supplements the state with information about the current track — title, artist, album art URI. Android Auto and smartwatches use MediaMetadata to display information on screen. According to Google I/O 2024, proper MediaMetadata population increases app visibility in third-party launchers by 40%.
The MediaSession architecture consists of four interconnected components, each serving its own purpose. A developer needs to implement all four for full system integration.
The session is created in the onCreate method of a service or Activity by calling MediaSessionCompat(context, tag). After creation, setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS) must be called. In onDestroy, release() is called to free system resources.
Improper lifecycle management is one of the most common mistakes. If release() is not called, the session remains in the system, and the next app may receive a stale state. Android 13+ shows a Logcat warning for session leaks.
Basic integration starts with creating a session and implementing a Callback. It is recommended to use MediaSessionCompat from the AndroidX media library, which provides a unified API for all Android versions — from API 14 to 35.
class MusicService : Service() {
private lateinit var mediaSession: MediaSessionCompat
private lateinit var stateBuilder: PlaybackStateCompat.Builder
override fun onCreate() {
super.onCreate()
mediaSession = MediaSessionCompat(this, "MusicService")
mediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
mediaSession.setCallback(MediaSessionCallback())
updatePlaybackState(false)
}
private inner class MediaSessionCallback : MediaSessionCompat.Callback() {
override fun onPlay() {
updatePlaybackState(true)
}
override fun onPause() {
updatePlaybackState(false)
}
}
private fun updatePlaybackState(isPlaying: Boolean) {
stateBuilder = PlaybackStateCompat.Builder()
.setState(
if (isPlaying) PlaybackStateCompat.STATE_PLAYING
else PlaybackStateCompat.STATE_PAUSED,
AudioTrackCompat.CURRENT_POSITION_NOT_SET,
1.0f
)
.setActions(
PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
mediaSession.setPlaybackState(stateBuilder.build())
}
override fun onDestroy() {
mediaSession.release()
super.onDestroy()
}
}
In this example, a MediaSession is created with the tag MusicService and flags for handling media buttons. The Callback implements onPlay and onPause, updating the PlaybackState. The setActions method declares available actions, which the system displays on the lock screen and in the media center.
private fun setMetadata(title: String, artist: String) {
val metadata = MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 300000L)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUrl)
.build()
mediaSession.setMetadata(metadata)
}
Metadata should be updated on every track change. The system uses METADATA_KEY_TITLE and METADATA_KEY_ARTIST to display information on car Bluetooth displays and wearable devices. If the cover art URI is not set, the player will show a gray placeholder.
Bluetooth headsets send commands through the AVRCP 1.6+ profile. Android broadcasts these commands as ACTION_MEDIA_BUTTON intents, which are intercepted by MediaSession when the FLAG_HANDLES_MEDIA_BUTTONS flag is set.
When a user presses the Play button on Bluetooth headphones, the system creates a KeyEvent with code KEYCODE_MEDIA_PLAY, which is dispatched to the onMediaButtonEvent method of the Callback. If onPlay() is implemented in the Callback, the system calls it directly. A single press of the headset button sends KEYCODE_MEDIA_PLAY_PAUSE — the player should toggle the state.
Modern Bluetooth headphones can have up to 5 buttons: volume +/-, play/pause, next, previous. Each button generates its own KeyEvent, which must be properly handled by the Callback. A double press of play/pause is typically interpreted as skipping to the next track (ACTION_SKIP_TO_NEXT) on many headsets.
According to the Android Compatibility Definition Document (2026), all apps with media content must properly handle KEYCODE_MEDIA_PLAY_PAUSE. Ignoring this requirement leads to an automatic rating decrease in Google Play for the Music & Audio category.
Starting with Android 11, the system media center (Media Control Panel) displays up to 5 recent MediaSession instances in the notification shade. The user can control the player without opening the app. Proper display requires implementing MediaBrowserService and correctly populating PlaybackState.
The Media Control Panel shows: track title, artist, cover art (from MediaMetadata), and control buttons (from available PlaybackState actions). If the app does not update PlaybackState at least once every 10 seconds during active playback, the media center hides the session from the panel.
private fun startForegroundService() {
val notification = NotificationCompat.Builder(this, "media_channel")
.setSmallIcon(R.drawable.ic_play)
.setContentTitle("Now Playing")
.setContentText(currentTrackTitle)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
)
.build()
startForeground(1001, notification)
}
MediaStyle notification links to MediaSession via the sessionToken and displays standard media buttons. Without MediaStyle, the notification will appear as a regular alert without control buttons. Android 13+ requires explicit POST_NOTIFICATIONS permission for display.
Frequently Asked Questions
Technically yes, but only one session is considered active at any given moment. When creating a new session without calling setActive(true), the previous one remains active. It is recommended to have one session per app or one per independent audio source with active state switching.
MediaSession does not manage Audio Focus automatically — this is a separate mechanism. When receiving an onPlay command, the developer must independently request AudioFocus through AudioManager, and when focus is lost, pause playback through the session.
Check that the FLAG_HANDLES_MEDIA_BUTTONS and FLAG_HANDLES_TRANSPORT_CONTROLS flags are set. Also make sure the session is active (setActive(true)). On Android 12+, media buttons only work through MediaSession — the old registerMediaButtonEventReceiver is not supported.
For basic button handling — no. But for integration with Android Auto, Wear OS, and the system media center, MediaBrowserService is required. Google recommends implementing MediaBrowserService in all apps with long-duration audio playback.
Use the adb shell dumpsys media_session command to view active sessions, their Callbacks, and PlaybackState. This utility shows all registered sessions with their tags, activity status, and last known state — a convenient debugging tool.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also