我要播放8个音频文件的序列。
通过触摸分段控件,用户可以选择要在背景文件顶部播放的声音。
playCountOneAndFive(index: index)
playCountOneToSeven(index: index)
playCountOneToEight(index: index)
我面临的问题是,当我尝试将播放从一个功能切换到另一个功能时,某些节点最终会同时播放几次。例如,代替播放1、2、3,它播放1、1、2、2、2、3。等等。
即使用户触摸了分段控件上的另一个分段,看起来某些节点仍在继续播放。
这是video,显示了正在运行的应用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