引言

开发一个自定义的视频播放器时,我们会用到大量的AV*系列的类,本片博客将从一个比较高的层面入手介绍每个类在视频播放中所扮演的角色以及类与类之间的关系,后面还会继续深入分析具体的类和相关的API,并通过开发一个完全自定义的视频播放器来实际使用这些类。

AVAsset

AVAsset是一个抽象类,定义了媒体资源混合呈现的方式,将媒体资源的静态属性标题,时长,元数据等柔和成一个整体。有了它意味着我们在处理各种类型的媒体数据的时候我们面对的就只有资源这一个概念。

AVPlayer

整个播放都是围绕着AVPlayer类展开的,AVPlayer是一个播放媒体的控制器。支持本地媒体,分步下载媒体,并在多种播放场景中播放这些视频媒体资源。这里说的控制器并不是我们通常说的视图控制器(ViewController),而是一个对播放和资源时间相关信息进行管理的一个控制器,我们可以通过AVPlayer相关的API来开发控制器播放基于时间媒体的用户界面。

AVPlayer只能管理一个单独资源的播放,AVPlayer是一个不可见的组件,当播放视频媒体资源的时候我们需要借助AVPlayerLayer类来显示画面。

AVPlayerLayer

AVPlayerLayer是属于Core Animation框架中的特殊图层,继承自CALayer类,专门用作在屏幕上显示视频内容,它并不提供任何可视化的操作控件,它的作用就只是针对媒体内容进行渲染。

创建一个AVPlayerLayer需要一个AVPlayer类的实例,将图层和播放器紧密地绑定在一起。AVPlatyerLayer和其它CLayer一样可以直接设置为UIView的备用图层,也可以手动添加到已有的图层上。

AVPlayerLayer使用起来非常简单,我们可以设置的属性只有videoGravity,而videoGravity属性一共有三个不同的值,它用来确定在承载层的范围内视频内容的拉伸或缩放程度,类似与UIView的contentMode属性。

AVLayerVideoGravityResizeAspect:会在承载层的范围内缩放食品大小来保持视频的原始宽高比。这事在没其他设置情况下的默认值,适用于大部分情况。

AVLayerVideoGravityResizeAspectFill:将保留视频的宽高比,并使其通过缩放填满层的范围区域,通常会导致视频图片被部分剪裁

AVLayerVideoGravityResize:会将视频内容拉伸来匹配承载层的范围。这种情况最不常用,因为它通常会导致图片变形。

AVPlayerItem

AVPlayerItem会建立媒体资源动态视角的数据模型并保存AVPlayer在播放资源时的呈现状态,在这个类中我们会看到诸如seekToTime:的方法以及访问currentTime和presentationSize的属性。AVPlayerItem由一个或者多个媒体曲目组成。

播放示例

以上就是视频播放相关的核心类,简单了解过这些类的概念之后,我们通过一小段代码来看看如何播放保存在应用程序bundle中的视频资源。

import UIKit
import AVFoundation

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1. 获取url
        guard let url = Bundle.main.url(forResource: "waves", withExtension: "mp4")  else { return }
        //2. 创建AVAsset
        let asset = AVAsset(url: url)
        //3. 创建AVPlayerItem
        let playerItem = AVPlayerItem(asset: asset)
        //4. 创建player
        let player = AVPlayer(playerItem: playerItem)
        //5. 创建AVPlayerLayer
        let playerLayer = AVPlayerLayer(player: player)
        //6. 添加playerLayer
        playerLayer.frame = self.view.bounds
        self.view.layer.addSublayer(playerLayer)
    }
}

上面的代码对播放视频文件进行了最基本的设置,不过还不能实际播放视频,因为播放器的播放控件还没有做好准备。AVPlayerItem没有准备播放的界面,不过我们可以监听它的准备状态来主动进行播放。

AVPlayerItem有一个status属性,它的类型是AVPlayerItemStatus,当对象刚被创建的时候该熟悉是AVPlayerItemStatusUnKnown状态,表示当前媒体还没有载入不在播放队列中。将AVPlayerItem与一个AVPlayer对象进行关联就是将它放到了播放队列中,但是在可以播放前需要等待AVPlayerItem对象的状态由AVPlayerItemStatusUnknown转变为AVPlayerItemStatusReadyToPlay。我们通过KVO机制来观察status属性值的变化。

import UIKit
import AVFoundation

private var playerContext = 0

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1. 获取url
        guard let url = Bundle.main.url(forResource: "waves", withExtension: "mp4")  else { return }
        //2. 创建AVAsset
        let asset = AVAsset(url: url)
        //3. 创建AVPlayerItem
        let playerItem = AVPlayerItem(asset: asset)
        playerItem.addObserver(self, forKeyPath: "status", context: &playerContext)
        //4. 创建player
        let player = AVPlayer(playerItem: playerItem)
        //5. 创建AVPlayerLayer
        let playerLayer = AVPlayerLayer(player: player)
        //6. 添加playerLayer
        playerLayer.frame = self.view.bounds
        self.view.layer.addSublayer(playerLayer)
    }
    
    override class func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &playerContext {
            guard let playerItem:AVPlayerItem = object as? AVPlayerItem else { return }
            if playerItem.status == .readyToPlay {
             // 可以开始播放
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

当观察到播放控件的status变为AVPlayerItemStatusReadyToPlay的时候,就可以开始进行播放。

结语

通过这篇博客,我们初步认识了iOS中播放器的核心类,以及通过一个简单的播放实例感受了其基本功能。

在接下来的系列文章中,我们将深入挖掘播放功能的方方面面。我们将探讨更多高级功能,解析优化策略,以及介绍一些常见的问题和解决方案。

感谢你的阅读,希望你在这个系列中找到了对你有价值的信息。如果你有任何问题或建议,欢迎在评论区留言,我们期待在未来的文章中为你解答疑惑。敬请期待下一篇深度剖析!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