如何在 iOS 中使用 CoreMotion 和 AVAudioPlayer 播放短音频文件?

How to play a short audio file with CoreMotion and AVAudioPlayer in iOS?

当我在 X 轴上移动 iPhone 时(我正在使用 CoreMotionAVAudioPlayer),我试图播放较短的声音(1 到 4 秒)。我想为每个运动方向变化播放一个声音。

我写了下面的代码,但是当我移动iPhone时,声音播放了很多次,移动方向没有改变。下面的 print 调用显示了许多 DownUp,例如 Down Down Down Up Down Down Up Up...。如果我评论两个 play 回调,print 显示我期望的交替:Down Up Down Up Down Up....

为什么AVAudioPlayer.play在移动方向改变时被多次调用?

override func viewDidLoad() {
    super.viewDidLoad()

    // Audio
    audioURL = NSBundle.mainBundle().URLForResource("shortSound", withExtension: "wav")!
    do {
        try audioPlayer = AVAudioPlayer(contentsOfURL: audioURL)
        audioPlayer.prepareToPlay()
    } catch {
        print("audioPlayer failure")
    }

    // Sensor
    lastDirection = 0
    threshold = 2.1

    motionManager = CMMotionManager()
    if motionManager.accelerometerAvailable {
        let queue = NSOperationQueue()
        motionManager.startAccelerometerUpdatesToQueue(queue, withHandler: {
            data, error in

            guard let data = data else{
                return
            }

            // Get the acceleration
            let xAccel = data.acceleration.x
            let xPositive = xAccel > 0

            // Run if the acceleration is higher than theshold
            if abs(xAccel) > self.threshold {

                // Run only if the direction is changed
                if self.lastDirection != 1 && xPositive {
                    print("Up")
                    self.play() 
                    self.lastDirection = 1
                } else if self.lastDirection != -1 && !xPositive {
                    print("Down")
                    self.play()
                    self.lastDirection = -1
                }
            }
        })
    }
}

func play() {
    audioPlayer.currentTime = 0
    audioPlayer.play()
}

您可能遇到线程问题。你是 运行 后台队列的更新(queue,一个任意的 NSOperationQueue,顺便说一句,你也未能保留),但是你正在与 self.lastDirection 交谈并调用 self.play() 在同一个后台队列上,而不考虑这些活动的 thread-safety。

我建议至少重写此部分:

            if self.lastDirection != 1 && xPositive {
                print("Up")
                self.play() 
                self.lastDirection = 1
            } else if self.lastDirection != -1 && !xPositive {
                print("Down")
                self.play()
                self.lastDirection = -1
            }

...更像这样:

           dispatch_async(dispatch_get_main_queue()) {
               if self.lastDirection != 1 && xPositive {
                    self.lastDirection = 1
                    print("Up")
                    self.play() 
                } else if self.lastDirection != -1 && !xPositive {
                    self.lastDirection = -1
                    print("Down")
                    self.play()
                }
            }

请注意,我做了两处更改:我已经跳转到整个 check-print-play-toggle 舞蹈的主线程,并且我颠倒了事件的顺序,以便 check-toggle-print-play.

我还建议另外两个更改:保留操作队列(即使其成为 属性 而不是本地),并减少运动管理器更新的频率(通过设置较低的 accelerometerUpdateInterval).