问题
On top of a background audio file, I want to play a sequence of 8 audio files.
By touching a segmented control, the user has the possibility to choose which sounds will be played on top of the background file.
playCountOneAndFive(index: index)
playCountOneToSeven(index: index)
playCountOneToEight(index: index)
The problem I am facing is that when I try to switch playing from one function to another, some of the nodes end up playing simultaneously several times. For example instead of playing 1, 2, 3, it plays 1,1,2,2,2,3. and so on.
It looks like some of the nodes continue playing, even though the user has touched to another segment on the segmented control.
Here's a video showing the app in action 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
来源:https://stackoverflow.com/questions/56126570/avaudioplayernode-playing-unexpectedly-multiple-times