如何在AVAudioPlayer播放到指定时间时调用方法?
解决AVAudioPlayer播放到指定时间触发操作的方案
嘿,这个问题我之前做项目的时候也碰到过!AVAudioPlayer确实没有原生的代理方法帮你监听特定的播放时间点,但咱们完全可以自己动手实现,思路其实很清晰——核心就是定期检查当前播放进度,对比目标时间点。下面给你详细讲两种可行的方式,重点说纯AVAudioPlayer的实现,也提一下更优雅的替代方案(如果你能切换播放器的话)。
方法一:用Timer定期检查播放进度(纯AVAudioPlayer实现)
这是最直接的方案,通过定时器每隔一小段时间就检查当前播放的currentTime,当它接近目标时间时触发你想要的操作。
具体实现步骤
- 声明必要的变量:播放器实例、定时器、目标时间(可以是单个值或数组)
- 初始化AVAudioPlayer:加载音频文件,设置代理
- 启动播放时同时启动定时器:定期检查播放进度
- 编写检查逻辑:对比当前时间和目标时间,触发操作后销毁定时器(或移除已触发的时间点)
- 处理播放状态变化:暂停、结束播放时要及时销毁定时器,避免无效执行
代码示例(Swift版)
import AVFoundation import UIKit class AudioPlayerViewController: UIViewController, AVAudioPlayerDelegate { // 播放器实例 private var audioPlayer: AVAudioPlayer? // 检查进度的定时器 private var progressCheckTimer: Timer? // 目标时间点(这里示例用多个时间点,单个的话直接用TimeInterval即可) private var targetTimePoints: [TimeInterval] = [3.0, 5.5, 8.0] override func viewDidLoad() { super.viewDidLoad() setupAudioPlayer() } // 初始化播放器 private func setupAudioPlayer() { guard let audioFilePath = Bundle.main.path(forResource: "your_audio_file", ofType: "mp3") else { print("找不到音频文件") return } let audioURL = URL(fileURLWithPath: audioFilePath) do { audioPlayer = try AVAudioPlayer(contentsOf: audioURL) audioPlayer?.delegate = self audioPlayer?.prepareToPlay() } catch { print("播放器初始化失败:\(error.localizedDescription)") } } // 开始播放 @IBAction func startPlayButtonTapped(_ sender: UIButton) { audioPlayer?.play() // 启动定时器,每隔0.1秒检查一次(精度和性能的平衡值) progressCheckTimer = Timer.scheduledTimer( timeInterval: 0.1, target: self, selector: #selector(checkPlayProgress), userInfo: nil, repeats: true ) } // 暂停播放 @IBAction func pausePlayButtonTapped(_ sender: UIButton) { audioPlayer?.pause() // 暂停播放时销毁定时器,避免无用检查 progressCheckTimer?.invalidate() progressCheckTimer = nil } // 检查播放进度的核心方法 @objc private func checkPlayProgress() { guard let player = audioPlayer, player.isPlaying else { // 如果播放器没在播放,直接销毁定时器 progressCheckTimer?.invalidate() progressCheckTimer = nil return } // 遍历目标时间点,检查是否到达 targetTimePoints.removeAll { targetTime in // 加一个小误差范围,避免定时器间隔导致错过触发 if abs(player.currentTime - targetTime) < 0.05 { // 触发对应操作 executeActionForTimePoint(targetTime) return true // 移除已触发的时间点,避免重复执行 } return false } // 如果所有目标时间都触发完了,销毁定时器 if targetTimePoints.isEmpty { progressCheckTimer?.invalidate() progressCheckTimer = nil } } // 根据不同时间点执行不同操作 private func executeActionForTimePoint(_ timePoint: TimeInterval) { switch timePoint { case 3.0: print("到达3秒节点:执行操作A") // 这里写你的业务逻辑,比如弹出提示、切换音效等 case 5.5: print("到达5.5秒节点:执行操作B") case 8.0: print("到达8秒节点:执行操作C") default: break } } // MARK: - AVAudioPlayerDelegate // 播放结束时销毁定时器 func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { progressCheckTimer?.invalidate() progressCheckTimer = nil } // 页面销毁时清理资源 deinit { progressCheckTimer?.invalidate() progressCheckTimer = nil audioPlayer?.stop() audioPlayer = nil } }
注意事项
- 定时器间隔:0.1秒是精度和性能的平衡值,太频繁会增加CPU消耗,太长可能错过目标时间点。
- 误差范围:因为定时器不是绝对精确的,所以用
abs(currentTime - targetTime) < 0.05这样的判断,确保不会因为微小的时间差错过触发。 - 内存管理:一定要在播放器停止、页面销毁时销毁定时器,避免内存泄漏。
方法二:切换到AVPlayer(更优雅的方案)
如果你的项目允许替换播放器,AVPlayer提供了原生的边界时间监听方法addBoundaryTimeObserverForTimes,不需要手动写定时器,更可靠。
简单示例
import AVFoundation class AVPlayerDemoViewController: UIViewController { private var avPlayer: AVPlayer? private var timeObserver: Any? override func viewDidLoad() { super.viewDidLoad() setupAVPlayer() } private func setupAVPlayer() { guard let audioURL = Bundle.main.url(forResource: "your_audio_file", withExtension: "mp3") else { return } avPlayer = AVPlayer(url: audioURL) // 设置目标时间点 let timePoints: [CMTime] = [ CMTimeMakeWithSeconds(3.0, preferredTimescale: 1000), CMTimeMakeWithSeconds(5.5, preferredTimescale: 1000) ] // 添加边界时间监听 timeObserver = avPlayer?.addBoundaryTimeObserver(forTimes: timePoints.map { NSValue(time: $0) }, queue: .main) { [weak self] in // 到达目标时间点时执行操作 print("到达指定播放节点") self?.executeTargetAction() } } private func executeTargetAction() { // 你的业务逻辑 } @IBAction func startPlay(_ sender: UIButton) { avPlayer?.play() } deinit { if let observer = timeObserver { avPlayer?.removeTimeObserver(observer) } avPlayer?.pause() avPlayer = nil } }
这个方法的优点是系统原生支持,不需要自己处理定时器的生命周期,精度也更高,但前提是你能把AVAudioPlayer替换成AVPlayer。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

