应用程序被iOS通话中断后,音频将无法播放

时间:2019-06-05 19:39:58

标签: ios swift audio avaudiosession avaudioengine

我的SpriteKit游戏出现问题,该应用程序被电话中断后,无法使用playSoundFileNamed(_ soundFile :, waitForCompletion :)播放音频。 (我还在我的应用程序中使用了未受影响的SKAudioNodes,但我确实非常希望能够使用SKAction playSoundFileNamed。)

这是精简版SpriteKit游戏模板中的gameScene.swift文件,它再现了该问题。您只需要将音频文件添加到项目中并命名为“ note”

我已经将应驻留在appDelegate中的代码附加到开/关切换按钮,以模拟电话中断。该代码1)停止AudioEngine,然后停用AVAudioSession-(通常在applicationWillResignActive中)... 2)激活AVAudioSession,然后启动AudioEngine-(通常在applicationDidBecomeActive中)

错误:

AVAudioSession.mm:1079:-[AVAudioSession setActive:withOptions:error:]:停用正在运行I / O的音频会话。在停用音频会话之前,应停止或暂停所有I / O。

在尝试停用音频会话时会发生这种情况,但仅在播放声音至少一次之后才发生。  再现:

1)运行应用  2)将引擎关闭再打开几次。不会发生错误。  3)点击playSoundFileNamed按钮1次或更多次以播放声音。  4)等待声音停止  5)再确认一下

6)点击切换音频引擎按钮以停止audioEngine并停用会话-  错误发生。

7)切换引擎几次,以查看调试区域中已打印的会话已激活,会话已禁用,会话已激活-即未报告任何错误。  8)现在,在会话处于活动状态并且引擎正在运行的情况下,playSoundFileNamed按钮将不再播放声音。

我在做什么错了?

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<UITouch>, 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))
    }
   }
  }
 }
}

2 个答案:

答案 0 :(得分:2)

让我在这里为您节省一些时间:playSoundFileNamed在理论上听起来很棒,如此美妙,您可能会说在您花了4年时间开发的应用程序中使用它,直到有一天您意识到它不仅会因为中断而完全崩溃,甚至会崩溃应用程序中最关键的中断,即您的IAP。不要做我仍然不确定是要用SKAudioNode还是AVPlayer来解决问题,但这可能取决于您的用例。只是不做。

如果您需要科学的证据,请创建一个应用程序并创建一个for循环,该循环使用playSoundFileNamed进行touchesBegan所需的任何操作,然后查看内存使用情况。该方法是一块泄漏的垃圾。

答案 1 :(得分:1)

为响应Mike Pandolfini不使用playSoundFileNamed的建议,我已将代码转换为仅使用SKAudioNodes。 (并将错误报告发送给苹果)。 然后,我发现其中一些SKAudioNodes在应用中断后也无法播放……我偶然发现了一个修复程序。 您需要让每个SKAudioNode在应用程序退出或从后台返回时都停止它们(即使它们不在播放中)。

(我现在在第一篇帖子中没有使用任何代码来停止音频引擎并停用会话)

然后,问题就变成了如何快速播放可能在其自身上方播放的相同声音。那就是playSoundFileNamed的优点。

1)SKAudioNode修复程序:

即预加载您的SKAudioNodes

let sound = SKAudioNode(fileNamed: "super-20")

在didMoveToView中添加它们

sound.autoplayLooped = false
addChild(sound)

添加willResignActive通知

notificationCenter.addObserver(self, selector:#selector(willResignActive), name:UIApplication.willResignActiveNotification, object: nil)

然后创建选择器的功能,该功能停止所有audioNode的播放:

@objc func willResignActive() {
  for node in self.children {
    if NSStringFromClass(type(of: node)) == “SKAudioNode" {
      node.run(SKAction.stop())
    }
  }
}

所有SKAudioNode现在在应用程序中断后都能可靠播放。

2)要复制playSoundFileNamed播放短时间快速重复声音或较长声音的功能,这些声音可能需要播放多次,因此可能会重叠,请为每种声音创建/预加载多个属性,并按以下方式使用它们:

let sound1 = SKAudioNode(fileNamed: "super-20")
let sound2 = SKAudioNode(fileNamed: "super-20")
let sound3 = SKAudioNode(fileNamed: "super-20")
let sound4 = SKAudioNode(fileNamed: "super-20")

var soundArray: [SKAudioNode] = []
var soundCounter: Int = 0

在didMoveToView

soundArray = [sound1, sound2, sound3, sound4]
for sound in soundArray {
  sound.autoplayLooped = false
  addChild(sound)
}

创建播放功能

func playFastSound(from array:[SKAudioNode], with counter:inout Int) {
counter += 1
if counter > array.count-1 {
  counter = 0
}
  array[counter].run(SKAction.play())
}

要播放声音,请将特定声音的数组及其计数器传递给播放功能。

playFastSound(from: soundArray, with: &soundCounter)