Created
June 3, 2020 17:21
-
-
Save DDavis1025/731330c5fbb5cba6364b0f5566893f63 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AVFoundation | |
class VideoPlayerView: UIView { | |
private var playerLayer: AVPlayerLayer? | |
var player:AVPlayer? | |
var timer: Timer? | |
let activityIndicatorView:UIActivityIndicatorView = { | |
let aiv = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.large) | |
aiv.startAnimating() | |
return aiv | |
}() | |
lazy var pausePlayButton: UIButton = { | |
let button = UIButton(type: .system) | |
let config = UIImage.SymbolConfiguration(pointSize: 35, weight: .black, scale: .medium) | |
let image = UIImage(systemName: "pause.fill", withConfiguration: config) as UIImage? | |
let whiteImage = image?.withTintColor(.white, renderingMode: .alwaysOriginal) | |
button.setImage(whiteImage, for: .normal) | |
button.translatesAutoresizingMaskIntoConstraints = false | |
button.isUserInteractionEnabled = true | |
button.isHidden = true | |
button.addTarget(self, action: #selector(handlePause(_:)), for: .touchUpInside) | |
return button | |
}() | |
var isPlaying = false | |
@objc func handlePause(_ sender: UIButton!) { | |
print("pause clicked") | |
if isPlaying { | |
player?.pause() | |
let button = UIButton(type: .system) | |
let config = UIImage.SymbolConfiguration(pointSize: 35, weight: .black, scale: .medium) | |
let play = UIImage(systemName: "play.fill", withConfiguration: config) as UIImage? | |
let playImage = play?.withTintColor(.white, renderingMode: .alwaysOriginal) | |
pausePlayButton.setImage(playImage, for: .normal) | |
} else { | |
let button = UIButton(type: .system) | |
let config = UIImage.SymbolConfiguration(pointSize: 35, weight: .black, scale: .medium) | |
let image = UIImage(systemName: "pause.fill", withConfiguration: config) as UIImage? | |
let whiteImage = image?.withTintColor(.white, renderingMode: .alwaysOriginal) | |
pausePlayButton.setImage(whiteImage, for: .normal) | |
player?.play() | |
} | |
isPlaying = !isPlaying | |
} | |
let controlsContainerView: UIView = { | |
let view = UIView() | |
view.backgroundColor = UIColor(white: 0, alpha: 1) | |
return view | |
}() | |
let videoLengthLabel:UILabel = { | |
let label = UILabel() | |
label.text = "00:00" | |
label.textColor = UIColor.white | |
label.translatesAutoresizingMaskIntoConstraints = false | |
label.font = UIFont.boldSystemFont(ofSize: 13) | |
label.textAlignment = .right | |
return label | |
}() | |
let currentTimeLabel:UILabel = { | |
let label = UILabel() | |
label.text = "00:00" | |
label.textColor = UIColor.white | |
label.translatesAutoresizingMaskIntoConstraints = false | |
label.font = UIFont.boldSystemFont(ofSize: 13) | |
label.textAlignment = .right | |
return label | |
}() | |
lazy var videoSlider: UISlider = { | |
let slider = UISlider() | |
slider.translatesAutoresizingMaskIntoConstraints = false | |
slider.minimumTrackTintColor = UIColor.black | |
slider.maximumTrackTintColor = UIColor.white | |
let config = UIImage.SymbolConfiguration(pointSize: 17, weight: .black, scale: .medium) | |
let thumb = UIImage(systemName: "circle.fill", withConfiguration: config) as UIImage? | |
let thumbImage = thumb?.withTintColor(.black, renderingMode: .alwaysOriginal) | |
slider.setThumbImage(thumbImage, for: .normal) | |
slider.addTarget(self, action: #selector(handleSliderChange), for: .valueChanged) | |
return slider | |
}() | |
@objc func handleSliderChange() { | |
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?.seek(to: seekTime) | |
} | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupPlayer() | |
controlsContainerView.isUserInteractionEnabled = true | |
addSubview(controlsContainerView) | |
controlsContainerView.addSubview(activityIndicatorView) | |
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false | |
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true | |
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true | |
controlsContainerView.addSubview(pausePlayButton) | |
pausePlayButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true | |
pausePlayButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true | |
pausePlayButton.widthAnchor.constraint(equalToConstant: 50).isActive = true | |
pausePlayButton.heightAnchor.constraint(equalToConstant: 50).isActive = true | |
controlsContainerView.addSubview(videoLengthLabel) | |
videoLengthLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true | |
videoLengthLabel.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true | |
videoLengthLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true | |
videoLengthLabel.heightAnchor.constraint(equalToConstant: 24).isActive = true | |
controlsContainerView.addSubview(currentTimeLabel) | |
currentTimeLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: -8).isActive = true | |
currentTimeLabel.centerYAnchor.constraint(equalTo: videoLengthLabel.centerYAnchor).isActive = true | |
currentTimeLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true | |
currentTimeLabel.heightAnchor.constraint(equalToConstant: 24).isActive = true | |
controlsContainerView.addSubview(videoSlider) | |
videoSlider.rightAnchor.constraint(equalTo: videoLengthLabel.leftAnchor, constant: -10).isActive = true | |
videoSlider.leftAnchor.constraint(equalTo: currentTimeLabel.rightAnchor, constant: 10).isActive = true | |
videoSlider.centerYAnchor.constraint(equalTo: videoLengthLabel.centerYAnchor).isActive = true | |
videoSlider.heightAnchor.constraint(equalToConstant: 30).isActive = true | |
backgroundColor = UIColor.black | |
} | |
private func setupPlayer() { | |
let urlString = "http://www.w3schools.com/html/mov_bbb.mp4" | |
guard let url = URL(string: urlString) else { return } | |
player = AVPlayer(url: url) | |
let playerLayer = AVPlayerLayer(player: player) | |
playerLayer.videoGravity = .resizeAspectFill | |
self.layer.addSublayer(playerLayer) | |
self.playerLayer = playerLayer | |
player?.play() | |
player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil) | |
//track player progress | |
let interval = CMTime(value: 1, timescale: 2) | |
player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in | |
let seconds = CMTimeGetSeconds(progressTime) | |
let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60))) | |
let minutesString = String(format: "%02d", Int(seconds / 60)) | |
self.currentTimeLabel.text = "\(minutesString):\(secondsString)" | |
if let duration = self.player?.currentItem?.duration { | |
let durationSeconds = CMTimeGetSeconds(duration) | |
self.videoSlider.value = Float(seconds / durationSeconds) | |
} | |
}) | |
} | |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | |
if keyPath == "currentItem.loadedTimeRanges" { | |
print(change) | |
activityIndicatorView.stopAnimating() | |
controlsContainerView.backgroundColor = .clear | |
pausePlayButton.isHidden = false | |
isPlaying = true | |
if let duration = player?.currentItem?.duration { | |
let seconds = CMTimeGetSeconds(duration) | |
let secondsText = Int(seconds.truncatingRemainder(dividingBy: 60)) | |
let minutesText = String(format:"%02d", Int(seconds) / 60) | |
videoLengthLabel.text = "\(minutesText):\(secondsText)" | |
} | |
} | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
playerLayer?.frame = bounds | |
controlsContainerView.frame = bounds | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment