There are some seemingly similar questions already on SO, but after hours of searching and experimenting with my code, I have been unable to find a clear answer. The closest thing I could find was this answer which alludes to a "known bug" 4 years ago, but does not elaborate on how to address it.
I have an audioPlayer class that is playing an audio file with AVAudioPlayer and listening for the AVAudioSessionDelegate
methods beginInterruption
and endInterruption
. I know that endInterruption
is not guaranteed so I store a boolean in beginInterruption
and handle restarting the audio playback in applicationDidBecomeActive
.
This all works perfectly as expected if the phone receives a call and the user declines it or lets it go to voicemail. As soon as my app goes back into the active state, audio playback resumes.
Here's where I'm getting weird behavior: If the user answers the call, once they hang up everything seems to work as expected but no sound comes out through either the headphones or the speakers.
I can verify that the audio is technically playing by printing its volume and currentTime every second, but the sound isn't coming out.
If I wait for 20-40 seconds, the audio suddenly cuts in and becomes audible, as if it had been silently playing in the background.
After more debugging, I noticed that AVAudioSession.sharedInstance().secondaryAudioShouldBeSilencedHint
remains true
for these 20-40 seconds of silence, before suddenly changing to false
and letting the audio play.
I subscribed to the AVAudioSessionSilenceSecondaryAudioHintNotification
to see if I could detect this change, but it never gets called, even when .secondaryAudioShouldBeSilencedHint
changes from true
to false
.
I even tried explicitly setting AVAudioSession.sharedInstance().setActive(true)
in the method that resumes playback of the audio, but the behavior did not change.
Lastly, I tried setting a timer to delay the resume for 10 seconds after applicationDidBecomeActive
, but the behavior did not change.
So, why does it seem that the phone call is not relinquishing control of the audio session back to my app?
Thanks for taking a look!
Code:
AVAudioSession setup in init()
:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.handleAudioHintChange), name: AVAudioSessionSilenceSecondaryAudioHintNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.handleAudioInterruption), name: AVAudioSessionInterruptionNotification, object: nil)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true, withOptions: .NotifyOthersOnDeactivation)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
Notification handlers:
///////////////////////////////////
// This never gets called :( //////
func handleAudioHintChange(notification: NSNotification) {
print("audio hint changed")
}
///////////////////////////////////
func handleAudioInterruption(notification: NSNotification) {
if notification.name != AVAudioSessionInterruptionNotification || notification.userInfo == nil{
return
}
if let typeKey = notification.userInfo [AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeKey) {
switch type {
case .Began:
print("Audio Interruption Began")
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"wasInterrupted")
case .Ended:
print("Audio Interuption Ended")
}
}
}
App Delegate:
func applicationDidBecomeActive(application: UIApplication) {
if(NSUserDefaults.standardUserDefaults().boolForKey("wasInterrupted")) {
audioPlayer.resumeAudioFromInterruption()
}
}
Restart function:
// This works great if the phone call is declined
func resumeAudioFromInterruption() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("wasInterrupted")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
thisFunctionPlaysMyAudio()
}