Implementing Background Audio Playback in Mobile Apps
Background playback isn't one line of code. iOS and Android by default stop audio when app goes background. For music, podcasts, or game audio to continue, properly configure AudioSession/AudioFocus, register Background Mode, handle system interruptions. Separate implementation for each platform.
iOS: AVAudioSession and Background Mode
Three mandatory steps:
1. Info.plist Background Mode. Add UIBackgroundModes → audio. Without this, iOS kills audio seconds after background.
2. AVAudioSession category .playback. Only this category allows playback on locked screen and background. .ambient mutes on lock—common mistake.
func configureAudioSession() {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(
.playback,
mode: .default,
options: [.allowBluetooth, .allowAirPlay, .mixWithOthers] // remove .mixWithOthers if you need to interrupt others
)
try session.setActive(true)
} catch {
print("AudioSession error: \(error)")
// don't ignore — without active session, background playback doesn't work
}
}
3. Handle interruptions. Incoming call—iOS deactivates AVAudioSession. After—must explicitly restore:
NotificationCenter.default.addObserver(
forName: AVAudioSession.interruptionNotification,
object: nil,
queue: .main
) { notification in
guard let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
switch type {
case .began:
audioPlayer.pause()
case .ended:
let shouldResume = (notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt)
.flatMap { AVAudioSession.InterruptionOptions(rawValue: $0).contains(.shouldResume) } ?? false
if shouldResume {
try? AVAudioSession.sharedInstance().setActive(true)
audioPlayer.play()
}
@unknown default: break
}
}
Additionally—AVAudioSession.routeChangeNotification: headphones disconnected → pause (standard behavior).
Android: Foreground Service and Audio Focus
Android background playback requires Foreground Service with mediaPlayback type. Without it, OS kills process after minutes.
class AudioPlaybackService : Service() {
private lateinit var player: ExoPlayer
private lateinit var mediaSession: MediaSessionCompat
override fun onCreate() {
super.onCreate()
player = ExoPlayer.Builder(this).build()
// Foreground notification — mandatory for Foreground Service
val notification = buildMediaNotification()
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
mediaSession = MediaSessionCompat(this, "AudioService")
mediaSession.setCallback(mediaSessionCallback)
mediaSession.isActive = true
}
}
Starting Android 14 (API 34)—FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK mandatory in manifest and in startForeground. Without explicit type—SecurityException.
Audio Focus. Request before playback:
val audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> player.pause()
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> player.pause()
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> player.volume = 0.3f
AudioManager.AUDIOFOCUS_GAIN -> {
player.volume = 1.0f
player.play()
}
}
}
.build()
val result = audioManager.requestAudioFocus(audioFocusRequest)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
player.play()
}
MediaSession and Lock Screen Controls
Without MediaSession (Android) / MPRemoteCommandCenter (iOS), Lock Screen and earbud control doesn't work.
Android (Jetpack Media3 / MediaSessionCompat): MediaNotification.Provider creates notification with previous/pause/next buttons. MediaSession.Callback handles commands. MediaBrowserServiceCompat lets Android Auto connect.
iOS (MPNowPlayingInfoCenter + MPRemoteCommandCenter): update nowPlayingInfo on each track change. MPRemoteCommandCenter.shared().playCommand.addTarget—Play handler. Without periodically updating MPNowPlayingInfoPropertyElapsedPlaybackTime—Lock Screen progress bar inaccurate.
Typical Mistakes
No audio after incoming call on iOS. Forgotten AVAudioSession.setActive(true) after interruption end. Add to InterruptionType.ended handler.
Notification disappears — service gets killed. On Android, when stopping playback, either call stopForeground(false) (remove foreground, keep notification), or stay in PAUSED state correctly. Calling stopForeground(true)—notification gone, OS kills service soon.
Bluetooth earbuds — no sound. iOS: options: .allowBluetooth in setCategory. Android: AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)—without, Bluetooth may switch to SCO mode (phone quality).
Timelines
2–3 working days for standard background playback with Lock Screen controls. Android Auto / CarPlay integration or complex lifecycle with multiple content types—up to 5 days. Cost calculated individually.







