UISlider jumps when updating for AVPlayer
Asked Answered
M

1

5

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?

Mcbride answered 8/3, 2019 at 8:17 Comment(4)
see this for help : #43063370Sty
How about case handling within addPeriodicTimeObserver block to return immediately when slider is being touched?Sucy
@Sucy how would you like to implement it?;) Please answer your idea and I will try it.Nguyetni
@BartłomiejSemańczyk Well, the basic idea is to prevent slider from updating when it is being touched/moved. I will chalk up a sample example :)Sucy
S
6

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.

Sucy answered 8/3, 2019 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.