Reputation: 57
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:
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
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
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
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
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