The Problem
I was building a Subsonic-based music streaming app. Background playback worked fine — until I noticed something odd:
- Play a song, send the app to the background
- Open YouTube and play a video (music stops — expected)
- Close the YouTube video
- Come back to the app: the pause button is showing, but no audio is playing
The UI says “playing” while the actual audio is paused. Tapping the button toggles it to play, then you have to tap again. Broken UX.
Why This Happens
When another app claims the audio session, iOS automatically pauses your AVPlayer. But it doesn’t update your ViewModel — that’s your job. iOS does send a notification though: AVAudioSession.interruptionNotification. If you’re not listening, your isPlaying state drifts out of sync with reality.
The Fix
I added an interruption observer to the audio manager:
private func setupInterruptionObserver() {
interruptionObserver = NotificationCenter.default.addObserver(
forName: AVAudioSession.interruptionNotification,
object: AVAudioSession.sharedInstance(),
queue: .main
) { [weak self] notification in
guard let self,
let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else { return }
switch type {
case .began:
Task { @MainActor in self.onInterruptionBegan?() }
case .ended:
let options = (userInfo[AVAudioSessionInterruptionOptionKey] as? UInt)
.flatMap { AVAudioSession.InterruptionOptions(rawValue: $0) } ?? []
let shouldResume = options.contains(.shouldResume)
Task { @MainActor in self.onInterruptionEnded?(shouldResume) }
@unknown default:
break
}
}
}
The key is the shouldResume flag in the .ended event. When the system says “you can resume now,” you auto-play. The ViewModel side is straightforward:
func handleInterruptionBegan() {
guard isPlaying else { return }
isPlaying = false
}
func handleInterruptionEnded(shouldResume: Bool) {
if shouldResume {
isPlaying = true
audioManager?.togglePlayPause()
}
}
Result:
- YouTube takes over → play button flips to paused automatically
- YouTube stops → music resumes on its own
Exactly how Spotify and Apple Music behave.
One Gotcha
shouldResume isn’t always true. Phone calls typically send it, but some apps don’t set the flag when they release the audio session. When shouldResume is false, keep the paused state — don’t force playback.
Takeaway
If you’re building any app with AVPlayer audio, interruptionNotification isn’t optional. Without it, your UI lies to the user.