I'm trying to play short sounds (1 to 4 seconds) when I move the iPhone on the X axis (I'm using CoreMotion
and AVAudioPlayer
). I want to play one sound for each movement direction change.
I wrote the code below, but when I move the iPhone, the sound is played many times without the movement direction change. The print
calls below show many Down
and Up
such as Down Down Down Up Down Down Up Up...
. If I comment both play
callbacks, the print
shows the alternation that I expect: Down Up Down Up Down Up...
.
Why the AVAudioPlayer.play
is called more than one time when the movement direction changes?
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()
}
You probably have a threading problem. You are running the updates on a background queue (queue
, an arbitrary NSOperationQueue, which by the way you are also failing to retain), but then you are talking to self.lastDirection
and calling self.play()
on that same background queue without regard to the thread-safety of those activities.
I would suggest at the very least rewriting this section:
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
}
...more like this:
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()
}
}
Note that I've made two changes: I've stepped out to the main thread for the entire check-print-play-toggle dance, and I've reversed the order of events so that it goes check-toggle-print-play.
Also I would suggest two other changes: retain the operation queue (i.e. make it a property instead of a local), and reduce the frequency of motion manager updates (by setting a lower accelerometerUpdateInterval
).