AVF 6 - 视频播放
一、相关说明
1、相关类
- AVPlayerItem,相比 AVAsset(描述资源静态数据),可以描述资源的动态数据,如播放状态等。
- AVPlayer
- AVPlayerLayer
2、精细监听
- 定期监听
- 边界时间监听
- 条目结束监听
二、使用
1、播放视频
// AVPlayerItem's status property
#define STATUS_KEYPATH @"status"
// Refresh interval for timed observations of AVPlayer
#define REFRESH_INTERVAL 0.5f
// Define this constant for the key-value observation context.
static const NSString *PlayerItemStatusContext;
@interface VideoPlayer ()
@property (strong, nonatomic) AVAsset *asset;
@property (strong, nonatomic) AVPlayerItem *playerItem;
@property (strong, nonatomic) AVPlayer *player;
@property (nonatomic, strong) NSWindow *window;
@end
@implementation VideoPlayer
- (void)setup{
NSString *fileName = @"ElephantSeals.mov";
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:fileName
withExtension:nil];
self.asset = [AVAsset assetWithURL:fileURL];
NSArray *keys = @[
@"tracks",
@"duration",
@"commonMetadata",
@"availableMediaCharacteristicsWithMediaSelectionOptions"
];
self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset
automaticallyLoadedAssetKeys:keys]; // 自动异步载入
[self.playerItem addObserver:self
forKeyPath:STATUS_KEYPATH
options:0
context:&PlayerItemStatusContext];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
// 创建 playerLayer 存放资源内容
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
playerLayer.frame = NSMakeRect(0, 0, 300, 300);
playerLayer.backgroundColor = [NSColor yellowColor].CGColor;
NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
self.window = [[NSWindow alloc]initWithContentRect:CGRectMake(0, 0, 400, 400) styleMask:style backing:NSBackingStoreBuffered defer:YES];
self.window.contentView.wantsLayer = YES;
self.window.contentView.layer.backgroundColor = [NSColor cyanColor].CGColor;
[self.window.contentView.layer addSublayer:playerLayer];
[self.window orderFront:nil];
self.window.title = @"123";
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playOverNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
}
#pragma mark - 播放完成
- (void)playOverNotification:(NSNotification *)notification {
[self.player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
// self.playButton.selected = NO;
// self.timeLabel.text = @"0/0";
}];
}
#pragma mark - 监听播放状态
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == &PlayerItemStatusContext) {
dispatch_async(dispatch_get_main_queue(), ^{ // 1
[self.playerItem removeObserver:self forKeyPath:STATUS_KEYPATH];
if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) {
CMTime duration = self.playerItem.duration;
[self.player play];
} else {
NSLog(@"Failed to load video");
}
});
}
}
2、时间监听
#pragma mark - Time Observers
// 更精确的时间监听
// 定期监听
- (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];
}
// 条目结束监听
- (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];
}
3、生成缩略图
#pragma mark - Thumbnail Generation
- (void)generateThumbnails {
self.imageGenerator = // 1
[AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];
// Generate the @2x equivalent
self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f); // 2
CMTime duration = self.asset.duration;
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
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];
}