'UISlider stuttering when updating value

I am creating an audio player and am using UISlider to update the audio's playback time in real time. I decided to use a periodic time observer to do this:

player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
    slider.setValue(Float(time.seconds), animated: false)
}

This was a good start, however I ran into an issue where this would fire (understandably) while I was trying to change/seek the time with the slider, so I altered it to:

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

This was great, but the last issue I'm facing is that when I let go of the slider, it seems to quickly set the slider value back to what it was before setting the new value, and then quickly fixes itself. See a visual below:

enter image description here

To clarify again, it's stuttering like that as soon as I let go, not while I am trying to slide



Solution 1:[1]

This looks like the PeriodicTimeObserver is sending the correct value but the seeking is not actually completing until afterwards which is causing the stuttering. I'd suggest you have a flag that would determine if the seeking has completed before asking the observer to update any values.

I'd suggest you do something like this as it eliminates the need for listeners to be removed but rather ignored. May not be the best solution but it will do the job.

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

Solution 2:[2]

Try to listen to touch events from the slider. And remove/add the observer on each - touchBegin/touchEnd event. This way you will not receive updates from the player while changing the time manually. Checkout the docs on how to keep the observation token and remove it when needed. https://developer.apple.com/documentation/avfoundation/avplayer/1385829-addperiodictimeobserver Some useful SO links - In iOS AVPlayer, addPeriodicTimeObserverForInterval seems to be missing

Solution 3:[3]

Try this one, it seems that timeObserver is fired before the seek and after the seek and those unwanted updates always have different timescale

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 CloudBalancing
Solution 3