iOS中AVAudioPlayer连续播放失败:audioPlayerDidFinishPlaying未触发
音频顺序播放问题排查与解决
问题描述
需要按顺序播放三段音频:getReady→nextExercise→burpees,预期流程:
- 在
WorkoutTabataViewController中调用playGetReady() - 播放第一段音频
- 音频播放结束后自动触发
audioPlayerDidFinishPlaying() - 调用
playNextSound()播放下一段音频
实际问题:仅第一段音频播放,且audioPlayerDidFinishPlaying()未被触发,相关代码如下:
class AudioPlayerManager: AVAudioPlayerDelegate { var description: String static let shared = AudioPlayerManager() var audioPlayer: AVAudioPlayer? var workoutVC: WorkoutTabataViewController? var mainVC: MainTabataViewController? var currentSound = 0 let urls: [URL] init() { self.description = "" //First sound let getReady = Bundle.main.path(forResource: "Get ready", ofType: "mp3")! let urlGetReady = URL(fileURLWithPath: getReady) //Second sound let nextExercise = Bundle.main.path(forResource: "Next Exercise", ofType: "mp3")! let urlNextExercise = URL(fileURLWithPath: nextExercise) //Third sound let burpees = Bundle.main.path(forResource: "Burpees", ofType: "mp3")! let urlBurpees = URL(fileURLWithPath: burpees) urls = [urlGetReady, urlNextExercise, urlBurpees] } func playGetReady() { do { audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound]) audioPlayer?.delegate = self audioPlayer?.play() } catch { print(error) } } func playNextSound() { currentSound += 1 if currentSound < urls.count { do { audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound]) audioPlayer?.delegate = self audioPlayer?.play() } catch { print(error) } } } func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { if flag { playNextSound() } } }
问题根源
- 未配置音频会话:
AVAudioPlayer的播放完成回调依赖活跃的AVAudioSession,未初始化激活会话会导致系统无法传递播放完成信号。 - 未继承NSObject:
AVAudioPlayerDelegate是Objective-C协议,Swift类必须继承NSObject才能正确响应代理回调。 - 播放索引未重置:多次调用
playGetReady()时,currentSound保留之前数值会导致播放顺序混乱(虽不是当前回调失效直接原因,但属于潜在问题)。
解决方案
修正后的代码如下,解决了所有问题:
import AVFoundation class AudioPlayerManager: NSObject, AVAudioPlayerDelegate { var description: String static let shared = AudioPlayerManager() var audioPlayer: AVAudioPlayer? var workoutVC: WorkoutTabataViewController? var mainVC: MainTabataViewController? var currentSound = 0 let urls: [URL] // 私有化初始化,保证单例唯一性 private override init() { self.description = "" // 初始化音频URL let getReady = Bundle.main.path(forResource: "Get ready", ofType: "mp3")! let urlGetReady = URL(fileURLWithPath: getReady) let nextExercise = Bundle.main.path(forResource: "Next Exercise", ofType: "mp3")! let urlNextExercise = URL(fileURLWithPath: nextExercise) let burpees = Bundle.main.path(forResource: "Burpees", ofType: "mp3")! let urlBurpees = URL(fileURLWithPath: burpees) urls = [urlGetReady, urlNextExercise, urlBurpees] super.init() // 初始化并激活音频会话 setupAudioSession() } private func setupAudioSession() { let session = AVAudioSession.sharedInstance() do { // 设置音频会话为播放模式 try session.setCategory(.playback, mode: .default) // 激活音频会话 try session.activate() } catch { print("音频会话初始化失败: \(error)") } } func playGetReady() { currentSound = 0 // 重置播放索引,确保每次调用从第一段开始 guard currentSound < urls.count else { return } do { audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound]) audioPlayer?.delegate = self audioPlayer?.play() } catch { print("播放音频失败: \(error)") } } func playNextSound() { currentSound += 1 guard currentSound < urls.count else { return } do { audioPlayer = try AVAudioPlayer(contentsOf: urls[currentSound]) audioPlayer?.delegate = self audioPlayer?.play() } catch { print("播放下一段音频失败: \(error)") } } func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { guard flag else { return } playNextSound() } }
关键修正点说明
- 继承NSObject:确保Swift类能正确响应Objective-C代理回调
- 初始化音频会话:激活播放模式会话是触发
audioPlayerDidFinishPlaying回调的核心前提 - 重置播放索引:每次调用
playGetReady()时重置索引,避免重复调用导致的播放位置错误 - 私有化初始化:严格保证单例唯一实例,避免意外创建多个管理实例
内容的提问来源于stack exchange,提问作者Ростислав Григорьев
相关产品推荐
相关产品推荐

