张东轩的博客

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。

0%

iOS视频播放常用类

之前做过一些iOS视频边下边播的一些东西,现在抽时间整理一下关于视频播放的一些东西。

AVAsset

AVAsset是所有媒体资源的承载类,他定义了媒体资源的呈现形式,对媒体资源的读取、播放、处理都是在AVAsset上进行,其包含了资源的duration、preferredRate(默认音速)、preferredVolume(默认音量)、createTime等属性。
AVAsset是一个抽象类,我们一般使用其子类AVURLAsset和AVComposition。一般使用AVURLAsset对资源文件进行读取,使用AVComposition对资源进行编辑。

懒加载属性

对类似于视频这类资源,我们从AVAsset中获取tracks、duration等属性会比较耗时,如果在创建对象的时候就去加载这些属性可能会阻塞线程,所以这类属性使用了懒加载的方式,在第一次访问这类属性的时候可以使用AVFoundation提的了AVAsynchronousKeyValueLoading协议来异步加载属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:@"xxx.mp4"]];
NSString *tracksKey = @"tracks";
NSString *durationKey = @"duration";

__weak AVAsset *weakAsset = asset;
[asset loadValuesAsynchronouslyForKeys:@[tracksKey, durationKey] completionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];

if (status == AVKeyValueStatusLoaded) {
NSArray *audioTracks = [weakAsset tracksWithMediaType:AVMediaTypeAudio];
}

AVKeyValueStatus durationStatus = [weakAsset statusOfValueForKey:durationKey error:&error];
if (durationStatus == AVKeyValueStatusLoaded) {
Float64 duration = CMTimeGetSeconds(weakAsset.duration);
}

}
}];

AVAssetTrack

媒体资源会有多种组成部分,一般的视频至少有两个轨道,一个播放声音一个播放画面。AVAssetTrack中包括duration、rage、volume、trackId等属性。

可以用以下方法获取AVAssetTrack实例:

1
2
3
- (nullable AVAssetTrack *)trackWithTrackID:(CMPersistentTrackID)trackID;
- (NSArray<AVAssetTrack *> *)tracksWithMediaType:(NSString *)mediaType;
- (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;

AVPlayer

AVPlayer是iOS提供的一个强大的播放组件,其支持播放本地、分步下载或通过HLS协议得到的流媒体。其不能直接显示画面,需要创建一个播放器层AVPlayerLayer用于显示视频画面。

AVPlayerLayer

AVPlayerLayer构建于Core Animation之上,是AV Foundation为数不多的可见组件.

AVPlayerLayer扩展了Core Animation的CALayer类,并通过框架在屏幕上显示视频内容,这一图层并不提供任何可交互的控件.创建AVPlayerLayer需要一个AVPlayer实例指针,这就将图层和播放器绑定在一起,保证了二者间的时间同步.AVPlayerLayer可以和其他CALayer一样,添加到UIView或NSView的layer上.

AVPlayerLayer中可自定义的属性只有video gravity,用来设置视频的拉伸或缩放程度.

AVPlayerItem

一个媒体资源管理对象,是一个载体,承载AVAsset,然后通过AVPlayer进行播放,管理并保存着AVPlayer播放视频时的状态。
我们的目的是使用AVPlayer来播放AVAsset,AVAsset中只有时长,创建时间等静态信息,AVPlayerItem则有获取当前播放时间以及调节播放进度的方法。

动态对象

AVPlayerItem是一个动态对象,除了可以被我们改变的属性之外,许多read-only属性在准备播放和播放过程中被其相关联的AVPlayer所改变。可以使用KVO去监听这些变化,一个非常重要的属性是status,这个属性指示了什么时候可以播放。当我们刚创建出AVPlayerItem对象的时候这个属性的值是AVPlayerItemStatusUnknown表示媒体资源还没有被加载。关联AVPlayer对象之后会立马将AVPlayerItem对象对应的资源排队,毕竟开始为播放做准备。当status的值变为AVPlayerItemStatusReadyToPlay的时候表示我们可以准备播放。

如下代码指示了如何创建并注册监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)prepareToPlay {

NSURL *url = <#Asset URL#>
// Create asset to be played
asset = [AVAsset assetWithURL:url];
NSArray *assetKeys = @[@"playable", @"hasProtectedContent"];

// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = [AVPlayerItem playerItemWithAsset:asset automaticallyLoadedAssetKeys:assetKeys];

NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;

// Register as an observer of the player item's status property
[playerItem addObserver:self
forKeyPath:@"status"
options:options
context:&PlayerItemContext];

// Associate the player item with the player
player = [AVPlayer playerWithPlayerItem:playerItem];
}

然后当status发生变化时,我们需要实现如下如下方法以获得通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
// Only handle observations for the PlayerItemContext
if (context != &PlayerItemContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}

if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = AVPlayerItemStatusUnknown;
// Get the status change from the change dictionary
NSNumber *statusNumber = change[NSKeyValueChangeNewKey];
if ([statusNumber isKindOfClass:[NSNumber class]]) {
status = statusNumber.integerValue;
}
// Switch over the status
switch (status) {
case AVPlayerItemStatusReadyToPlay:
// Ready to Play
break;
case AVPlayerItemStatusFailed:
// Failed. Examine AVPlayerItem.error
break;
case AVPlayerItemStatusUnknown:
// Not ready
break;
}
}
}