AVPlayer视频播放

AVPlayer是AVFoundation的核心类之一,主要的功能是用于做多媒体的播放控制,支持大部分的音频、视频文件格式,AVPlayer是用于视频播放的不二人选,本文不对AVPlayer的基础知识做介绍,直接进入我们的开发实战中来,涉及到UI的搭建,播放器的控制,帧图片的获取,媒体资源的加载进度等

注:本文代码块均使用Swift进行编写

1、控制器的实现

新建一个项目,取名:HJWealthT,再新建一个Swift文件取名:HJVideoViewController,本文继承于HJBaseViewController,HJBaseViewController继承于UIViewController,是本项目所有控制器的根控制器
然后我们给定一个场景:在cell上进行视频的播放。那么我们需要在控制器中添加一个UITableView,再准备一些播放视频的URL数组。使用懒加载实现两个内部属性:contentView,videoArray,并遵守协议:UITableViewDelegate,UITableViewDataSource

class HJVideoViewController: HJBaseViewController,UITableViewDelegate,UITableViewDataSource {
    
    //MARK: - 内部属性
    private lazy var contentView: UITableView = {
        let temp = UITableView.init(frame: CGRect.init(x: 0, y: HJMacro_sextyFourHeight, width: HJMacro_ScreenWidth, height: HJMacro_ScreenHeight - HJMacro_sextyFourHeight), style: .plain)
        temp.delegate = self
        temp.dataSource = self
        return temp
    }()
    /// 视频数据
    private lazy var videoArray: NSMutableArray = {
        let temp = NSMutableArray.init()
        return temp
    }()
}

在viewDidLoad方法中注册cell,添加contentView并实现数据的加载

//MARK: - 重写方法
    override func viewDidLoad() {
        super.viewDidLoad()
        self.addBackButton()
        self.setNavigation(title: "视频")
        //注册cell
        self.contentView.register(UINib.init(nibName: "HJVideoCell", bundle: nil), forCellReuseIdentifier: "HJVideoCell")
        self.view.addSubview(self.contentView)
        //数据
        let array = NSArray.init(array:
            ["http://wvideo.spriteapp.cn/video/2017/1126/fb6917b4-d2a9-11e7-9e71-90b11c479401_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1127/5790f2b6-d2c6-11e7-ae90-1866daeb0df1cutblack_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1202/5a228a358cbc0_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1203/36bc8e86d7dc11e7995b842b2b4c75ab_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1203/4391821ad7d211e793da842b2b4c75ab_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1202/ac98b61e-d702-11e7-a824-1866daeb0df1_wpd.mp4",
             "http://wvideo.spriteapp.cn/video/2017/1202/5dafd8d4d74311e7b9c8842b2b4c75ab_wpd.mp4"
            ])
        self.videoArray.removeAllObjects()
        self.videoArray.addObjects(from: array as! [Any])
    }

实现代理方法UITableViewDelegate,UITableViewDataSource

//MARK: - UITableViewDelegate,UITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.videoArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell:HJVideoCell = tableView.dequeueReusableCell(withIdentifier: "HJVideoCell", for: indexPath) as! HJVideoCell
        cell.index = indexPath
        return cell
 }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }

    func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
        return false
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 300
    }

2、HJVideoCell UI的搭建

所有我们简单的实现"HJVideoCell",HJVideoCell继承于UITableViewCell,我们通过xib实现视图的搭建,如图1:


AVPlayer视频播放_第1张图片
图1.png

HJVideoCell代码的实现:index是外部传入的属性,当"播放按钮"点击时实现一个闭包用于HJVideoViewController中实现播放器的添加

class HJVideoCell: UITableViewCell {
    
    //MARK: - 外部属性
    var index:IndexPath?
    var playButtonBlock:((_ sender:UIButton,_ index:IndexPath)->Void)?
    
    @IBOutlet weak var backgroundImageView: UIImageView!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    @IBAction func playButtonDidClicked(_ sender: UIButton) {
        if (self.playButtonBlock != nil && self.index != nil) {
            self.playButtonBlock!(sender,self.index!)
        }
    }
}

