SwiftUI 中 AVAudioPlayer 播放音频为何逐个变慢?
结论
现有信息不足以确定具体原因。点击 “A” 到 “Z” 时,延迟逐渐增加。可以先检查对应 MP3 文件开头的静音是否也逐渐变长。如果有前置静音,AVAudioPlayer 会从文件开头播放,因此听到声音的时间会相应推迟。如果文件开头没有静音,再检查播放器是否已准备好,以及按钮回调中是否有耗时操作。
你已尝试在 .task 中创建播放器,但没有调用 prepareToPlay()。另一个 Sounds 示例则是在点击时重新创建播放器。这两种做法都不足以单独证明延迟是由播放器初始化造成的。
先检查音频文件
在音频编辑器中查看每个 MP3 的波形,或逐个播放文件开头,确认第一个有效声音出现前是否有静音。也可以裁掉每个文件开头的部分,重新导出后比较播放效果。MP3 编码可能会带来很短的编码延迟;如果空白有数秒,应先检查文件内容和资源映射。
同时确认 character.file 指向的是预期资源。可以在点击时打印文件名,并在 play() 前后记录时间,以判断延迟发生在调用播放之前,还是文件开始播放之后:
private func keypressed() {
print("播放资源:\(character.file)")
let startedAt = Date()
guard let soundURL = Bundle.main.url(
forResource: character.file,
withExtension: "mp3"
) else {
print("找不到音频资源:\(character.file)")
return
}
do {
player = try AVAudioPlayer(contentsOf: soundURL)
player?.prepareToPlay()
let didStart = player?.play() ?? false
print("play() 返回:\(didStart),耗时:\(Date().timeIntervalSince(startedAt)) 秒")
} catch {
print("Failed to load the sound: \(error)")
}
}
play() 返回 true 只表示播放器已安排播放,不能确认扬声器已经输出可听见的声音。要准确比较实际出声时间,可以用录音或音频分析工具观察输出。
预先创建并准备播放器
如果文件没有前置静音,可以在视图出现时创建播放器并调用 prepareToPlay()。点击时只需触发播放,并将播放位置重置到文件开头:
.task {
guard let soundURL = Bundle.main.url(
forResource: character.file,
withExtension: "mp3"
) else {
return
}
do {
let audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
audioPlayer.prepareToPlay()
player = audioPlayer
} catch {
print("Failed to load the sound: \(error)")
}
}
private func keypressed() {
onTap(character)
player?.currentTime = 0
let didStart = player?.play() ?? false
print("play() 返回:\(didStart)")
}
播放期间要确保播放器仍被强引用。上例用 @State 保存播放器,通常可以满足这一点。如果把播放器放在管理类中,也要确保管理类持有播放器实例,避免播放器在播放后立即释放。
若问题仍存在
在 onTap(character) 前后记录时间,检查回调是否执行了耗时操作。也可以暂时注释这一行进行测试。如果 play() 很快返回,但实际出声仍有延迟,重点检查 MP3 前置静音、资源对应关系和音频会话配置。如果延迟发生在调用 play() 之前,则检查文件加载或回调中的同步工作。
备注:内容仅供参考。
