我正在创建一个音频播放器并使用
UISlider
实时更新音频的播放时间。我决定使用一个周期性时间观察器来做到这一点:
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
slider.setValue(Float(time.seconds), animated: false)
}
这是一个好的开始,但是我遇到了一个问题,当我试图用滑块更改/寻找时间时,它会触发(可以理解),所以我将其更改为:
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
if !slider.isHighlighted {
slider.setValue(Float(time.seconds), animated: false)
}
}
这很棒,但我面临的最后一个问题是,当我放开滑块时,它似乎会快速将滑块值设置回设置新值之前的状态,然后快速修复自身。请看下面的视觉效果:
再次澄清一下,它像那样口吃我一松手,而不是在我试图滑动的时候
这看起来像
PeriodicTimeObserver
正在发送正确的值,但直到之后搜索才真正完成,这导致了卡顿。我建议你有一个标志,可以在要求观察者更新任何值之前确定搜索是否已完成。
我建议你做这样的事情,因为它消除了删除听众的需要,而是忽略了。可能不是最好的解决方案,但它会完成这项工作。
var isSeeking = false
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
if !isSeeking {
slider.setValue(Float(time.seconds), animated: false)
}
}
func seekToTime(_ time: CMTime) {
isSeeking = true
player.seek(to: time, completionHandler: { [unowned self] (completed) in
if completed {
isSeeking = false
}
})
}
尝试从滑块监听触摸事件。并在每个 - touchBegin/touchEnd 事件上删除/添加观察者。这样您在手动更改时间时将不会收到来自播放器的更新。 查看有关如何保留观察令牌并在需要时将其删除的文档。 https://developer.apple.com/documentation/avfoundation/avplayer/1385829-addperiodictimeobserver 一些有用的 SO 链接 - 在 iOS AVPlayer 中,addPeriodicTimeObserverForInterval 似乎丢失了
试试这个,似乎 timeObserver 在搜索之前和搜索之后被触发,那些不需要的更新总是有不同的时间尺度
let preferredTimescale = CMTimeScale(NSEC_PER_SEC)
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: preferredTimescale), queue: .main) { time in
if time.timescale == preferredTimescale, time.flags == .valid {
slider.setValue(Float(time.seconds), animated: false)
}
}
欧文的答案是正确的,需要时间来寻找,并且在寻找时你不应该更新滑块。
你也应该在
slider.isTracking
而不是slider.isHighlighted
上测试:
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
if !isSeeking && !slider.isTracking {
slider.setValue(Float(time.seconds), animated: false)
}
}