AVPlayer
是一个用来播放基于时间的视听媒体的控制器对象。支持播放从本地、分布下载或通过HTTP Live Streaming协议取得的流媒体。
AVPlayer
是一个不可见组件。要将视频资源导出到用户界面上,需要使用AVPlayerLayer
类。
AVPlayer
只管理一个单独资源的播放,不过框架还提供了AVPlayer的一个子类AVQueuePlayer
,可以用来管理一个资源队列。
创建一个AVPlayerLayer
需要一个指向AVPlayer
实例的指针,这就将图层和播放器紧密绑定在一起。
AVPlayerLayer
有一个videoGravity
属性,可以定义三个不同的gravity值:
AVLayerVideoGravityResize
- 会将视频内容拉伸来匹配承载层的范围AVLayerVideoGravityResizeAspect
- 会在承载层的范围内缩放视频大小来保持视频的原始宽高比AVLayerVideoGravityResizeAspectFill
- 将保留视频的宽高比,并使其通过缩放填满层的范围区域AVAsset
是一个抽象类和不可变类,定义了媒体资源混合呈现的方法,将媒体资源的静态属性模块化成一个整体,比如它们的标题、时长和元数据等。
根据AVAsset
获取标题,需要用到Common键空间:
@implementation AVAsset (THAdditions)
- (NSString *)title
{
AVKeyValueStatus status = [self statusOfValueForKey:@"commonMetadata" error:nil];
if (status == AVKeyValueStatusLoaded) {
NSArray *items = [AVMetadataItem metadataItemsFromArray:self.commonMetadata
withKey:AVMetadataCommonKeyTitle
keySpace:AVMetadataKeySpaceCommon];
if (items.count > 0) {
AVMetadataItem *titleItem = [items firstObject];
return (NSString *)titleItem.value;
}
}
return nil;
}
@end
AVAsset
模型只包含媒体资源的静态信息,这些不变的属性用来描述对象的静态状态。当我们需要对一个资源及其相关曲目进行播放时,首先需要通过AVPlayerItem
和AVPlayerItemTrack
类构建相应的动态内容。
AVPlayerItem
由一个或多个媒体曲目组成,由AVPlayerItemTrack
类建立模型。AVPlayerItemTrack
实例用于表示播放器条目中的类型统一的媒体流,比如音频或视频。AVPlayerItem
中的曲目直接与基础AVAsset
中的AVAssetTrack实例相对应。
NSURL *assetURL = [[NSBundle mainBundle] URLForResource:@"hubblecast" withExtension:@"m4v"];
AVAsset *asset = [AVAsset assetWithURL:assetURL];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self forKeyPath:@"status" options:0 context:&PlayerItemStatusContext];
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[playerLayer setFrame:self.view.bounds];
[self.view.layer addSublayer:playerLayer];
AVPlayerItem
具有一个名为status
的AVPlayerItemStatus
类型的属性。在对象创建之初,status
为AVPlayerItemStatusUnknown
。需要通过KVO进制监测status
属性值的来跟踪这一变化过程。需要等待状态由AVPlayerItemStatusUnknown
变为AVPlayerItemStatusReadyToPlay
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == &PlayerItemStatusContext) { AVPlayerItem *playerItem = (AVPlayerItem *)object; if (playerItem.status == AVPlayerItemStatusReadyToPlay ) { [self.player play];
}
}
}
获取时间duration,返回类型为CMTime
,CMTimeGetSeconds
函数将CMTime
的值转换为秒。
CMTime duration = self.playerItem.duration;
AVPlayer
提供了两种基于时间的监听方法,让应用程序可以对时间变化进
行精准的监听。
定期监听
方法为- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
- (void)addPlayerItemTimeObserver {
// Create 0.5 second refresh interval - REFRESH_INTERVAL == 0.5
CMTime interval =
CMTimeMakeWithSeconds(REFRESH_INTERVAL, NSEC_PER_SEC); // 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);
[weakSelf.transport setCurrentTime:currentTime duration:duration]; // 4
};
// Add observer and store pointer for future use
self.timeObserver = // 5
[self.player addPeriodicTimeObserverForInterval:interval
queue:queue
usingBlock:callback];
}
边界时间监听
方法为- (id)addBoundaryTimeObserverForTimes:(NSArray *)times queue:(dispatch_queue_t)queue usingBlock:(void (^)(void))block;
主要用于同步用户界面变更或随着视频播放记录一些非可视化数据。比如,可以定义25%、50%和75%边界的标记,以此判断用户播放进度。
条目结束监听
另一常见的需要监听的事件是条目播放完毕的时间。当播放完成时,AVPlayerItem
会发送一个AVPlayerItemDidPlayToEndTimeNotification
通知。
- (void)addItemEndObserverForPlayerItem {
NSString *name = AVPlayerItemDidPlayToEndTimeNotification;
NSOperationQueue *queue = [NSOperationQueue mainQueue];
__weak THPlayerController *weakSelf = self; // 1
void (^callback)(NSNotification *note) = ^(NSNotification *notification) {
[weakSelf.player seekToTime:kCMTimeZero // 2
completionHandler:^(BOOL finished) {
[weakSelf.transport playbackComplete]; // 3
}];
};
self.itemEndObserver = // 4
[[NSNotificationCenter defaultCenter] addObserverForName:name
object:self.playerItem
queue:queue
usingBlock:callback];
}
AVAssetImageGenerator
可以用来从一个AVAsset
视频曲目中提取图片。
- (CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(CMTime *)actualTime error:(NSError **)outError
如果开发者希望捕捉一张图片,此方法最适合,可能用于在视频列表中展示视频缩略图- (void)generateCGImagesAsynchronouslyForTimes:(NSArray *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler;
按照第一个参数所指定的时间段生成一个图片序列。- (void)generateThumbnails {
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];
// 指定一个width值为200、height值为0的CGSize,确保生产的图片都遵循一定的宽度
//并且会根据视频的宽高比自动设置高度值
self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f);
//计算生成CMTime值的集合
CMTime duration = self.asset.duration;
NSMutableArray *times = [NSMutableArray array];
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;
__block NSMutableArray *images = [NSMutableArray array];
//回调块
AVAssetImageGeneratorCompletionHandler handler;
handler = ^(CMTime requestedTime,
CGImageRef imageRef,
CMTime actualTime,
AVAssetImageGeneratorResult result,
NSError *error) {
if (result == AVAssetImageGeneratorSucceeded) {
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) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *name = THThumbnailsGeneratedNotification;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:name object:images];
});
}
};
[self.imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:handler];
}