本文讲解的是 iOS 视频播放, 支持本地,在线播放. 进度拖动,精准跳转,获取视频的长度等问题
demo 记得点亮 star. 3Q
iOS 视频播放主要使用的类 AVPlayerItem,AVPlayerLayer,AVPlayer
AVPlayerItem: 提供视频信息, 利用视频的 url 创建一个对象.利用 kvo 监听 status,可以获取当前视频的状态.
playerItem = AVPlayerItem(url: videoUrl)
//添加状态观察者
playerItem?.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
//添加视频播放结束通知
NotificationCenter.default.addObserver(self, selector: #selector(moviePlayDidEnd(userinfo:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
extension LYZPlayerView {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//状态
if keyPath == "status" {
print(change?[NSKeyValueChangeKey(rawValue: "new")] ?? "")
let status: Int = change?[NSKeyValueChangeKey(rawValue: "new")] as! Int
playerStatusBlock(AVPlayer.Status(rawValue: status) ?? AVPlayer.Status.unknown)
switch status {
case AVPlayer.Status.unknown.rawValue:
do {
print("不知道状态")
}
case AVPlayer.Status.readyToPlay.rawValue:
do {
loading.stopAnimating()
print("准备开始播放")
self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 5), queue: nil, using: {[weak self] time in
guard let _self = self else {
return
}
let second: Int64 = Int64(_self.playerItem?.currentTime().value ?? 0) / Int64(_self.playerItem?.currentTime().timescale ?? 0)
print("时间----\(second)")
_self.controlView.currentTime = second
_self.currentTimeBlock(second)
})
}
case AVPlayer.Status.failed.rawValue:
do {
print("播放失败")
loading.stopAnimating()
}
default: do {
print("123")
}
}
}
//缓冲进度
if keyPath == "loadedTimeRanges" {
print("进度数据是--\(change?[NSKeyValueChangeKey(rawValue: "new")] ?? "")")
}
}
@objc func moviePlayDidEnd(userinfo: Notification) {
print("视频结束")
playerEndBlock()
if isRoop {
//跳转到 0
player?.seek(to: CMTime(value: 0, timescale: 5))
play()
}
}
}
AVPlayer: 就类似视频的控制者. 可以用来控制视频的播放,暂停,跳转等. 它的创建依赖于AVPlayerItem
player = AVPlayer(playerItem: playerItem)
//播放
player?.play()
//暂停
player?.pause()
//播放状态是否正在播放
var isPlaying: Bool {
get {
return player?.timeControlStatus == .playing
}
}
AVPlayerLayer: 用于视频的显示. 可以理解成一个 view. 它的创建依赖于AVPlayer
playerLayer = AVPlayerLayer(player: player)
//当前 view 将视频 layer 添加进去
layer.addSublayer(playerLayer ?? AVPlayerLayer())
简单总结: AVPlayer控制视频状态, AVPlayerLayer用于显示,AVPlayerItem提供视频信息
获取视频的长度 s
/*因为获取视频信息本身是个耗时操作,放在子线程去操作,totalTime 里面的赋值是在主线程的,这里封装过
简单解释一下: duration是 CMTime 类
public var value: CMTimeValue / * !@field value CMTime的值。值/时间表=秒。*/ 我们都知道视频都是一帧一帧凑起来的. value就是视频总共的帧数
public var timescale: CMTimeScale / * !@field timescale CMTime的时间刻度。值/时间表=秒。*/
timescale 表示 每一秒里面有多少帧
所以 value / timescale = 视频的长度(秒)*/
let queue = DispatchQueue(label: "asset")
queue.async {
let asset = AVAsset(url: self.videoUrl)
let totalTime = Int64(asset.duration.value) / Int64(asset.duration.timescale)
self.controlView.totalTime = totalTime
}
视频精准跳转.拖动 UISlider,监听 value 改变方法如下:
//为什么timescale要设置成 600 CMTime教程
@objc func sliderValueChanged(slider: UISlider) {
print("slider == \(slider.value)")
let time: Float64 = Float64(self.totalTime) * Float64(slider.value)
print("当前跳转的时间是\(time)")
let cmtime: CMTime = CMTime(value: CMTimeValue(time * 600), timescale: 600)
//需要使用下面的额方法进行定位, 后面两个参数设置容错范围
self.playerView?.player?.seek(to: cmtime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)
}