- 注:
此文是对 AVPlayer 的简单封装,实现播放器的常用功能。由于播放器的UI自定义性,所以此文没有关于播放器 UI 的代码,而是将 player 功能抽取出来封装的工具类。
AJPlayerManager.swift 的功能:
(1)更新播放时间(当前播放时间、总时间)
(2)更新播放状态
(3)更新缓存时间(开始时间、缓存时间、总时长)
/*
AVPlayer的工具类封装
*/
import UIKit
import AVKit
@objc enum AJPlayerStatus:Int {
case playbackBufferEmpty//正在缓存
case playbackLikelyToKeepUp//缓存好了
case readyToPlay//准备播放
case playToEndTime//播放结束
case failed//播放失败
case unknown//未知情况
}
@objc protocol AJPlayerManagerDelegate: NSObjectProtocol {
@objc optional func updatePlayerTime(currentTime:Float64, totalTime:Float64)//更新播放时间(当前播放时间、总时间)
@objc optional func updatePlayerStatus(status:AJPlayerStatus)//更新播放状态(播放状态)
@objc optional func updateLoadedTime(startTime:Float64, durationTime:Float64, totalTime:Float64)//更新缓存时间(开始时间、缓存时间、总时长)
}
class AJPlayerManager: NSObject {
//MARK: - 属性
var player:AVPlayer?//播放器
var playerItem:AVPlayerItem?//媒体资源管理对象
var playerLayer:AVPlayerLayer?//显示视频的图层
weak var delegate:AJPlayerManagerDelegate?
var timeObserver:Any?//实时监听者
var isAddPlayerItemObserver = false//属否添加playerItem监听
//MARK: - 初始化设置
func config(delegate:AJPlayerManagerDelegate?, url:URL, toView:UIView) {
self.delegate = delegate
self.playerItem = AVPlayerItem.init(url: url)
self.player = AVPlayer.init(playerItem: self.playerItem)
self.player?.rate = 1.0
self.playerLayer = AVPlayerLayer.init(player: self.player)
self.playerLayer?.videoGravity = .resizeAspectFill
self.playerLayer?.frame = toView.bounds
toView.layer.addSublayer(self.playerLayer!)
//实时的获取播放时长
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 50), queue: DispatchQueue.main) { [weak self] (time) in
guard self?.player?.currentItem != nil else {
return
}
let currentTime = CMTimeGetSeconds(time).isNaN ? 0 : CMTimeGetSeconds(time)
let totalTime = CMTimeGetSeconds((self?.player?.currentItem?.duration)!).isNaN ? 0 : CMTimeGetSeconds((self?.player?.currentItem?.duration)!)
self?.delegate?.updatePlayerTime?(currentTime: currentTime, totalTime: totalTime)
}
//playerItem-添加监听
self.isAddPlayerItemObserver = true
self.playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)//播放状态
self.playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)//加载的缓存时间
self.playerItem?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)//缓存不够(正在缓存)
self.playerItem?.addObserver(self, forKeyPath:"playbackLikelyToKeepUp", options: .new,context:nil)//缓存好了
//添加播放结束的监听
NotificationCenter.default.addObserver(self, selector: #selector(playToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
//销毁方法
func destory() {
self.pause()
self.delegate = nil
self.playerLayer?.removeFromSuperlayer()
if let observer = self.timeObserver {
try? self.player?.removeTimeObserver(observer)
self.timeObserver = nil
}
if isAddPlayerItemObserver {
try? self.playerItem?.removeObserver(self, forKeyPath: "status", context: nil)
try? self.playerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges", context: nil)
try? self.playerItem?.removeObserver(self, forKeyPath: "playbackBufferEmpty", context: nil)
try? self.playerItem?.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp", context: nil)
self.isAddPlayerItemObserver = false
}
self.playerItem?.cancelPendingSeeks()
self.playerItem?.asset.cancelLoading()
self.playerItem = nil
self.player?.replaceCurrentItem(with: nil)
self.player = nil
NotificationCenter.default.removeObserver(self)
}
//MARK: - 其他
//更新当前时间
func updateCurrentTime(value:Float) {
guard let item = self.player?.currentItem else { return }
let totalTime = CMTimeGetSeconds(item.duration)
let currentTime = totalTime*Double(value)
let time = CMTime.init(seconds: currentTime, preferredTimescale: item.asset.duration.timescale)
self.player?.seek(to: time)
}
//MARK: - 播放 & 暂停
func play() {//播放
self.player?.play()
}
func pause() {//暂停
self.player?.pause()
}
//MARK: - 监听方法
//playerItem的监听
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let item = self.playerItem else { return }
if keyPath == "status" {//播放状态
switch item.status{
case .readyToPlay://准备播放
self.delegate?.updatePlayerStatus?(status: .readyToPlay)
case .failed://播放失败
self.delegate?.updatePlayerStatus?(status: .failed)
case.unknown://未知情况
self.delegate?.updatePlayerStatus?(status: .unknown)
}
} else if keyPath == "loadedTimeRanges"{//加载的缓存区间
let loadTimeArray = item.loadedTimeRanges
//获取最新缓存的区间
guard let newTimeRange : CMTimeRange = loadTimeArray.first as? CMTimeRange else { return }
let startSeconds = CMTimeGetSeconds(newTimeRange.start)
let durationSeconds = CMTimeGetSeconds(newTimeRange.duration)
//let totalBuffer = startSeconds + durationSeconds//缓冲总长度
let totalSeconds = CMTimeGetSeconds(item.duration)//总时长
self.delegate?.updateLoadedTime?(startTime: startSeconds.isNaN ? 0 : startSeconds, durationTime: durationSeconds.isNaN ? 0 : durationSeconds, totalTime: totalSeconds.isNaN ? 0 : totalSeconds)
} else if keyPath == "playbackBufferEmpty"{//正在缓存
self.delegate?.updatePlayerStatus?(status: .playbackBufferEmpty)
} else if keyPath == "playbackLikelyToKeepUp"{//缓存好了
self.delegate?.updatePlayerStatus?(status: .playbackLikelyToKeepUp)
}
}
//播放结束的监听
@objc func playToEndTime() {
self.player?.seek(to: kCMTimeZero)//回退到0
self.delegate?.updatePlayerStatus?(status: .playToEndTime)
}
}