Audio won't play after app interrupted by phone call iOS

I have a problem in my SpriteKit game where audio using playSoundFileNamed(_ soundFile:, waitForCompletion:) will not play after the app is interrupted by a phone call. (I also use SKAudioNodes in my app which aren't affected but I really really really want to be able to use the SKAction playSoundFileNamed as well.)

Here's the gameScene.swift file from a stripped down SpriteKit game template which reproduces the problem. You just need to add an audio file to the project and call it "note"

I've attached the code that should reside in appDelegate to a toggle on/off button to simulate the phone call interruption.

That code 1) Stops AudioEngine then deactivates AVAudioSession - (normally in applicationWillResignActive) ... and 2) Activates AVAudioSession then Starts AudioEngine - (normally in applicationDidBecomeActive)

The error:


AVAudioSession.mm:1079:-[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.


This occurs when attempting to deactivate the audio session but only after a sound has been played at least once. to reproduce:

1) Run the app 2) toggle the engine off and on a few times. No error will occur. 3) Tap the playSoundFileNamed button 1 or more times to play the sound. 4) Wait for sound to stop 5) Wait some more to be sure


6) Tap Toggle Audio Engine button to stop the audioEngine and deactivate session - the error occurs.


7) Toggle the engine on and of a few times to see session activated, session deactivated, session activated printed in debug area - i.e. no errors reported.

8) Now with session active and engine running, playSoundFileNamed button will not play the sound anymore.


What am I doing wrong?


p.s. I also tried self.removeAllActions() before deactivating but it didn't help.


import SpriteKit
import AVFoundation

class GameScene: SKScene {
  var toggleAudioButton: SKLabelNode?
  var playSoundFileButton: SKLabelNode?
  var engineIsRunning = true

  override func didMove(to view: SKView) {
    toggleAudioButton = SKLabelNode(text: "toggle Audio Engine")
    toggleAudioButton?.position = CGPoint(x:20, y:100)
    toggleAudioButton?.name = "toggleAudioEngine"
    toggleAudioButton?.fontSize = 80
    addChild(toggleAudioButton!) 
   
    playSoundFileButton = SKLabelNode(text: "playSoundFileNamed")
    playSoundFileButton?.position = CGPoint(x: (toggleAudioButton?.frame.midX)!, y: (toggleAudioButton?.frame.midY)!-240)
    playSoundFileButton?.name = "playSoundFileNamed"
    playSoundFileButton?.fontSize = 80
    addChild(playSoundFileButton!)
  }

  override func touchesBegan(_ touches: Set, with event: UIEvent?) {
    if let touch = touches.first {
      let  location = touch.location(in: self)
      let  nodes = self.nodes(at: location)
     
      for spriteNode in nodes {
        if spriteNode.name == "toggleAudioEngine" {
          if engineIsRunning { // 1 stop engine, 2 deactivate session

            scene?.audioEngine.stop() // 1
            toggleAudioButton!.text = "engine is paused"
            engineIsRunning = !engineIsRunning
            do{
              // this is the line that fails when hit anytime after the playSoundFileButton has played a sound
              try AVAudioSession.sharedInstance().setActive(false) // 2
              print("session deactivated")
            }
            catch{
              print("DEACTIVATE SESSION FAILED")
            }
          }
          else { // 1 activate session/ 2 start engine
            do{
              try AVAudioSession.sharedInstance().setActive(true) // 1
              print("session activated")
            }
            catch{
              print("couldn't setActive = true")
            }
            do {
              try scene?.audioEngine.start() // 2
              toggleAudioButton!.text = "engine is running"
              engineIsRunning = !engineIsRunning
            }
            catch {
              //
            }
          }
        }
       
        if spriteNode.name == "playSoundFileNamed" {
          self.run(SKAction.playSoundFileNamed("note", waitForCompletion: false))
        }
      }
    }
  }
}

Hello,

We are also facing the same issue. Upon testing, we found that as soon as avaudiosession setactive becomes false, there is no way to get audio from Skaction playsoundfilenamed during the app.

Further testing found that, minimizing maximizing the app, twice, brings back the audio. Which is hope giving but not the solution. So whatever the system is doing during that two times of minimize and maximize needs to be done in ended of handleinterruption so as to get back audio. Or get back audio after setActive false of a AvAudioSession. SetActive true during app does not works only that after setting true , this minimize/maximize things work.

have tested Scenekit, Realitykit, Spritekit audio , 3rd party audioengine for our audio needs. Scenekit audio works with phone calls fine but has some memory issues which can be managed but not as ideal as spritekit. Realitykit audio is great but handling memory is scary sometimes and the above issue happens but can be removed by creating a new instance ( but memory increases with app minimize maximize which can be resolved with singleton then) which is not working with spritekit as some allocation of playsoundfilenamed is persitent within the app. Does not deallocates. 3rd party audioengines crash sometimes on phone call. Atleast Apple APi's dont crash. Hopefully this gets resolved and will be a great thing if there are no more issues also.

Audio won't play after app interrupted by phone call iOS
 
 
Q