回到HJVideoViewController的 tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell方法中,需要在播放按钮点击的时候创建一个播放视图,
第一步:添加一个内部属性 playerView

class HJVideoViewController: HJBaseViewController,UITableViewDelegate,UITableViewDataSource {
    
     //MARK: - 内部属性
    //....
    /// 播放视图
    private var playerView:HJVideoPlayView?
}

第二步:播放按钮点击的时将playerView添加到cell的contentView上

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell:HJVideoCell = tableView.dequeueReusableCell(withIdentifier: "HJVideoCell", for: indexPath) as! HJVideoCell
        cell.index = indexPath
        //播放按钮点击
        cell.playButtonBlock = {
            (sender:UIButton,index:IndexPath)->Void in
              //添加播放视图
              self.playerView = HJVideoPlayView.singleView
              self.playerView?.frame = cell.backgroundImageView.frame
              self.playerView?.videoUrl = self.videoArray[index.row] as? String
              cell.contentView.addSubview(self.playerView!)
        }
        return cell
 }

3、创建播放器视图

新建一个Swift文件,取名:HJVideoPlayView,继承于UIView,使用xib进行UI布局,如图2:


AVPlayer视频播放_第2张图片
图2.png

视频播放视图我们运用单例模式来进行创建,保证只有一个播放视图,第一步:创建videoPlayView类方法,返回视图本身HJVideoPlayView,第二步:申明一个全局常量singleHJVideoPlayView,第三步:申明一个类变量,返回 singleHJVideoPlayView常量。

创建一个变量用于接收url地址,并重写set,get方法;导入AVFoundation框架,创建变量:asset,playerItem,player,playerLayer

import UIKit
import AVFoundation
let singleHJVideoPlayView:HJVideoPlayView = HJVideoPlayView.videoPlayView()

class HJVideoPlayView: UIView{
    //MARK: - 外部属性

    /// url地址
    var videoUrl:String? {
        set {
            self.priVideoUrl = newValue
            self.addPlayer()
        }
        get {
            return nil
        }
    }
    /// 单利
    class var singleView:HJVideoPlayView {
        return singleHJVideoPlayView
    }
    //MARK: - 内部属性    
    
    //播放
    private var asset:AVURLAsset?
    private var playerItem:AVPlayerItem?
    private var player:AVPlayer?
    private var playerLayer:AVPlayerLayer?
    /// 视频url
    private var priVideoUrl:String?
    
    /// 全屏按钮
    @IBOutlet weak var fullScreenButton: UIButton!
    /// 小图片
    @IBOutlet weak var currentImageView: UIImageView!
    
    /// 滑动时间显示
    @IBOutlet weak var centerSlipTimeLabel: UILabel!
    /// 下方10%透明视图
    @IBOutlet weak var bottomView: UIView!
    //播放暂停按钮
    @IBOutlet weak var playOrPauseButton: UIButton!
    /// 总时间
    @IBOutlet weak var durationTimeLabel: UILabel!
    /// 当前时间
    @IBOutlet weak var currentTimeLabel: UILabel!
    /// 进度条
    @IBOutlet weak var timeSlider: UISlider!
    /// 缓存进度
    @IBOutlet weak var cacheProgressView: UIProgressView!
    
    /// 停止按钮
    @IBOutlet weak var stopButton: UIButton!
    
    class func videoPlayView() -> HJVideoPlayView {
        return Bundle.main.loadNibNamed("HJVideoPlayView", owner: nil, options: nil)?.first as! HJVideoPlayView
    }
    //....

重写awakeFromNib方法,做一些需要的UI控制,centerSlipTimeLabel用于滚动条拖动时显示当前时间,currentImageView用于滚动条拖动时显示当前帧图片

