Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UISlider jumps when updating for AVPlayer

I try to implement simple player with UISlider to indicate at what time is current audio file.

enter image description here

In code I have added two observers:

    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)
    }

with one private function:

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)
}

When audio plays, everything is fine and slider is pretty much updated. The problem occurs when I try to move slider manually while audio is playing, then it jumps. Why?

UPDATE:

I know why actually. Because I update it twice: manually and from player observer, but how to prevent from this behaviour? I have no idea;) please, help.

like image 562
Bartłomiej Semańczyk Avatar asked Mar 08 '19 08:03

Bartłomiej Semańczyk


1 Answers

One simple way to go about this would be to prevent addPeriodicTimeObserver from calling self?.updateSlider(with: time) when the slider is being touched.

This can be determined via the UISliders isTracking property:

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

This is present in all UIControl elements which you can use in this way:

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)
}

Generic Example:

@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
}

I'm sure there are other (better) ways out there but I haven't done much in RxSwift (yet).
I hope this is good enough for now.

like image 191
staticVoidMan Avatar answered Oct 10 '22 01:10

staticVoidMan