When I shipped a small app, built as an indie developer, that plays meditation and study audio, the very first bug report read: "after a phone call, the sound doesn't come back." It is natural for audio to stop when a call comes in mid-playback, but even after the call ended, the app stayed paused, with the on-screen play button frozen in its "playing" look.
The cause was that I was not handling AVAudioSession interruptions at all. Playback was left entirely to AVAudioPlayer, and my code was not listening to the OS telling it "I've interrupted you now" and "you may resume." I learned the hard way that the quality of an audio app is decided less by whether the features run and more by how carefully it answers this kind of interruption.
This article walks through handling interruptions from calls and Siri, and route changes from plugging and unplugging headphones, in a form you can drop straight into the native Swift that Rork Max generates.
First, decide the audio session category
Before anything else, declare to the OS what kind of audio app this is. Skip it and your sound may vanish with the silent switch, or you may needlessly stop another app's music.
import AVFoundation
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
// A playback-first app: plays regardless of the silent switch
try session.setCategory(.playback, mode: .default)
try session.setActive(true)
} catch {
print("Audio session setup failed: \(error)")
}
}.playback declares "this app's sound is the content itself," letting it play while locked or in the background. Conversely, if you only want to layer a short effect over another app's music, you would choose .ambient. Choosing the right declaration up front is the foundation for every later behavior.
Subscribe to interruption notifications
Interruptions arrive via AVAudioSession.interruptionNotification. There are two phases — .began and .ended — and the end carries a hint about whether you may resume on your own.
final class PlaybackController {
private var wasPlayingBeforeInterruption = false
func observeInterruptions() {
NotificationCenter.default.addObserver(
self, selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification, object: nil)
}
@objc private func handleInterruption(_ note: Notification) {
guard let info = note.userInfo,
let raw = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: raw) else { return }
switch type {
case .began:
// A call or Siri broke in. Remember the state.
wasPlayingBeforeInterruption = player.isPlaying
player.pause()
case .ended:
guard let optsRaw = info[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optsRaw)
// Resume only if shouldResume is set AND we were playing before
if options.contains(.shouldResume), wasPlayingBeforeInterruption {
try? AVAudioSession.sharedInstance().setActive(true)
player.play()
}
@unknown default:
break
}
}
}The crux here is how you treat shouldResume. The OS distinguishes "interruptions you may resume from" and "interruptions you should not." For example, when another music app comes to the front by the user's action, you should not grab playback back on your own. Resuming when shouldResume is not set goes against the user's intent. Check whether you were playing before the interruption as well, and only restore quietly when both hold — that is the well-behaved implementation.