iosswiftavfoundationavaudioplayer

iOS AVAudioPlayer audio won't play in background when Spotify is actively playing audio


I'm working on an iOS app that plays a short timer sound (TimerDone.wav) using AVAudioPlayer. The sound plays correctly in these scenarios:

  1. When the app is in the foreground.
  2. When the app is in the background and Spotify is not playing.
  3. When the app is in the background and Spotify is open but paused.
  4. However, the sound does NOT play when the app is in the background and Spotify is actively playing audio.

Things I've tried (tested on a real iPhone 13, iOS 18, Xcode 16):

  1. Ensuring that "Background Modes > Audio, AirPlay, and Picture in Picture" capability is enabled in Xcode.

  2. Using no options. This works for the first 3 scenarios above.

    try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])

  3. Using .duckOther option. This does NOT work for any of the scenarios above.

  4. 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)")
}

Solution

  • 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)")
    }