I'm working on an exercise app that needs to be able to play sounds when the app goes into the background and also when the screen is locked. I also need to allow sounds from other apps to play (such as music) while also allowing my sound effects to come through.
I had this working in iOS 9 by using AVAudioSessionCategoryAmbient, but as soon as my app becomes inactive or the screen locks, the sounds stop.
Here's a simplified version of my code in a demo app:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var session: AVAudioSession = AVAudioSession.sharedInstance()
var timer = Timer()
let countdownSoundFile = "154953__keykrusher__microwave-beep"
let countdownSoundExt = "wav"
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
activateAudio()
}
func activateAudio() {
_ = try? session.setCategory(AVAudioSessionCategoryAmbient, with: [])
_ = try? session.setActive(true, with: [])
}
@IBAction func play() {
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.playSound), userInfo: nil, repeats: true)
}
func playSound() {
if let soundURL = Bundle.main.url(forResource: countdownSoundFile, withExtension: countdownSoundExt) {
do {
print("sound playing")
try audioPlayer = AVAudioPlayer(contentsOf: soundURL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("no sound found")
}
}
}
}
I also checked the Audio, Airplay, and Picture in Picture
capability in Background Modes, which added the Required background mode to my plist.
I'm testing this on a device, and as soon as I lock the screen or press the home button, my sounds stop, and they resume once my app becomes active again.
I found a workaround by using:
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
but this doesn't allow the volume of the sounds to be changed, and I also need to use AVSpeechSynthesizer, and this workaround doesn't work for that.
Any ideas on what I can do to make this work?
EDIT:
I changed the category line to _ = try? session.setCategory(AVAudioSessionCategoryPlayback, with: [.mixWithOthers])
and this allows music to be played while the app is running, however the sounds still stop when the screen locks or goes into the background. I need the sounds to continue to play in these scenarios.
EDIT:
As the answer pointed out, I cannot play sounds when my app is backgrounded, but using _ = try? session.setCategory(AVAudioSessionCategoryPlayback, with: [.mixWithOthers])
does allow sounds to play when the screen turns off and mixes with sounds (music) from other apps.
You cannot play in the background with an Ambient audio session category. Your category must be Playback.
To allow sounds from other apps to play, modify your Playback category with the Mixable option (.mixWithOthers
).
(Also keep in mind that you can change your category at any time. You could have it be Ambient most of the time but switch to Playback when you know you're about to go into the background so that you can keep playing.)
EDIT Also it occurs to me that there is another possible misconception that might need clearing up. You cannot (easily) start a new sound while in the background. All that background audio allows you to do is continue playing the current sound when your app goes into the background. Once that sound stops (in the background), your app suspends and that's the end of that.