AVPlayer存在于AVFoundation中,它更加接近于底层,可以进行自定义,所以也更加灵活。AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。
最近实现一个基本的AVPlayer播放器,代码如下:
自定义一个View,播放器的UI部分主要在这里实现
import UIKit
import AVFoundationprotocol WXPlayerViewDelegate: NSObjectProtocol {
func wxplayer(playerView: WXPlayerView,slider: UISlider) func wxplayer(playerView:WXPlayerView,playAndPause playBtn:UIButton)
}
class WXPlayerView: UIView {
var playerLayer: AVPlayerLayer! var timeLabel: UILabel! var slider: UISlider! var progressView:UIProgressView! var playBtn:UIButton! var sliding = false var playing = true weak var delegate: WXPlayerViewDelegate? init(){ super.init(frame: CGRect.zero) self.initView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func initView() { self.playerLayer = AVPlayerLayer() self.layer.addSublayer( self.playerLayer) self.timeLabel = UILabel()
// self.timeLabel.backgroundColor = UIColor.red
self.timeLabel.textColor = UIColor.white
self.timeLabel.font = UIFont.systemFont(ofSize: 14)
self.addSubview(self.timeLabel)self.slider = UISlider() self.addSubview(self.slider) self.slider.minimumValue = 0 self.slider.maximumValue = 1 self.slider.value = 0 self.slider.maximumTrackTintColor = UIColor.clear self.slider.minimumTrackTintColor = UIColor.white self.slider.setThumbImage(UIImage(named:"ic_shipinjindu_nor"), for: .normal) // 按下的时候 self.slider.addTarget(self, action: #selector(self.sliderTouchDown), for: .touchDown) // 弹起的时候 self.slider.addTarget(self, action: #selector(self.sliderTouchUpOut), for: .touchUpOutside) self.slider.addTarget(self, action: #selector(self.sliderTouchUpOut), for: .touchUpInside) self.slider.addTarget(self, action: #selector(self.sliderTouchUpOut), for: .touchCancel) self.progressView = UIProgressView() self.progressView.backgroundColor = UIColor.lightGray self.insertSubview(self.progressView, belowSubview: self.slider) self.progressView.tintColor = UIColor.red self.progressView.progress = 0 self.playBtn = UIButton() self.playBtn.setImage(UIImage(named:"videopause_control_nor"), for: .normal) self.addSubview(self.playBtn) self.playBtn.addTarget(self, action: #selector(self.playBtnAction), for: .touchUpInside) } func sliderTouchDown(){ self.sliding = true } func sliderTouchUpOut(){ // 代理处理 delegate?.wxplayer(playerView: self, slider: self.slider) } func updateUI(timeStr: String) { self.timeLabel.text = timeStr self.timeLabel.sizeToFit() self.setNeedsLayout() } func playBtnAction() { self.playing = !self.playing // 改变状态 // 根据状态设定图片 if playing { playBtn.setImage(UIImage(named: "videopause_control_nor"), for: .normal) }else{ playBtn.setImage(UIImage(named: "videoplay_control_nor"), for: .normal) } // 代理方法 delegate?.wxplayer(playerView: self, playAndPause: self.playBtn) } override func layoutSubviews() { super.layoutSubviews() self.playerLayer.frame = self.bounds self.timeLabel.frame.origin.x = self.frame.width - self.timeLabel.frame.width - 5 self.timeLabel.frame.origin.y = self.frame.height - self.timeLabel.frame.height - 50 self.slider.frame.origin.x = 40 self.slider.frame.origin.y = self.frame.height - 75 self.slider.frame.size.width = self.frame.width - 140 self.progressView.frame.origin.x = 42 self.progressView.frame.origin.y = self.frame.height - 59 self.progressView.frame.size.width = self.frame.width - 142 self.playBtn.frame = CGRect(x: 5, y: self.frame.height - 75, width: 30, height: 30) }
}
接下来是VC中的代码,主要是实现逻辑部分
import UIKit
import AVFoundationextension ViewController: WXPlayerViewDelegate{
func wxplayer(playerView: WXPlayerView, slider: UISlider) { if self.avplayer.status == AVPlayerStatus.readyToPlay{ let duration = slider.value * Float(CMTimeGetSeconds(self.avplayer.currentItem!.duration)) let seekTime = CMTimeMake(Int64(duration), 1) self.avplayer.seek(to: seekTime, completionHandler: { (b) in playerView.sliding = false }) } } func wxplayer(playerView: WXPlayerView, playAndPause playBtn: UIButton) { if !playerView.playing{ self.avplayer.pause() }else{ if self.avplayer.status == AVPlayerStatus.readyToPlay{ self.avplayer.play() } } }
}
class ViewController: UIViewController {
var playerView: WXPlayerView!
var playerItem: AVPlayerItem!
var avplayer: AVPlayer!
var playerLayer: AVPlayerLayer!
var link: CADisplayLink!override func viewDidLoad() { super.viewDidLoad() self.initPlayer() self.link = CADisplayLink(target: self, selector: #selector(self.update)) self.link.add(to: .main, forMode: RunLoopMode.defaultRunLoopMode) } deinit { self.playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges") self.playerItem.removeObserver(self, forKeyPath: "status") } func initPlayer() { let url = URL(string: "http://vt1.doubanio.com/201609291737/4af83e686c0432c0dbd3c320f14eba6f/view/movie/M/302030039.mp4") self.playerItem = AVPlayerItem(url: url!) self.playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil) self.playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil) self.avplayer = AVPlayer(playerItem: self.playerItem) self.playerLayer = AVPlayerLayer(player: self.avplayer) self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect self.playerLayer.contentsScale = UIScreen.main.scale self.view.layer.addSublayer( self.playerLayer) self.playerView = WXPlayerView() self.playerView.delegate = self self.playerView.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: 300) self.view.addSubview(self.playerView) self.playerView.playerLayer = self.playerLayer self.playerView.layer.insertSublayer(self.playerLayer, at: 0) } func update(){ //暂停的时候 if !self.playerView.playing{ return } let currentTime = CMTimeGetSeconds(self.avplayer.currentTime()) let totalTime = TimeInterval(self.playerItem.duration.value) / TimeInterval(self.playerItem.duration.timescale) let timeStr = "\(self.formatPlayTime(secounds: currentTime))/\(self.formatPlayTime(secounds: totalTime))" self.playerView.updateUI(timeStr: timeStr) if !self.playerView.sliding{ // 播放进度 self.playerView.slider.value = Float(currentTime/totalTime) } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "loadedTimeRanges"{ // 通过监听AVPlayerItem的"loadedTimeRanges",可以实时知道当前视频的进度缓冲 let loadedTime = avalableDurationWithplayerItem() let totalTime = CMTimeGetSeconds(playerItem.duration) let percent = loadedTime/totalTime // 计算出比例 // 改变进度条 self.playerView.progressView.progress = Float(percent) }else if keyPath == "status"{ if self.playerItem.status == AVPlayerItemStatus.readyToPlay{ self.avplayer.play() }else { print("加载异常") } } } func formatPlayTime(secounds: TimeInterval) -> String{ if secounds.isNaN{ return "00:00" } let min = Int(secounds / 60) let sec = Int(secounds.truncatingRemainder(dividingBy: 60) ) return String(format: "%02d:%02d", min,sec) } // 计算当前缓冲进度 func avalableDurationWithplayerItem() -> TimeInterval{ guard let loadedTimeRanges = self.avplayer.currentItem?.loadedTimeRanges,let first = loadedTimeRanges.first else {fatalError()} let timeRange = first.timeRangeValue let startSeconds = CMTimeGetSeconds(timeRange.start) let durationSecound = CMTimeGetSeconds(timeRange.duration) let result = startSeconds + durationSecound return result }
}
实现后的效果图