    //MARK: - 重写方法
    override func awakeFromNib() {
        super.awakeFromNib()
        self.centerSlipTimeLabel.isHidden = true
        self.currentImageView.isHidden = true
        self.currentImageView.layer.masksToBounds = true
        self.currentImageView.layer.cornerRadius = 4
        self.backgroundColor = UIColor.black
        //添加播放器
        self.addPlayer()
    }
    /// 添加播放器
    private func addPlayer() {
        if self.priVideoUrl != nil {
            self.asset = AVURLAsset.init(url: URL.init(string: self.priVideoUrl!)!)
            self.playerItem = AVPlayerItem.init(asset: self.asset!, automaticallyLoadedAssetKeys: nil)
            // 设置播放器和播放layer
            if self.player != nil || self.playerLayer != nil {
                self.stop()
            }
            self.player = AVPlayer.init(playerItem: self.playerItem!)
            self.playerLayer = AVPlayerLayer.init(player: self.player)
            self.playerLayer?.backgroundColor = UIColor.black.cgColor
            self.playerLayer?.videoGravity = .resizeAspect
            self.playerLayer?.masksToBounds = true
            self.playerLayer?.frame = self.bounds
            self.layer.insertSublayer(self.playerLayer!, at: 0)
            //总时间
            self.durationTimeLabel.text = self.formatTime(time:(self.asset?.duration)!)
            //获取当前时间
            self.currentTimeLabel.text = self.formatTime(time: (self.playerItem?.currentTime())!)
            self.player?.addPeriodicTimeObserver(forInterval: CMTime.init(value: 1, timescale: (self.asset?.duration.timescale)!), queue: DispatchQueue.main, using: { (time) in
                self.currentTimeLabel.text = self.formatTime(time: time)
                self.timeSlider.value = Float(CMTimeGetSeconds(time) / CMTimeGetSeconds((self.asset?.duration)!))
            })
        
            //处理滑块
            self.timeSlider.addTarget(self, action: #selector(timeSliderValueChange(slider:)), for: .valueChanged)
        }
    }

对时间进行格式化

    //MARK: - 时间格式化
    /// 时间格式化
    ///
    /// - Parameter time:
    /// - Returns:
    private func formatTime(time:CMTime)->String {
        let hour:NSInteger = NSInteger((time.value/Int64(time.timescale)) / 3600)
        let min:NSInteger = NSInteger((time.value/Int64(time.timescale)) / 60) % 60
        let second = (time.value/Int64(time.timescale)) % 60
        let str:String?
        if hour > 0 {
            str = NSString.init(format: "%02d:%02d:%02d", hour,min,second) as String
        } else {
            str = NSString.init(format: "%02d:%02d",min,second) as String
        }
        return str!
    }

拖动滑块时,添加target,监听valueChanged;在方法中去跟新进度条的值,获取当前帧图片,当前时间,self.player调用seek方法来设置视频播放的当前时间

    /// 滑动滑块
    ///
    /// - Parameter slider:
    @objc private func timeSliderValueChange(slider:UISlider) {
        if self.player?.status == AVPlayerStatus.readyToPlay {
            //更新时间进度条
            self.timeSlider.value = slider.value
            let currentTime = CMTimeMake(Int64(slider.value*Float((self.playerItem?.duration.value)!)), (self.playerItem?.duration.timescale)!)
            //我们去获取当前帧图片
            var tempImage:UIImage?
            if self.getCurrentTimeImage(currentTime: currentTime) != nil {
                tempImage = (self.getCurrentTimeImage(currentTime: currentTime))!
            }
            self.currentImageView.image = tempImage
            self.currentImageView.isHidden = false
            self.centerSlipTimeLabel.text = self.formatTime(time: currentTime)
            self.centerSlipTimeLabel.isHidden = false
            self.currentTime = currentTime
            self.player?.seek(to: self.currentTime!, completionHandler: { (complet) in
                if complet {
                    self.currentImageView.isHidden = true
                    self.centerSlipTimeLabel.isHidden = true
                }
            })
        }
    }

     /// 获取图片
    ///
    /// - Parameter currentTime: 当前时间
    /// - Returns: 返回一张image
    func getCurrentTimeImage(currentTime:CMTime) -> UIImage? {
        //获取AVAssetImageGenerator
        var imageRef:CGImage?
        var newImage:UIImage?
        let assetImageG = AVAssetImageGenerator.init(asset: self.asset!)
        assetImageG.appliesPreferredTrackTransform = true
        assetImageG.apertureMode = AVAssetImageGeneratorApertureMode.encodedPixels
        do {
            imageRef = try assetImageG.copyCGImage(at: currentTime, actualTime: nil)
        } catch {
            print("获取图片失败")
        }
        if imageRef != nil {
            newImage = UIImage.init(cgImage: imageRef!)
        }
        return newImage
    }

回到addPlayer()方法中,去监听playerItem的播放状态,覆写监听的回调方法,判断playerItem的状态如果为readyToPlay,调用本类play方法

private func addPlayer() {
        if self.priVideoUrl != nil {
        //...
        //添加监听
        //播放的状态
        self.playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
        }
}
    //MARK: - 监听回调
    /// 监听回调
    
    ///
    /// - Parameters:
    ///   - keyPath:
    ///   - object:
    ///   - change:
    ///   - context:
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "status" {
            if self.playerItem?.status == AVPlayerItemStatus.readyToPlay {
                self.play()
            }
        } 
    }
        

播放和暂停方法,由于业务的需求,在视频未播放的时候是不能点击全屏按钮和拖动滑块的,所以在播放方法中,我需要去将全屏按钮和滑块的用户交互打开

