I'm working on an iOS app that plays a short timer sound (TimerDone.wav) using AVAudioPlayer. The sound plays correctly in these scenarios:
Things I've tried (tested on a real iPhone 13, iOS 18, Xcode 16):
Ensuring that "Background Modes > Audio, AirPlay, and Picture in Picture" capability is enabled in Xcode.
Using no options. This works for the first 3 scenarios above.
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
Using .duckOther
option. This does NOT work for any of the scenarios above.
Using .mixWithOthers
option. This does NOT work either for any of the scenarios above.
Current code snippets where the first 3 scenarios work:
// This do-catch is called in viewDidLoad.
do {
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default,
options: [])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
NSLog("Error activating audio session: \(error.localizedDescription)")
}
// This is called in another function whenever timer sets off.
guard let url = Bundle.main.url(forResource: "TimerDone", withExtension: "wav") else { return }
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("Audio playback error: \(error.localizedDescription)")
}
The issue was that my app was being suspended by iOS in the background before it could prepare and play the sound. To fix this, I used a background task to request execution time from the system while preparing and playing the audio.
var bgTask: UIBackgroundTaskIdentifier = .invalid
func beginBackgroundTask() {
bgTask = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(self.bgTask)
self.bgTask = .invalid
}
}
Call beginBackgroundTask
right before your code to play the audio.
beginBackgroundTask()
guard let url = Bundle.main.url(forResource: "TimerDone", withExtension: "wav") else { return }
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("Audio playback error: \(error.localizedDescription)")
}