Reputation: 3274
I want a sound to play with negligible delay when a user taps down on a view. I've tried this with both AVAudioPlayer
and AudioServices, but I'm experiencing the same issue with both: a slight delay if there is more than about a second between taps. In other words, here's what happens:
The delay we're talking about is short, maybe 100 ms or so, but enough so that the playback doesn't feel/sound instantaneous like the others. Here's the AVAudioPlayer
version of the code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
player = try! AVAudioPlayer(contentsOf: soundUrl)
player.volume = 0 // "Prime the pump"
player.play()
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
player.volume = 1
player.play()
}
}
And here's the AudioServices version. In this case, there are other posts suggesting that a silent sound could be played during initialization to "prime the pump" but that would only address the first sound. This issue affects all sounds that occur after a 1+ second delay.
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var soundID:SystemSoundID = 0
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundID)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
AudioServicesPlaySystemSound(soundID)
}
}
In both cases, this "touch down" recognizer (by @le-sang) is used:
import Foundation
import UIKit.UIGestureRecognizerSubclass
class TouchDownGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}
I've also tried using a UILongPressGestureRecognizer
with a minimumPressDuration=0
and the same thing happens. Also, same thing if I replace the UIView
with a UIControl
and setup a target/action. Any insights would be much appreciated, thanks for reading.
Upvotes: 0
Views: 242
Reputation: 3274
Here's the fix I found. I'm not totally happy with this solution (still not sure why this was happening), but it does indeed work for both players.
The idea is simple: if you leave either player alone for a second or so, it starts to nap and then takes a moment to wake up when you need it. So, the solution is to "nudge" the player at regular intervals so it doesn't start napping.
For the AVAudioPlayer
case:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer!
var ghostPlayer:AVAudioPlayer!
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
player = try! AVAudioPlayer(contentsOf: soundUrl)
ghostPlayer = try! AVAudioPlayer(contentsOf: soundUrl)
ghostPlayer.volume = 0
ghostPlayer.play()
timer = Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(nudgeAudio),
userInfo: nil,
repeats: true)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
player.play()
}
func nudgeAudio() {
ghostPlayer.play()
}
}
And for AudioServices:
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var soundID:SystemSoundID = 0
var ghostID:SystemSoundID = 1
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
let soundUrl = Bundle.main.url(forResource: "short_sound", withExtension: "wav")!
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundID)
let ghostUrl = Bundle.main.url(forResource: "silence", withExtension: "wav")!
AudioServicesCreateSystemSoundID(ghostUrl as CFURL, &ghostID)
timer = Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(nudgeAudio),
userInfo: nil,
repeats: true)
let r = TouchDownGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(r)
}
func tapped(_ gesture: TouchDownGestureRecognizer){
AudioServicesPlaySystemSound(soundID)
}
func nudgeAudio() {
AudioServicesPlaySystemSound(ghostID)
}
}
Upvotes: 1