视频播放
AVFoundation 是 Objective-C 中创建及编辑视听媒体文件的几个框架之一,其提供了检查、创建、编辑或重新编码媒体文件的接口,也使得从设备获取的视频实时数据可操纵。
在 iOS 中,对于视频的播放我们会直接采用 AVFoundation。
AVAsset
AVAsset 是 AVFoundation 框架中的核心的类,它提供了基于时间的音视频数据(如电影文件、视频流),一个asset包含很多轨道的结合。
它是一个或者多个媒体数据的集合,描述的是整个集合的属性,如标题、时长、大小等,并且没有特定的数据格式。集合的每一个媒体数据都是统一的数据类型,称之为 track。简单的情况是一种数据是音频数据,一种是视频数据,而较复杂的情况是一种数据交织着音频和视频数据。
需要明白的是在 AVFoundation 中,初始化了 asset 及 track 后,并不意味着资源已经可用,因为若资源本身并不携带自身信息时,那么系统需要自己计算相关信息,这个过程会阻塞线程,所以应该使用异步方式进行获取资源信息后的操作。
NSURL *url = <#资源路径#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"duration",@"tracks"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
NSError *error = nil;
AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"tracks" error:&error];
//根据相应的属性状态进行对应的处理
switch (tracksStatus) {
case AVKeyValueStatusUnknown:
//TODO
break;
case AVKeyValueStatusLoading:
//TODO
break;
case AVKeyValueStatusLoaded:
//TODO
break;
case AVKeyValueStatusFailed:
//TODO
break;
case AVKeyValueStatusCancelled:
//TODO
break;
}
}];
AVFoundation 中可以使用 compositions 将多个媒体数据(video/audio tracks)合成为一个 asset ,这个过程中,可以添加或移除 tracks ,调整它们的顺序,或者设置音频的音量和变化坡度,视频容量等属性。这些媒体数据的集合保存在内存中,直到使用 export session 将它导出到本地文件中。另外,还可以使用 asset writer 创建 asset 。
AVPlayer
在开发中,视频资源一般直接使用 AVPlayer 进行播放,单纯的使用 AVPlayer 类是无法现实视频的播放的,要将视频添加到AVPlayerLayer中,这样才能将视频展示来。
// 媒体资源
NSURL *url = [NSURL URLWithString:@"url"];
AVAsset *asset = [AVAsset assetWithURL:url];
// 播放资源文件
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
// 播放器
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
// 播放界面
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame= self.view.bounds;
[self.view.layer addSublayer:playerLayer];
- AVPlayerItem 是一个视频资源的载体,承载着 AVAsset,AVPlayerItem实例来管理整个 Asset 的表示状态,然后通过 AVPlayer 进行播放
- AVPlayer 是一个控制器对象,你可以使用它来管理媒体资源的播放,例如启动和停止回放,以及查找特定的时间。您使用AVPlayer实例来播放单个资源。你也可以使用 AVQueuePlaye 对象按顺序播放许多项资源。
- AVPlayerLayer 视频画面展示图层,首先我们之所以能够看到视频是因为 AVPlayerLayer 帮我们把视频呈现出来了,可以说是 AVPlayerLayer 就是一个视频播放器的载体,它负责需要播放的画面。
应用例子
首先我们先准备承载播放界面的 View,可以通过传入 AVPlayer 进行初始化我们的播放界面:
@implementation PlayerView
// 将 view 本身的 layer 指定成 AVPlayerLayer
+ (Class)layerClass {
return [AVPlayerLayer class];
}
// 在初始化的时候,将传入的 AVPlayer,赋值给 AVPlayerLayer
- (instancetype)initWithPlayer:(AVPlayer *)player {
self = [super initWithFrame:CGRectZero];
if (self) {
self.backgroundColor = [UIColor blackColor];
[(AVPlayerLayer *) [self layer] setPlayer:player];
}
return self;
}
@end
在视图准备完成之后,可以进行初始化播放器的步骤:
NSURL *url = [[NSBundle mainBundle] URLForResource:@"cat.mp4" withExtension:nil];
self.asset = [AVAsset assetWithURL:url];
self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
self.playerView = [[PlayerView alloc] initWithPlayer:self.player];
[self.player play];
播放控制
通过调用 player 的 play 、pause 、setRate: 方法,可以控制 item 的播放,这些方法都会改变 player 的属性 rate 的值,该值为 1 表示 item 按正常速率播放,为 0 表示 item 暂停播放,0~1 表示低速播放,大于 1 表示高速播放,小于 0 表示从后向前播放。
这里我们可以通过监听 AVPlayer 的 status 属性,获取当前播放状态的变化:
// 通过KVO来观察status属性的变化,来获得播放之前的错误信息
[self.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = [change[NSKeyValueChangeNewKey]intValue];
switch (status) {
case AVPlayerItemStatusFailed:
//TODO
break;
case AVPlayerItemStatusReadyToPlay:
//TODO
break;
case AVPlayerItemStatusUnknown:
//TODO
break;
default:
break;
}
}
}
可以使用下面的方法监听播放时间的变化:
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
- (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(void))block;
切换 AVPlayer 的进度
//第二个方法能够进行更准确的跳转,但是需要进行额外的计算
- (void)seekToDate:(NSDate *)date;
- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter; (tolerance: 公差,前后公差)
//这两个方法传入了一个回调,当一个时间跳转请求被新的请求或其他操作打断时,回调也会被执行但是此时 finished 参数值为 NO
- (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0);
- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0);
如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。