   /// 播放
   func play() {
        if self.player == nil {
            self.addPlayer()
        }
        self.player?.play()
    self.fullScreenButton.isUserInteractionEnabled = true
        self.timeSlider.isUserInteractionEnabled = true
        if self.playOrPauseButton.currentTitle == "播放" {
            self.playOrPauseButton.setTitle("暂停", for: .normal)
        }
    }
    
    /// 暂停
    func pause() {
        self.player?.pause()
        if self.playOrPauseButton.currentTitle == "暂停" {
            self.playOrPauseButton.setTitle("播放", for: .normal)
        }
    }

停止播放时调用暂停播放,控制进度条和全屏按钮用户交互,并将播放的时间设置为0,将player置为空,移除playerLayer视图

    /// 停止
    func stop() {
        self.pause() 
        // 控制按钮
        self.timeSlider.isUserInteractionEnabled = false
        self.fullScreenButton.isUserInteractionEnabled = false
        self.player?.seek(to: CMTimeMake(0, 1), completionHandler: { (complet) in
            if complet {
                self.currentTimeLabel.text = self.formatTime(time: kCMTimeZero)
            }
        })
        self.player = nil
        self.playerLayer?.removeFromSuperlayer()
    }

这个时候,如果运行这个项目,是可以正常进行播放了,接下来我们需要对按钮的操作进行实现,以及程序切换到回台、前台的一些响应操作,以及实现一个缓冲进度条等

4播放视图按钮的操作实现

播放、暂停、停止操作,在播放或者暂停时,我们可以点击停止,所以打开了停止按钮的用户交互;在停止按钮点击了之后我们不希望再点击停止按钮,所以关闭了按钮的用户交互

     //MARK: - 点击事件
    /// 播放暂停
    ///
    /// - Parameter sender:
    @IBAction private func playOrPauseButtonDidClicked(_ sender: UIButton) {
        self.stopButton.isUserInteractionEnabled = true
        if sender.currentTitle == "暂停" {
            self.pause()
        } else if sender.currentTitle == "播放" {
            self.play()
        }
    }
    
    /// 停止播放
    ///
    /// - Parameter sender:
    @IBAction private func stopButtonDidClicked(_ sender: UIButton) {
        sender.isUserInteractionEnabled = false
        self.stop()
    }

给视图添加点击手势,回到awakeFromNib方法,将视图的用户交互打开,添加一个点击手势,实现selector中的isHiddenOtherView方法,方法中用到了isHiddenOther这个变量来做判断,所以我们需要申明isHiddenOther这个变量,类型是Bool?类型,bottomView指的是底部的一条%10透明视图,我们的播放按钮、时间、进度以及停止按钮都在bottomView视图中

    /// 是否隐藏其他试图
    private var isHiddenOther:Bool?

    //MARK: - 重写方法
    override func awakeFromNib() {
        //...
        //添加手势
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(self.isHiddenOtherView)))
    }

    /// 是否显示其他试图
    @objc private func isHiddenOtherView() {
        if self.isHiddenOther != nil && self.isHiddenOther == true {
            self.isHiddenOther = false
        } else  {
            self.isHiddenOther = true
        }
        self.bottomView.isHidden = self.isHiddenOther!
        self.fullScreenButton.isHidden = self.isHiddenOther!
    }

