引言
现在播放器已经拥有了核心的播放功能,包括基本的播放和暂停控制功能,以及通过拖拽进度条实现快进功能。然而,我们注意到在视频播放的过程中,播放进度并没有显示出来,当前时间也没有发生任何变化。这显然不能令用户满意。下面我们就来实现播放进度的同步功能,使用户更加方便地掌控视频的播放,提升使用体验。
实现方案
AVPlayer提供了两种基于时间的监听方法,让应用程序可以对时间变化进行精准的监听。
定期监听
利用AVPlayer提供的addPeriodicTimeObserver(forInterval:, queue:, using:)方法可以实现以一定时间间隔获得通知。该方法有如下三个参数:
interval:用于注定通知周期的CMTime。
queue:通知发送的顺序调度队列。大多数时候我们会指定在主队列。
block:在指定的时间间隔中会在指定的队列上调用的block,这个block会回传一个CMTime表示播放器的当前时间。
边界时间监听
还有一种更具针对性的方法来监听时间,我们可以得到播放器时间轴中多个边界点的遍历结果。比如我们可定义25%,50%的边界标记,来判断用户播放进度。这个功能需要用到addBoundaryTimeObserver(forTimes:, queue: , using: )方法,该方法参数如下:
times:CMTime值组成一个NSArray数组定义了需要通知的边界点。
queue:与定期监听类似,为方法提供一个用来发送通知的顺序调度队列。
block:每当正常播放中跨越一个边界点时就会在队列中调用这个回调块。但该块不提供遍历的CMTime值,所以我们需要为此执行一些额外计算进行确定。
我们就采用定期监听的方案,将时间间隔设置为0.5来监听播放器的进度。然后通过代理将进度来同步到播放页面的PHControlView。
/// 监听播放进度func addPlayerItemTimeObserver() {guard let player = player else { return }let interval = CMTimeMakeWithSeconds(0.5, preferredTimescale: Int32(NSEC_PER_SEC))let queue = DispatchQueue.maintimeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: queue, using: {[weak self] time inguard let self = self else { return }guard let playerItem = self.playerItem else { return }guard let delegate = self.delegate else { return }let currentTime = CMTimeGetSeconds(time)let duration = CMTimeGetSeconds(playerItem.duration)delegate.setCuttentTime(time: currentTime, duration: duration)})}
代理方法在PHControlView的实现,来同步进度条的进度,以及播放时间信息。
//MARK: PHControlDelegate/// 设置时间func setCuttentTime(time: TimeInterval, duration: TimeInterval) {currentTimeLabel.text = timeString(from: time)totalTimeLabel.text = timeString(from: duration)sliderView.minimumValue = 0.0sliderView.maximumValue = Float(duration)sliderView.value = Float(time)}
在监听到播放资源准备完成开始播放的时候调用监听播放进度的方法。
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {if context == &playerItemContext {guard let playerItem = playerItem else { return }guard let player = player else { return }if playerItem.status == .readyToPlay{playerItem.removeObserver(self, forKeyPath: status_keypath)player.play()let duration = playerItem.durationself.delegate?.playstart()self.delegate?.setCuttentTime(time: 0.0, duration: CMTimeGetSeconds(duration))addPlayerItemTimeObserver()}} else {super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)}}
现在在运行代码,我们就能发现播放进度的同步功能就已经完成了。
结语
同步播放进度的整体代码并不复杂,值得注意的是我们不能使用KVO来监听播放器的播放时间,因为这些信息需要非常高的精确度,比键值监听要求要高。更不能开始定时器自己来处理播放进度的时间,会产生很大的误差。
现在播放器已经与大多数视频播放器一样,具备了所有的播放器功能。但视频的标题,视频的字幕还没有显示出来,下一篇我们来处理这两组元数据,将标题和字幕显示到播放器中。
项目地址:PHPlayer: 视频播放器