AJP
AJP

Reputation: 57

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

Upvotes: 0

Views: 789

Answers (4)

Lars C. Hassing
Lars C. Hassing

Reputation: 33

The answer by Owen is correct, it takes time to seek, and while seeking you shouldn't update the slider.

Also you should test on slider.isTracking rather than 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)
    }
}

Upvotes: 1

Yaroslav L'vov
Yaroslav L'vov

Reputation: 31

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

Upvotes: 0

Owen
Owen

Reputation: 879

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

Upvotes: 3

CloudBalancing
CloudBalancing

Reputation: 1676

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

Upvotes: 0

Related Questions