接下来实现全屏按钮的控制,首先控制文字显示,然后申明一个闭包用于外部调用,创建一个改变playerLayer尺寸的方法用于外部来调用

    ///是否全屏闭包
    var fullScreenButtonBlock:(()->Void)?
    /// 全屏
    ///
    /// - Parameter sender:
    @IBAction func fullScreenButtonDidClicked(_ sender: UIButton) {
        if sender.currentTitle! == "全屏" {
            sender.setTitle("返回", for: .normal)
        } else if sender.currentTitle! == "返回" {
            sender.setTitle("全屏", for: .normal)
        }
        if fullScreenButtonBlock != nil {
            self.fullScreenButtonBlock!()
        }
    }
    /// 改变playerLayer的size
    ///
    /// - Parameter size: 
    func changePlayerLayerWith(size:CGSize) {
        self.playerLayer?.removeFromSuperlayer()
        self.playerLayer?.frame.size = size
        self.layer.insertSublayer(self.playerLayer!, at: 0)
    }

回到HJVideoViewController的tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell方法中来去实现闭包,判断当前设置的方向,改变当前设备方向,设置是否显示状态栏,控制playerView的frame,playerLayer的frame,playerView添加到哪个视图上。代码中我们用到了
isShowStatusBar,这是用于是否显示状态栏创建的变量,所以需要添加一个新的变量

/// 是否显示状态栏
private var isShowStatusBar:Bool?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            //...
            self.playerView?.fullScreenButtonBlock = {
                ()->Void in
                if UIDevice.current.orientation == .landscapeLeft {
                    //改变方向-->返回
                    let tempNumber:NSNumber = NSNumber.init(value: Int8(UIDeviceOrientation.portrait.rawValue))
                    UIDevice.current.setValue(tempNumber, forKey: "orientation")
                    self.isShowStatusBar = true
                    self.setNeedsStatusBarAppearanceUpdate()
                    //接收到UIDeviceOrientationDidChange通知之后
                    self.playerView?.removeFromSuperview()
                    self.playerView?.frame = cell.backgroundImageView.frame
                    self.playerView?.changePlayerLayerWith(size: (self.playerView?.frame.size)!)
                    cell.contentView.addSubview(self.playerView!)
                } else {
                    //改变方向-->全屏
                    let tempNumber:NSNumber = NSNumber.init(value: Int8(UIDeviceOrientation.landscapeLeft.rawValue))
                    UIDevice.current.setValue(tempNumber, forKey: "orientation")
                    self.isShowStatusBar = false
                    self.setNeedsStatusBarAppearanceUpdate()
                    //接收到UIDeviceOrientationDidChange通知之后
                    self.playerView?.removeFromSuperview()
                    self.playerView?.frame = CGRect.init(x: 0, y: 0, width: HJMacro_ScreenWidth, height: HJMacro_ScreenHeight)
                    self.playerView?.changePlayerLayerWith(size:CGSize.init(width: HJMacro_ScreenHeight, height: HJMacro_ScreenWidth))
                    self.view.addSubview(self.playerView!)
                }
            //...
        }

在调用了 self.setNeedsStatusBarAppearanceUpdate()之后,为了让状态栏在我们需要的时候显示隐藏,需要去重写prefersStatusBarHidden,shouldAutorotate

override var shouldAutorotate: Bool {
        return false
    }
    /// 显示隐藏状态栏
    override var prefersStatusBarHidden: Bool {
        get {
            if self.isShowStatusBar != nil {
                if self.isShowStatusBar! == true {
                    return false
                } else {
                    return true
                }
            } else {
                return false
            }
        }
    }

