为 AVPlayer 更新时 UISlider 跳转

UISlider jumps when updating for AVPlayer

我尝试用 UISlider 实现简单的播放器来指示当前音频文件的时间。

在代码中我添加了两个观察者:

    slider.rx.value.subscribe(onNext: { value in
        let totalTime = Float(CMTimeGetSeconds(self.player.currentItem!.duration))
        let seconds = value * totalTime
        let time = CMTime(seconds: Double(seconds), preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        self.player.seek(to: time)
        }).disposed(by: bag)
    let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
    player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { [weak self] time in
        self?.updateSlider(with: time)
    }

具有一个私有函数:

private func updateSlider(with time: CMTime) {
    let currentTime = CMTimeGetSeconds(time)
    var totalTime = CMTimeGetSeconds(player.currentItem!.duration)
    if totalTime.isNaN {
        totalTime = 0
    }
    startLabel.text = Int(currentTime).descriptiveDuration
    endLabel.text = Int(totalTime).descriptiveDuration
    slider.value = Float(currentTime / totalTime)
}

当音频播放时,一切正常,滑块几乎更新。当我尝试在播放音频时手动移动滑块时出现问题,然后它跳了。为什么?

更新:

其实我知道为什么。因为我更新了两次:手动和玩家观察者,但如何防止这种行为?我不知道;)请帮忙。

解决此问题的一个简单方法是防止 addPeriodicTimeObserver 在触摸滑块时调用 self?.updateSlider(with: time)

这可以通过 UISliders isTracking 属性:

来确定

isTracking

A Boolean value indicating whether the control is currently tracking touch events.

While tracking of a touch event is in progress, the control sets the value of this property to true. When tracking ends or is cancelled for any reason, it sets this property to false.

Ref: https://developer.apple.com/documentation/uikit/uicontrol/1618210-istracking

这存在于所有 UIControl 个元素中,您可以这样使用:

player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { [weak self] time in
    //check if slider is being touched/tracked
    guard self?.slider.isTracking == false else { return }

    //if slider is not being touched, then update the slider from here
    self?.updateSlider(with: time)
}

一般示例:

@IBOutlet var slider: UISlider!
//...
func startSlider() {
    slider.value = 0
    slider.maximumValue = 10

    Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] (timer) in
        print("Slider at: \(self?.slider.value)")
        guard self?.slider.isTracking == false else { return }
        self?.updateSlider(to: self!.slider.value + 0.1)
    }
}

private func updateSlider(to value: Float) {
    slider.value = value
}

我确定还有其他(更好)的方法,但我在 RxSwift 方面(还)做得不多。
我希望这已经足够好了。