Created
June 4, 2020 00:15
-
-
Save DDavis1025/5932d7576e4d0fe59505c91943d89463 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 isPlaying = false | |
let activityIndicatorView:UIActivityIndicatorView = { | |
let aiv = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.large) | |
aiv.translatesAutoresizingMaskIntoConstraints = false | |
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 | |
}() | |
@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 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() { | |
print(videoSlider.value) | |
if let duration = player?.currentItem?.duration{ | |
let totalSeconds = CMTimeGetSeconds(duration) | |
let value = Float64(videoSlider.value) * totalSeconds | |
print("value \(value)") | |
let seekTime = CMTime(value: Int64(value), timescale: 1) | |
print("seekTime \(seekTime)") | |
player?.seek(to: seekTime, completionHandler: { (completedSeek) in | |
}) | |
} | |
} | |
let controlsContainerView: UIView = { | |
let view = UIView() | |
view.translatesAutoresizingMaskIntoConstraints = false | |
view.backgroundColor = UIColor(white: 0, alpha: 1) | |
return view | |
}() | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupPlayer() | |
addSubview(controlsContainerView) | |
NSLayoutConstraint.activate([ | |
controlsContainerView.topAnchor.constraint(equalTo: topAnchor), | |
controlsContainerView.bottomAnchor.constraint(equalTo: bottomAnchor), | |
controlsContainerView.leftAnchor.constraint(equalTo: leftAnchor), | |
controlsContainerView.rightAnchor.constraint(equalTo: rightAnchor) | |
]) | |
controlsContainerView.addSubview(activityIndicatorView) | |
activityIndicatorView.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor).isActive = true | |
activityIndicatorView.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor).isActive = true | |
controlsContainerView.addSubview(pausePlayButton) | |
pausePlayButton.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor).isActive = true | |
pausePlayButton.centerYAnchor.constraint(equalTo: controlsContainerView.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 minutsString = String(format: "%02d", Int(seconds/60)) | |
let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60))) | |
self.currentTimeLabel.text = "\(minutsString):\(secondsString)" | |
if let duration = self.player?.currentItem?.duration{ | |
let durationSeconds = CMTimeGetSeconds(duration) | |
self.videoSlider.value = Float(seconds / durationSeconds) | |
print("videoSlider value \(self.videoSlider.value)") | |
} | |
}) | |
} | |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | |
if keyPath == "currentItem.loadedTimeRanges" { | |
activityIndicatorView.stopAnimating() | |
controlsContainerView.backgroundColor = .clear | |
pausePlayButton.isHidden = false | |
isPlaying = true | |
if let duration = player?.currentItem?.duration{ | |
let seconds = CMTimeGetSeconds(duration) | |
let minutsText = String(format: "%02d", Int(seconds)/60) | |
let secondsText = Int(seconds) % 60 | |
videoLengthLabel.text = "\(minutsText):\(secondsText)" | |
} | |
} | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
playerLayer?.frame = bounds | |
} | |
} | |
class VideoVC: UIViewController { | |
private lazy var playerView: VideoPlayerView = { | |
let playerView = VideoPlayerView(frame: .zero) | |
playerView.backgroundColor = .black | |
return playerView | |
}() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
view.backgroundColor = UIColor.white | |
view.addSubview(playerView) | |
playerView.translatesAutoresizingMaskIntoConstraints = false | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
let height = view.safeAreaLayoutGuide.layoutFrame.size.width / 1.777777777777778 | |
playerView.frame = CGRect(x: view.safeAreaLayoutGuide.layoutFrame.origin.x, y: view.safeAreaLayoutGuide.layoutFrame.origin.y, width: view.safeAreaLayoutGuide.layoutFrame.size.width, height: height) /* set your desired height here */ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment