You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:CMTimeGetSeconds时长与addPeriodicTimeObserver值不等(超半小时异常)

问题原因与解决方案

核心原因

你遇到的问题主要来自两个方面:浮点数精度限制和周期性时间观测的触发逻辑:

  1. CMTime转浮点数的精度损失:CMTime内部是用「整数分子 + 时间刻度分母」的分数形式存储的,当转成Double类型的秒数时,长时长(比如你这里的5303秒,约1.47小时)会因为浮点数的有效位数限制产生精度误差,导致CMTimeGetSeconds(duration)和CMTimeGetSeconds(progressTime)的浮点数结果出现细微差异。
  2. 周期性观测的触发机制: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:00:24