AVAudioPlayerNode воспроизводится неожиданно несколько раз - PullRequest
0 голосов
/ 14 мая 2019

Помимо фонового аудиофайла, я хочу воспроизвести последовательность из 8 аудиофайлов.
Прикасаясь к сегментированному элементу управления, пользователь имеет возможность выбрать, какие звуки будут воспроизводиться поверх фонового файла.

 playCountOneAndFive(index: index)   
 playCountOneToSeven(index: index)
 playCountOneToEight(index: index)

Проблема, с которой я сталкиваюсь, заключается в том, что, когда я пытаюсь переключить воспроизведение с одной функции на другую, некоторые узлы заканчивают играть одновременно несколько раз. Например, вместо 1, 2, 3, он играет 1,1,2,2,2,3. и т. д.
Похоже, что некоторые из узлов продолжают играть, даже если пользователь коснулся другого сегмента сегментированного элемента управления.
Вот видео , показывающее приложение в действии https://youtu.be/HGm1hJX1Oiw

//  AudioPlayer.swift


import Foundation
import AVFoundation


protocol CustomAudioPlayerDelegate: class {
    func present(beatCount: Int)
    func present(currentTimeOfPlayer: CMTime, songDuration:CMTime)
}

class CustomAudioPlayer {


    var playStepOneAndFive: Bool = true
    var playStepOneToSeven: Bool = false
    var playStepOneToEight: Bool = false

    var delegate: CustomAudioPlayerDelegate?

    var playerRate: Float
    var soundForCountIsOn:Bool = true //initially, sound must be played for all counts
    var timeSincePlayerStarted: TimeInterval!
    var timeIndex = 0 // keeps track of the index of the item transversed within `timesToTransverse`  by `player?.addBoundaryTimeObserver`

    var audioNodeIndex = 0 //keeps track of which countdown file should be played from topAudioFiles

    var topAudioFiles: [AVAudioFile] = []
    var engine:AVAudioEngine

    var topAudioAudioNodes = [AVAudioPlayerNode]()
    var mixer: AVAudioMixerNode

    var urls: [URL] = []
    var player: AVPlayer!
    var timesToTransverse = [NSValue]() //contains timeValues in seconds such as ["1.54",2.64, 67.20]. These mark the time when the corresponding count down file should be played over the background file


    fileprivate var timeObserverToken: Any?//for addBoundaryTimeObserver

    init (backgroundURL: URL, urls: [URL] = [], timesToTransverse: [NSValue], newPlayer: AVPlayer, playerRate:Float) {

        self.urls = urls
        self.topAudioFiles = urls.map { try! AVAudioFile(forReading: $0) }
        self.timesToTransverse = timesToTransverse
        self.player = newPlayer
        self.playerRate = playerRate
        self.engine = AVAudioEngine()
        self.mixer = AVAudioMixerNode()

        self.engine.attach(mixer)
        self.engine.connect(mixer, to: engine.outputNode, format: nil)

        initTopAudioNodes()
        try! engine.start()
    }


    func initTopAudioNodes() {
        for _ in topAudioFiles {
            topAudioAudioNodes += [AVAudioPlayerNode()]
        }

        for node in topAudioAudioNodes {
            engine.attach(node)
            engine.connect(node, to: mixer, format: nil)
        }
    }


    func playWithAudioPlayerAndNodes() {

        player.playImmediately(atRate: self.playerRate)


     timeObserverToken =  player.addBoundaryTimeObserver(forTimes: timesToTransverse, queue: DispatchQueue.main) {
        [weak self] in

        guard let strongSelf = self else {return}

        //using the reminder operator get the index of the file to be played
           let index = strongSelf.audioNodeIndex % strongSelf.topAudioAudioNodes.count
           let node = strongSelf.topAudioAudioNodes[index]

            node.scheduleFile(strongSelf.topAudioFiles[index], at: nil, completionHandler: nil)

        func playCountOneAndFive(index: Int) {

            switch index {

            case 0: node.play()
            case 1: node.pause()
            case 2: node.pause()
            case 3: node.pause()
            case 4: node.play()
            case 5: node.pause()
            case 6: node.pause()
            case 7: node.pause()
            default:
                printsNow(message: "unexpected case in playCountOneAndFive with index \(index)")
            }
        }


        func playCountOneToSeven(index: Int) {

            switch index {

            case 0: node.play()
            case 1: node.play()
            case 2: node.play()
            case 3: node.pause()
            case 4: node.play()
            case 5: node.play()
            case 6: node.play()
            case 7: node.pause()
            default:
                printsNow(message: "unexpected case in playCountOneToSeven with index \(index)")
            }
        }

            func playCountOneToEight(index: Int) {
                node.play()
            }


        //if sound is ON, only one of the three functions will be called
        if strongSelf.soundForCountIsOn == true {

            if strongSelf.playStepOneAndFive == true &&
                strongSelf.playStepOneToSeven == false &&
                strongSelf.playStepOneToEight == false {
                    playCountOneAndFive(index: index)

            }else if strongSelf.playStepOneToSeven == true &&
                      strongSelf.playStepOneAndFive == false &&
                      strongSelf.playStepOneToEight == false {
                        playCountOneToSeven(index: index)

            } else if strongSelf.playStepOneToEight == true &&
                        strongSelf.playStepOneToSeven == false &&
                        strongSelf.playStepOneAndFive == false {
                         playCountOneToEight(index: index)
            }

        }else {
            node.pause()
        }






        //reset or increment audioNodeIndex to obtain the correct index when searching the countdown file to play
        //There is a total of 8 audio files to be played over the background file
        if strongSelf.audioNodeIndex == 7 {
            strongSelf.audioNodeIndex = 0
        }else {
            strongSelf.audioNodeIndex += 1
        }


        //because there are no time signature changes, we can simply increment  timeIndex with + 1 every time `addBoundaryTimeObserver` completion handler is called and subscript timesToTransverse with timeIndex in order to get the subsequent timeInSeconds
        guard strongSelf.timeIndex < strongSelf.timesToTransverse.count else {return}


        //use reminder operator to determine the beat count
        let beat = (strongSelf.timeIndex + 1) % 8 == 0 ? 8 : ((strongSelf.timeIndex + 1) % 8)

        print("Beat would be: ", beat)
         strongSelf.delegate?.present(beatCount: beat)

        /*
         0: (0 + 1) % 8 = 1
         1: (1 + 1) % 8 = 2
         6: (6 + 1) % 8 = 7
         7: (7 + 1) % 8 = 0
         */

        strongSelf.timeIndex += 1

        }
    }



}//end class
...