I am trying to achieve smooth video scrubbing with AVPlayer
through UISlider
I have searched and it seems Apple has a Technical Q&A and explain
try to add selectors to your slider like this at the begining:
playerView.timeSlider.addTarget(self, action: #selector(PlayerViewController.timeSliderBeganTracking(_:)), for:.touchDown) // moved
playerView.timeSlider.addTarget(self, action: #selector(PlayerViewController.timeSliderEndedTracking(_:)), for: .touchUpInside)
playerView.timeSlider.addTarget(self, action: #selector(PlayerViewController.timeSliderEndedTracking(_:)), for: .touchUpOutside )
then in timeSliderBeganTracking
set isSeekInProgress
to true
and pause your player and invalidate timers and remove observers if you have any
then in timeSliderEndedTracking
seek to current time like this :
let currentSeconds = Float64(self.playerView.timeSlider.value)
container.player.seek(to: CMTimeMakeWithSeconds(currentSeconds, 100))
then add the observers and timers back and at the end set isSeekInProgress
to false
HERE you can find a complete sample of creating a custom videoPlayer
I know this is an old problem, I encountered it too. The solution can help someone now. Apple sample make slider super smooth and pretty. To make the sample work:
private static var context = 1
func playVideo() {
...
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options: [.new, .initial], context: &VideoViewController.context)
...
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else { return }
guard context == &VideoViewController.context else { return }
switch keyPath {
case #keyPath(AVPlayer.currentItem.status):
if let newStatus = change.map({ $0[.newKey] as? NSNumber }),
let parsedNewStatus = newStatus.map({ AVPlayerItem.Status(rawValue: $0.intValue) ?? .unknown}) {
playerCurrentItemStatus = parsedNewStatus
} else {
playerCurrentItemStatus = .unknown
}
default: break
}
}
Observe and update playerCurrentItemStatus
On slider value change event, just call the
stopPlayingAndSeekSmoothlyToTime(CMTime.init(seconds: (player.currentItem?.asset.duration.seconds)!* slider.value, preferredTimescale: 1000))
The Apples sample code will change the player current time. You can also adjust toleranceBefore and toleranceAfter if you scrub the slider really fast.
Although I'm not using the same method as you do which is stopPlayingAndSeekSmoothlyToTime
, I thought I should help you with the seeking action of the player.
func sliderValueChanged() {
var timeToSeek = player.currentItem?.asset.duration.seconds
timeToSeek = timeToSeek * Double(slider.value)
player.seek(to: CMTimeMake(Int64(timeToSeek), 1))
}
Also you should set the slider.maximumValue
to 1. Hope this helps.
Note: Please don't forget to handle currentItem
optional value. If it is nil
you should set the value 0 for timeToSeek
variable.