Create a music player notification on Android

A music player notification is the playback panel people see in the shade or on the lock screen. It should identify the current track and offer clear play, pause, previous and next actions without reopening the app. Album art and accurate track details make the panel useful at a glance.

Creating a music player notification in Android works best when the notification is a view of real playback state. The player, media session and foreground service must stay synchronised; otherwise buttons can show the wrong action or controls may disappear during background audio. Permission handling also changes across recent Android releases.

Prepare the playback stack

Use one playback owner, such as a Media3 ExoPlayer held by an injected service or repository. Pair it with a MediaSession so Android, Bluetooth devices, headphones and the notification all observe the same state. Avoid creating an ExoPlayer inside the activity, because the notification must survive the activity being closed.

Create a stable notification channel when the service starts, give the service one session reference and make its content intent open the player screen. Keep the channel ID and notification ID constant so updates replace the existing panel rather than stacking copies in the shade.

Request notification and service access

On Android 13 and later, request POST_NOTIFICATIONS when the user enables a visible music panel. A denied permission does not stop audio, but it can limit how the notification is presented, so explain the benefit and provide a route to system settings. Media-session notifications receive special treatment, so verify the behaviour on each supported OS version.

On Android 14 and later, a media foreground service needs both FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PLAYBACK. Declare the playback type in the manifest and start the service only in response to a valid playback request rather than from an unexplained background timer.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<service
    android:name=".PlaybackService"
    android:exported="false"
    android:foregroundServiceType="mediaPlayback" />

Call startForeground promptly after playback begins. If permission is denied, keep audio and controller state correct, avoid repeated prompts and direct the user to notification settings only when they choose to do so.

Connect tracks to a media session

Publish metadata and playback state through MediaSession whenever the player changes. Set the session token on the notification, then rebuild or update the notification from player callbacks for play, pause, seek, repeat and track changes. This gives every surface one consistent source of truth.

For records and artwork supplied by an API, populate that session data after the response arrives. This guide to Retrofit network calls shows how to move networking away from the UI thread; preserve the active track while loading data so a slow request cannot blank the controls.

Handle audio focus and route changes for wired headphones, Bluetooth speakers and car connections. When focus disappears temporarily, pause or duck according to the app’s playback policy; when it returns, resume only if the rules and the user’s current state allow it.

Build the playback controls

Use NotificationCompat.MediaStyle to place session artwork behind the controls and show three compact actions. Send each action to the playback service through an immutable, update-current PendingIntent, then let the service change the player rather than letting the notification mutate UI state directly.

val playPauseIcon =
    if (player.isPlaying) R.drawable.ic_pause else R.drawable.ic_play
val playPauseLabel =
    getString(if (player.isPlaying) R.string.pause else R.string.play)

val notification = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_music_note)
    .setContentTitle(
        player.mediaMetadata.title ?: getString(R.string.unknown_track)
    )
    .setContentText(
        player.mediaMetadata.artist ?: getString(R.string.unknown_artist)
    )
    .setContentIntent(openPlayerIntent())
    .addAction(
        R.drawable.ic_previous,
        getString(R.string.previous),
        actionIntent(PlaybackService.ACTION_PREVIOUS)
    )
    .addAction(
        playPauseIcon,
        playPauseLabel,
        actionIntent(PlaybackService.ACTION_PLAY_PAUSE)
    )
    .addAction(
        R.drawable.ic_next,
        getString(R.string.next),
        actionIntent(PlaybackService.ACTION_NEXT)
    )
    .setStyle(
        NotificationCompat.MediaStyle()
            .setMediaSession(mediaSession.sessionToken)
            .setShowActionsInCompactView(0, 1, 2)
    )
    .setOngoing(true)
    .setOnlyAlertOnce(true)
    .build()

Give every icon a content description and keep touch targets large enough for TalkBack and users with reduced motor control. Use a small monochrome status-bar icon, resilient colour choices for album artwork and plain labels such as “Play” rather than decorative wording. Remove previous or next controls when they cannot perform an action.

Keep playback alive and current

Call startForeground with the current panel as soon as playback starts, and keep the notification posted while audio or an active paused session remains. Observe the player and republish on each meaningful state change. Stop the foreground service and dismiss the panel only when playback ends and no useful paused session remains.

Cache small artwork and metadata, and do not poll the network while the panel is visible. These choices suit regional Australian connections, including fixed-wireless broadband outside major cities, where an unnecessary download can compete with streaming audio. Cached controls also keep pause and skip responsive when reception is poor.

Fit Australian listening habits

Australian listeners often meet the notification during a busy commute. A glanceable panel is useful on Sydney trains, Melbourne trams and Brisbane ferries, especially when one hand is holding a bag and the audio is coming from a Bluetooth headset. Prioritise the current title and primary controls over extra text.

Keep the interface in Australian English and use friendly, direct language. “Arvo” may appear in casual app copy for an afternoon listening session, but standard verbs such as “Play” and “Pause” are clearer on a small lock screen. Local artists such as Sia, Kylie Minogue, AC/DC and Tash Sultana make good sample tracks for realistic metadata.

Notification settings can also respect quiet hours, but schedules must handle Australia’s multiple time zones and daylight-saving changes in southern states. Store times in UTC, convert them with a time-zone database and never calculate tomorrow’s music reminder by adding a fixed 24 hours.

Verify behaviour on real devices

Start with fast unit tests that map each player state to the expected notification fields: visibility, title, artwork, compact actions and ongoing status. Then use instrumentation or UI automation to press every action, lock and unlock the device, rotate it, reconnect Bluetooth and repeat the checks after the task is removed from recent apps.

Test at least one Android 12 image without runtime notification access, then Android 13 and 14 with permission allowed and denied. Samsung Galaxy devices sold widely in Australia deserve a physical pass because their notification handling and background policies can differ from AOSP builds; also check large fonts, TalkBack and an album with missing artwork.

If the release also changes a web player or documentation page, end-to-end web checks can verify that companion journey. Keep notification assertions in Android instrumentation because a browser driver cannot open the system shade or inspect a foreground service.


Build the channel, service, session and actions in that order, then run the player with the screen locked before shipping. The finished panel should show the real track, respond immediately to every control and disappear cleanly when playback ends.