写在前面
正文
播放 Assets
图2-1 播放asset多个播放器图层:你可以从单个
AVPlayer
实例创建许多AVPlayerLayer
对象,但只有最近创建的此类图层才会在屏幕上显示任何视频内容。
这种抽象意味着你可以同时使用不同的播放器播放一个给定的asset
,但每个播放器以不同的方式呈现。图2-2显示了一种可能性,两个不同的播放器使用不同的设置播放相同的asset
。例如,使用item tracks
可以在播放期间禁用特定track
(例如,你可能不想播放声音)。
你可以使用现有asset
初始化播放器item
,也可以直接从URL
初始化播放器item
,以便你可以在特定位置播放asset
(然后AVPlayerItem
将为resource
创建和配置asset
)。但是,与AVAsset一样,简单地初始化播放器项并不一定意味着它可以立即播放。你可以观察(使用键值观察)item
的状态属性,以确定它是否以及何时可以播放。
处理不同类型的Asset
配置asset
进行播放的方式可能取决于你要播放的asset
类型。从广义上讲,有两种主要类型:基于文件的assets
,你可以随机访问(例如来自本地文件,相机胶卷或媒体库),以及基于流的assets
(HTTP实时流式传输格式-简称HLS
)。
加载和播放基于文件的asset
。播放基于文件的asset
有几个步骤:
- 使用创建一个
asset
。 - 使用
asset
创建一个AVPlayerItem
实例。 - 将
item
与AVPlayer
实例相关联。 - 等到
item
的状态属性指示它已准备好播放(通常你使用KVO
来在状态更改时接收通知)。
创建和准备HTTP
实时流以进行播放。使用URL
初始化AVPlayerItem
的实例。 (你无法直接创建AVAsset
实例来表示HTTP Live Stream
中的媒体。)
NSURL *url = [NSURL URLWithString:@"<#Live stream URL#>];
// You may find a test stream at
self.playerItem = [AVPlayerItem playerItemWithURL:url];
[playerItem addObserver:self forKeyPath:@"status" options:0
context:&ItemStatusContext];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
当你将播放器item
与播放器关联时,它开始准备播放。当它准备好播放时,播放器item
将创建AVAsset
和AVAssetTrack
实例,你可以使用它们来检查实时流的内容。
注意:在播放器
item
上使用duration属性需要iOS 4.3或更高版本。与所有iOS版本兼容的方法涉及观察播放器item
的status属性。当状态变为AVPlayerItemStatusReadyToPlay
时,可以使用以下代码行获取持续时间:
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration]
如果你只想播放实时流,可以使用快捷方式直接使用URL
创建播放器,使用以下代码:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
如果你不知道自己的URL
类型,请按以下步骤操作:
- 尝试使用
URL
初始化AVURLAsset
,然后加载其跟踪键。
如果tracks
成功加载,则为asset
创建player item
。 - 如果上面的方式结果是失败的,请直接从
URL
创建AVPlayerItem
。
观察播放器的属性以确定它是否可播放。
如果任一路线成功,你最终得到一个player item
,然后你就可以与播放器进行关联了。
播放一个Item
- (IBAction)play:sender {
[player play];
}
改变播放速率
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
Seeking - 重新定位播放头
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
使用零容差可能需要框架解码大量数据。例如,如果你正在编写需要精确控制的复杂媒体编辑应用程序,则应使用zero
。
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#The player item#>]
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[player seekToTime:kCMTimeZero];
}
播放多个Items
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
监控playback
你可以监视播放器的演示状态和正在播放的播放器item
的多个方面。这对于不受你直接控制的状态更改特别有用。例如:
你可以使用KVO
来监视对这些属性值的更改。
响应状态变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == <#Player status context#>) {
AVPlayer *thePlayer = (AVPlayer *)object;
if ([thePlayer status] == AVPlayerStatusFailed) {
NSError *error = [<#The AVPlayer object#> error];
// Respond to error: for example, display an alert sheet.
return;
}
// Deal with other status change if appropriate.
}
// Deal with other change notifications if appropriate.
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
return;
}
跟踪视觉显示的准备情况
跟踪时间
使用这两种方法,AV Foundation
不保证为每个传递的间隔或边界调用block
。如果先前调用的block
的执行尚未完成,AV Foundation
不会调用block
。因此,你必须确保你在block
中执行的工作不会对系统过度占用。
// Assume a property: @property (strong) id playerObserver;
Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = @[[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird]];
self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
NSString *timeDescription = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, [self.player currentTime]));
NSLog(@"Passed a boundary at %@", timeDescription);
}];
到达Item的结尾
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
综述:使用AVPlayerLayer播放视频文件
这个简短的代码示例说明了如何使用AVPlayer
对象播放视频文件。它显示了如何:
- 配置视图以使用
AVPlayerLayer
层。 - 创建一个
AVPlayer
对象。 - 为基于文件的
asset
创建AVPlayerItem
对象,并使用KVO
来观察其状态 - 通过启用按钮响应
item
准备播放 - 播放该
item
,然后将播放器的头部恢复到起始位置。
注意:为了关注最相关的代码,此示例省略了完整应用程序的几个方面,例如内存管理和取消注册为观察者(用于键值观察或用于通知中心)。要使用
AV Foundation
,你应该有足够的经验使用·Cocoa·来推断缺失的部分。
播放器视图
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface PlayerView : UIView
@property (nonatomic) AVPlayer *player;
@end
@implementation PlayerView
+ (Class)layerClass {
return [AVPlayerLayer class];
}
- (AVPlayer*)player {
return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
[(AVPlayerLayer *)[self layer] setPlayer:player];
}
@end
View Controller示例
假设你有一个简单的视图控制器,声明如下:
@class PlayerView;
@interface PlayerViewController : UIViewController
@property (nonatomic) AVPlayer *player;
@property (nonatomic) AVPlayerItem *playerItem;
@property (nonatomic, weak) IBOutlet PlayerView *playerView;
@property (nonatomic, weak) IBOutlet UIButton *playButton;
- (IBAction)loadAssetFromFile:sender;
- (IBAction)play:sender;
- (void)syncUI;
@end
syncUI
方法将按钮的状态与播放器的状态同步:
- (void)syncUI {
if ((self.player.currentItem != nil) &&
([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
self.playButton.enabled = YES;
}
else {
self.playButton.enabled = NO;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
其余属性和方法在其余部分中会有描述。
创建Asset
- (IBAction)loadAssetFromFile:sender {
NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:<#@"VideoFileName"#> withExtension:<#@"extension"#>];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = @"tracks";
[asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:
^{
// The completion block goes here.
}];
}
当你将播放器item
与播放器关联时,可以触发播放器item
的准备。
// Define this constant for the key-value observation context.
static const NSString *ItemStatusContext;
// Completion handler block.
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded) {
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
// ensure that this is done before the playerItem is associated with the player
[self.playerItem addObserver:self forKeyPath:@"status"
options:NSKeyValueObservingOptionInitial context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
[self.playerView setPlayer:self.player];
}
else {
// You should deal with the error appropriately.
NSLog(@"The asset's tracks were not loaded:\n%@", [error localizedDescription]);
}
});
响应播放器Item的状态变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == &ItemStatusContext) {
dispatch_async(dispatch_get_main_queue(),
^{
[self syncUI];
});
return;
}
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
return;
}
播放Item
- (IBAction)play:sender {
[player play];
}
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.player currentItem]];
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[self.player seekToTime:kCMTimeZero];
}