如何让AVAudioPlayer在另一实例播放结束后立即无延迟启动?
解决AVAudioPlayer无缝衔接播放延迟的问题
问题根源
使用AVAudioPlayerDelegate的回调触发后续播放时,系统调度回调本身存在一定延迟,再加上AVAudioPlayer.play()调用时的音频硬件初始化开销,就会出现你遇到的半秒间隙。
推荐解决方案:使用AVAudioEngine实现精准衔接
AVAudioEngine能直接控制音频流的播放时机,实现毫秒级的无缝切换,是处理这类衔接场景的更优选择。以下是完整实现代码:
import AVFoundation class ViewController: UIViewController { private let audioEngine = AVAudioEngine() private let introPlayerNode = AVAudioPlayerNode() private let loopPlayerNode = AVAudioPlayerNode() private var introAudioFile: AVAudioFile? private var loopAudioFile: AVAudioFile? override func viewDidLoad() { super.viewDidLoad() setupAudio() } private func setupAudio() { guard let introURL = Bundle.main.url(forResource: "intro", withExtension: "mp3"), let loopURL = Bundle.main.url(forResource: "loop", withExtension: "mp3") else { return } do { introAudioFile = try AVAudioFile(forReading: introURL) loopAudioFile = try AVAudioFile(forReading: loopURL) // 将播放器节点添加到音频引擎 audioEngine.attach(introPlayerNode) audioEngine.attach(loopPlayerNode) // 连接节点到输出 audioEngine.connect(introPlayerNode, to: audioEngine.mainMixerNode, format: introAudioFile?.processingFormat) audioEngine.connect(loopPlayerNode, to: audioEngine.mainMixerNode, format: loopAudioFile?.processingFormat) // 准备引擎 try audioEngine.start() // 安排intro播放,播放结束后立即启动loop循环 introPlayerNode.scheduleFile(introAudioFile!, at: nil) { [weak self] in guard let self = self, let loopFile = self.loopAudioFile else { return } // 循环播放loop文件 self.loopPlayerNode.scheduleFile(loopFile, at: nil, completionHandler: { [weak self] in self?.loopPlayerNode.scheduleFile(loopFile, at: nil, completionHandler: $0) }) self.loopPlayerNode.play() } // 启动intro播放 introPlayerNode.play() } catch { print("音频初始化失败: \(error.localizedDescription)") } } }
关键说明
AVAudioPlayerNode.scheduleFile(_:at:completionHandler:)的回调会在音频播放结束的瞬间触发,几乎没有延迟- 通过在loop的完成回调里重新调度自身,实现无限循环播放
- 提前启动
AVAudioEngine,避免播放时的引擎启动开销
备选方案(若坚持用AVAudioPlayer)
如果不想切换到AVAudioEngine,可以尝试提前让loopPlayer处于"预启动暂停"状态,减少play()的启动开销:
// 初始化时修改loopPlayer的设置 loopPlayer = try AVAudioPlayer(contentsOf: loopURL) loopPlayer?.prepareToPlay() loopPlayer?.numberOfLoops = -1 loopPlayer?.play() loopPlayer?.pause() // 启动后立即暂停,让音频硬件处于就绪状态 // 代理回调中改为恢复播放 func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { if player == introPlayer { loopPlayer?.currentTime = 0 loopPlayer?.play() } }
这种方法能减少部分延迟,但仍不如AVAudioEngine的精准度高,因为代理回调的延迟依然存在。
内容的提问来源于stack exchange,提问作者John DoeDoe
相关产品推荐
相关产品推荐

