Звук не воспроизводится после прерывания приложения из-за телефонного звонка iOS - PullRequest
1 голос
/ 05 июня 2019

У меня есть проблема в моей игре SpriteKit, когда звук с использованием playSoundFileNamed (_ soundFile :, waitForCompletion :) не будет воспроизводиться после того, как приложение было прервано телефонным звонком. (Я также использую SKAudioNodes в своем приложении, которые не затрагиваются, но я действительно очень хочу иметь возможность использовать SKAction playSoundFileNamed.)

Вот файл gameScene.swift из урезанного игрового шаблона SpriteKit, который воспроизводит проблему. Вам просто нужно добавить аудиофайл в проект и назвать его «note»

Я прикрепил код, который должен находиться в appDelegate, к кнопке включения / выключения для имитации прерывания телефонного звонка. Этот код 1) Останавливает AudioEngine, затем деактивирует AVAudioSession - (обычно в applicationWillResignActive) ... и 2) Активирует AVAudioSession, затем запускает AudioEngine - (обычно в applicationDidBecomeActive)

Ошибка:

AVAudioSession.mm: 1079: - [AVAudioSession setActive: withOptions: error:]: деактивировать аудиосеанс с запущенным вводом-выводом. Все операции ввода-вывода должны быть остановлены или приостановлены до отключения аудиосеанса.

Это происходит при попытке деактивировать аудио сеанс, но только после того, как звук был воспроизведен хотя бы один раз. воспроизвести:

1) Запустить приложение 2) выключить и включить двигатель несколько раз. Ошибка не возникнет. 3) Нажмите кнопку playSoundFileNamed 1 или более раз для воспроизведения звука. 4) Подождите, пока звук прекратится 5) Подождите немного, чтобы быть уверенным

6) Нажмите кнопку Toggle Audio Engine, чтобы остановить 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 ]

2 голосов
/ 16 июня 2019

Позвольте мне сэкономить ваше время здесь: playSoundFileNamed звучит замечательно в теории, настолько замечательно, что вы можете сказать, использовать его в приложении, которое вы разрабатывали 4 года, пока в один прекрасный день вы не поймете, что оно не просто полностью сломано из-за прерываний, но даже может привести к поломке вашегоприложение в наиболее критических перерывах, ваш IAP.Не делай этого.Я все еще не совсем уверен, является ли SKAudioNode или AVPlayer ответом, но это может зависеть от вашего варианта использования.Только не делайте этого.

Если вам нужны научные доказательства, создайте приложение и создайте цикл for, который playSoundFileNamed все, что вы хотите в touchesBegan, и посмотрите, что происходит с использованием вашей памяти.Метод представляет собой протекающий кусок мусора.

1 голос
/ 22 июня 2019

В ответ на совет Майка Пандольфини не использовать 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)

Затем создайте функцию селектора, которая останавливает воспроизведение всех аудио-узлов:

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

Все SKAudioNodes теперь надежно воспроизводятся после прерывания приложения.

2) Чтобы воспроизвести способность playSoundFileNamed воспроизводить короткие быстрые повторяющиеся звуки или более длинные звуки, которые могут понадобиться для воспроизведения более одного раза и, следовательно, могут перекрываться, создайте / предварительно загрузите более 1 свойства для каждого звука и используйте их следующим образом:

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)
...