AVFoundation-AVPlayer视频播放

AV Player是一个用来播放给予时间的视听媒体的控制器对象。将视频资源导出到界面需要使用AVPlayerLayer类;AVPlayer只管理一个单独资源的播放,当需要播放多个音频资源可以使用子类AVQueuePlayer

不需要自定义播放器的情况可以使用 MPMoviewPlayerController

参考 http://www.cnblogs.com/kenshincui/p/4186022.html#avPlayer

初始化

示例:

1
2
3
4
5
6
7
NSURL *assetURL = [[NSBundle mainBundle] URLForResource:@"waves" withExtension:@"mp4"];
AVAsset *asset = [AVAsset assetWithURL:assetURL];
AVPlayerItem *playeritem = [AVPlayerItem playerItemWithAsset:asset];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayer playerLayerWithPlayer:self.player];
//playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//视频填充模式
[self.view.layer addSubLayer:playerLayer];

对播放进行控制

监听播放状态

监听AVPlayerItem的status属性,播放项开始时status为 AVPlayerItemStatusUnknow,当状态改变为 AVPlayerItemStatusReadyToPlay才可以开始播放,只有处于这个状态时才能获得视频时长等信息。

1
2
3
4
5
6
7
8
9
10
11
12
//监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
AVPlayerItem *playerItem=object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
if(status==AVPlayerStatusReadyToPlay){
NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
}
}
}

监控网络加载情况属性

监听AVPlayerItem的loadedTimeRanges属性,当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//监控网络加载情况属性
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
AVPlayerItem *playerItem=object;
if([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray *array=playerItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
NSLog(@"共缓冲:%.2f",totalBuffer);
//
}
}

时间监听

AVPlayer提供了两种时间监听方法

定期监听

使用- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//每秒
CMTime interval = CMTimeMake(1.0, 1.0); // 1

// Main dispatch queue
dispatch_queue_t queue = dispatch_get_main_queue(); // 2

// Create callback block for time observer
__weak THPlayerController *weakSelf = self; // 3
void (^callback)(CMTime time) = ^(CMTime time) {
NSTimeInterval currentTime = CMTimeGetSeconds(time);
NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);
NSLog(@"当前已经播放%.2fs.", currentTime); // 4
};

// Add observer and store pointer for future use
self.timeObserver = // 5
[self.player addPeriodicTimeObserverForInterval:interval
queue:queue
usingBlock:callback];

边界时间监听

使用 - (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(void))block;

  • times : CMTime值组成的NSArray数组,定义了需要通知的边界点
  • queue : 调度队列,指定NULL等同设置主队列
  • block : 回调块

监听播放完成通知

注册 AVPlayerItemDidPlayToEndTimeNotification的通知

1
2
3
4
5
6
    //给AVPlayerItem添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];

-(void)playbackFinished:(NSNotification *)notification{
NSLog(@"视频播放完成.");
}

生成视频缩略图

使用 AVAssetImageGenerator类可以从 AVAsset视频中提取图片。

AVAssetImageGenerator有两个可以检索图片的方法:

  • - (void)generateCGImagesAsynchronouslyForTimes:(NSArray<NSValue *> *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler;
    • 生成一组图片
  • - (nullable CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(nullable CMTime *)actualTime error:(NSError * __nullable * __nullable)outError CF_RETURNS_RETAINED;
    • 在指定时间捕捉图片

生成一组图片示例:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	self.imageGenerator =                                                   // 1
[AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];

//默认情况下捕捉的图片都是原始尺寸,设置(200.0f, 0.0f)可以让图片宽度固定,高度自适应
self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f); // 2

CMTime duration = self.asset.duration;

//生成视频捕捉位置的CMTime集合
NSMutableArray *times = [NSMutableArray array]; // 3
CMTimeValue increment = duration.value / 20;
CMTimeValue currentValue = 2.0 * duration.timescale;
while (currentValue <= duration.value) {
CMTime time = CMTimeMake(currentValue, duration.timescale);
[times addObject:[NSValue valueWithCMTime:time]];
currentValue += increment;
}

__block NSUInteger imageCount = times.count; // 4
__block NSMutableArray *images = [NSMutableArray array];

AVAssetImageGeneratorCompletionHandler handler; // 5

//requestedTime:请求的时间对应times数组中的值;
//imageRef :生成的CGImageRef,若该时间点没有图片则为NULL
//actualTime:图片实际生成的时间
//result:表示生成图片失败还是成功
handler = ^(CMTime requestedTime,
CGImageRef imageRef,
CMTime actualTime,
AVAssetImageGeneratorResult result,
NSError *error) {

if (result == AVAssetImageGeneratorSucceeded) { // 6
UIImage *image = [UIImage imageWithCGImage:imageRef];
id thumbnail =
[THThumbnail thumbnailWithImage:image time:actualTime];
[images addObject:thumbnail];
} else {
NSLog(@"Error: %@", [error localizedDescription]);
}

// If the decremented image count is at 0, we're all done.
if (--imageCount == 0) { // 7
dispatch_async(dispatch_get_main_queue(), ^{
NSString *name = THThumbnailsGeneratedNotification;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:name object:images];
});
}
};

[self.imageGenerator generateCGImagesAsynchronouslyForTimes:times // 8
completionHandler:handler];

显示字幕(视频自带)

显示字幕需要用到AVMediaSelectionGroupAVMediaSelectionOption两个类

  1. 初始化 AVAsset 的时候加上 availableMediaCharacteristicsWithMediaSelectionOptions 属性
    • AVMediaSelectionOption的该属性会返回一个包含字符串的数组,这些字符串表示可用的媒体特性
      • AVMediaCharacteristicLegible (字幕或者隐藏式字幕)
      • AVMediaCharacteristicAudible (音频)
      • AVMediaCharacteristicVisual (字幕)
  2. 请求可用的媒体特性数据后,调用 AVAssetmediaSelectionGroupForMediaCharacteristic:方法为其传递需要的媒体特性,会返回 AVMediaSelectionGroup ,表示包含的备用媒体轨道

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
      NSString *mc = AVMediaCharacteristicLegible;                            // 1
    AVMediaSelectionGroup *group =
    [self.asset mediaSelectionGroupForMediaCharacteristic:mc]; // 2
    if (group) {
    NSMutableArray *subtitles = [NSMutableArray array]; // 3
    for (AVMediaSelectionOption *option in group.options) {
    NSLog(@" %@ ",option.displayName);
    }
    }

    //输出 ,表示多个字幕轨道
    @"English"
    @"English Forced"
    @"Italian"
    @"Italian Forced"
    @"Portuguese"
    @"Portuguese Forced"
    @"Russian"
    @"Russian Forced"
  3. 显示字幕,通过调用 AVPlayerItemselectMediaOption:inMediaSelectionGroup:方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (void)subtitleSelected:(NSString *)subtitle {
    NSString *mc = AVMediaCharacteristicLegible;
    AVMediaSelectionGroup *group =
    [self.asset mediaSelectionGroupForMediaCharacteristic:mc]; // 1
    BOOL selected = NO;
    for (AVMediaSelectionOption *option in group.options) {
    if ([option.displayName isEqualToString:subtitle]) {
    [self.playerItem selectMediaOption:option // 2
    inMediaSelectionGroup:group];
    }
    }

    }