Happy Sunday everyone. Let's continue building out our video player by introducing a video duration label and a UISlider component that allows us to jump to different portions of our clip. First, we look at how to figure out the duration by inspecting AVPlayer currentItem's duration property. Converting this value into seconds is somewhat tricky so I'll go through step by step how to do this.
Next we'll take a look at using a UISlider component to seek and scrub through our video. By utilizing the zero to one value of our slider and video duration, we can use seekToTime on AVPlayer to bounce from point to point very easily.
Have fun and enjoy.
!codebreak
<div class='filename'>VideoPlayerView</div>
!codebreak
!syntax-highlight
class VideoPlayerView: UIView {
//...
let videoLengthLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "00:00"
label.textColor = .whiteColor()
label.font = UIFont.boldSystemFontOfSize(14)
label.textAlignment = .Right
return label
}()
lazy var videoSlider: UISlider = {
let slider = UISlider()
slider.translatesAutoresizingMaskIntoConstraints = false
slider.minimumTrackTintColor = .redColor()
slider.maximumTrackTintColor = .whiteColor()
slider.setThumbImage(UIImage(named: "thumb"), forState: .Normal)
slider.addTarget(self, action: #selector(handleSliderChange), forControlEvents: .ValueChanged)
return slider
}()
func handleSliderChange() {
print(videoSlider.value)
if let duration = player?.currentItem?.duration {
let totalSeconds = CMTimeGetSeconds(duration)
let value = Float64(videoSlider.value) * totalSeconds
let seekTime = CMTime(value: Int64(value), timescale: 1)
player?.seekToTime(seekTime, completionHandler: { (completedSeek) in
//perhaps do something later here
})
}
}
override init(frame: CGRect) {
//...
controlsContainerView.addSubview(videoLengthLabel)
videoLengthLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: -8).active = true
videoLengthLabel.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
videoLengthLabel.widthAnchor.constraintEqualToConstant(60).active = true
videoLengthLabel.heightAnchor.constraintEqualToConstant(24).active = true
controlsContainerView.addSubview(videoSlider)
videoSlider.rightAnchor.constraintEqualToAnchor(videoLengthLabel.leftAnchor).active = true
videoSlider.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
videoSlider.leftAnchor.constraintEqualToAnchor(leftAnchor).active = true
videoSlider.heightAnchor.constraintEqualToConstant(30).active = true
//...
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
//this is when the player is ready and rendering frames
if keyPath == "currentItem.loadedTimeRanges" {
//...
if let duration = player?.currentItem?.duration {
let seconds = CMTimeGetSeconds(duration)
let secondsText = Int(seconds) % 60
let minutesText = String(format: "%02d", Int(seconds) / 60)
videoLengthLabel.text = "\(minutesText):\(secondsText)"
}
}
}
//...
}