求助:CMTimeGetSeconds时长与addPeriodicTimeObserver值不等(超半小时异常)
问题原因与解决方案
核心原因
你遇到的问题主要来自两个方面:浮点数精度限制和周期性时间观测的触发逻辑:
- CMTime转浮点数的精度损失:CMTime内部是用「整数分子 + 时间刻度分母」的分数形式存储的,当转成
Double类型的秒数时,长时长(比如你这里的5303秒,约1.47小时)会因为浮点数的有效位数限制产生精度误差,导致CMTimeGetSeconds(duration)和CMTimeGetSeconds(progressTime)的浮点数结果出现细微差异。 - 周期性观测的触发机制:
addPeriodicTimeObserver是按照你设置的固定interval来调度触发的,AVPlayer不会为了精确卡在duration时刻而调整最后一次触发时间。所以最后一次触发的progressTime很可能会略超过实际的播放时长,就像你看到的5303.112秒 vs 5303.088秒。
修复方案
推荐三种可靠的解决方式,按优先级排序:
1. 使用CMTime原生比较(最精确)
直接用CMTimeCompare比较原始的CMTime对象,避免浮点数转换带来的误差:
// 先保存观察者token,用于后续移除 var timeObserverToken: Any? audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in currentSeconds = CMTimeGetSeconds(progressTime) if let duration = self.audioPlayer?.currentItem?.duration { // 跳过未加载完成的无效时长 if CMTimeGetSeconds(duration).isNaN { return } // 用CMTimeCompare判断是否到达或超过时长 if CMTimeCompare(progressTime, duration) >= 0 { self.handleAudioFinishPlaying() // 移除观察者,防止重复触发 if let observer = self.timeObserverToken { self.audioPlayer?.removeTimeObserver(observer) self.timeObserverToken = nil } } } })
2. 用阈值判断浮点数差异
如果坚持用浮点数比较,设置一个合理的误差阈值(比如0.1秒),判断当前时间是否接近并超过总时长:
var timeObserverToken: Any? audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in currentSeconds = CMTimeGetSeconds(progressTime) if let duration = self.audioPlayer?.currentItem?.duration { let durationSeconds = CMTimeGetSeconds(duration) if !durationSeconds.isNaN && currentSeconds >= durationSeconds - 0.1 { self.handleAudioFinishPlaying() // 移除观察者 if let observer = self.timeObserverToken { self.audioPlayer?.removeTimeObserver(observer) self.timeObserverToken = nil } } } })
3. 监听官方播放完成通知(最可靠)
AVPlayer本身提供了播放完成的系统通知,这是最稳妥的方式,不需要自己通过时间观测判断:
// 在播放器初始化阶段添加通知监听 NotificationCenter.default.addObserver( self, selector: #selector(handleAudioFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: audioPlayer?.currentItem ) // 记得在页面销毁时移除监听 deinit { NotificationCenter.default.removeObserver(self) } // 播放完成的处理方法 @objc func handleAudioFinishPlaying() { // 你的自定义完成逻辑 }
内容的提问来源于stack exchange,提问作者user8896788
相关产品推荐
相关产品推荐