回到viewDidLoad方法中去注册通知,监听当前设备的方向已经发生改变

    //MARK: - 重写方法
    override func viewDidLoad() {
       //...
        //注册通知
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        NotificationCenter.default.addObserver(self, selector: #selector(self.deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

实现selector调用的方法deviceOrientationDidChange,竖屏和横屏去做动画,去改变bounds,最后别忘记,移除通知

//MARK: - 通知
    @objc private func deviceOrientationDidChange() {
        if UIDevice.current.orientation == .portrait {
            //竖屏
            self.orientationChange(change: false)
        } else if UIDevice.current.orientation == .landscapeLeft {
            self.orientationChange(change: true)
        }
    }
    private func orientationChange(change:Bool) {
        if change == true {
            //横屏
            UIView.animate(withDuration: 0.25, animations: {
                self.playerView?.transform = CGAffineTransform.init(rotationAngle: CGFloat(Double.pi / 2))
                self.view.bounds = CGRect.init(x: 0, y: 0, width: HJMacro_ScreenWidth, height: HJMacro_ScreenHeight)
            }, completion: { (complet) in
                
            })
        } else {
            //竖屏
            UIView.animate(withDuration: 0.25, animations: {
                self.playerView?.transform = CGAffineTransform.init(rotationAngle: CGFloat(0))
                self.view.bounds = CGRect.init(x: 0, y: 0, width: HJMacro_ScreenWidth, height: HJMacro_ScreenHeight)
            }, completion: { (complet) in
                
            })
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }

根据既定需求,当cell滚动出屏幕时我们需要移除播放视图,实现UITableViewDelegate的tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)方法

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if self.playerView != nil {
            //如果 playerView 存在,并在某一个cell上,划出屏幕就移除并停止播放
            if cell.contentView.subviews.contains(self.playerView!) {
                self.playerView?.removeFromSuperview()
                self.playerView?.stop()
            }
        }
    }

5、完善我们的播放器

首先为我们的播放器实现缓冲机制,回到addPlayer方法中,注册观察者,在observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)方法中判断keyPath是否为"loadedTimeRanges",如果字符串相等,调用changeCacheProgressView方法进行缓冲处理

private func addPlayer() {
        if self.priVideoUrl != nil {
        //...
        //添加监听
        //缓存
        self.playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        }
}
//MARK: - 监听回调
    /// 监听回调
    
    ///
    /// - Parameters:
    ///   - keyPath:
    ///   - object:
    ///   - change:
    ///   - context:
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "status" {
            //...
        }  else if keyPath == "loadedTimeRanges" {
            self.changeCacheProgressView()
        }
    }
    /// 缓冲处理
    private func changeCacheProgressView() {
        //获取缓冲
        let cacheArray = self.playerItem?.loadedTimeRanges
        if cacheArray != nil && cacheArray!.count > 0 {
            let cacheTimeRange:CMTimeRange = cacheArray?.first as! CMTimeRange
            //获取开始时间
            let startTime = CMTimeGetSeconds(cacheTimeRange.start)
            let durationTime = CMTimeGetSeconds(cacheTimeRange.duration)
            let allCacheTime = startTime + durationTime
            self.cacheProgressView.progress = Float(allCacheTime / CMTimeGetSeconds((self.asset?.duration)!))
        }
    }

实现视频播放完成的回调以及应用程序回到前台的监听,回到addPlayer,注册观察者,监听AVPlayerItemDidPlayToEndTime和UIApplicationWillEnterForeground

private func addPlayer() {
        if self.priVideoUrl != nil {
            //通知
            //播放完成
            NotificationCenter.default.addObserver(self, selector: #selector(self.playEnd(notifi:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
            //app回到前台
            NotificationCenter.default.addObserver(self, selector: #selector(self.willEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
        }
    }

实现playEnd(notifi:NSNotification)方法和willEnterForeground()方法,最后别忘记,移除观察者

    /// 播放完成
    ///
    /// - Parameter notifi:
    @objc private func playEnd(notifi:NSNotification) {
        print(notifi)
        self.stop()
    }
    
    /// app回到前台
    @objc private func willEnterForeground() {
        self.play()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }

 
 

一个简单的视频播放器就搞定了,欢迎持续的关注后续的文章,你的持续关注就是我前进的动力O(∩_∩)O哈哈~
来这里试试视频播放吧:项目地址

你可能感兴趣的:(AVPlayer视频播放)