AVAudioPlayer 和 AudioServices 如果两次播放之间 >1 s 都会延迟声音

AVAudioPlayer and AudioServices both delay sound if >1 s between plays

我希望在用户点击视图时播放声音的延迟可以忽略不计。我在 AVAudioPlayer 和 AudioServices 上都试过这个,但我在两者上都遇到了同样的问题:如果两次点击之间的间隔超过一秒,则会有轻微的延迟。换句话说,会发生以下情况:

我们谈论的延迟很短,可能是 100 毫秒左右,但足以让播放不像其他播放那样 feel/sound 是瞬时的。这是代码的 AVAudioPlayer 版本:

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()
    }
}

这是 AudioServices 版本。在这种情况下,other posts 建议在 "prime the pump" 初始化期间可以播放无声声音,但这只会解决第一个声音。此问题会影响 1 秒以上延迟后出现的所有声音。

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)
    }
}

在这两种情况下,都会使用此 "touch down" 识别器 (by @le-sang):

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
    }
}

我也尝试过将 UILongPressGestureRecognizerminimumPressDuration=0 一起使用,同样的事情发生了。另外,如果我用 UIControl 替换 UIView 并设置 target/action,也是一样的。任何见解将不胜感激,感谢阅读。

这是我找到的修复方法。我对这个解决方案不是很满意(仍然不确定为什么会发生这种情况),但它确实对两个玩家都有效。

这个想法很简单:如果你让任何一个玩家独处一秒钟左右,它就会开始小睡,然后在你需要它的时候花点时间醒来。因此,解决方案是定期 "nudge" 播放器,这样它就不会开始打盹。

对于 AVAudioPlayer 案例:

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()
    }
}

对于音频服务:

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)
    }
}