为了控制 asset 的播放,你要使用 AVPlayer 对象。在播放期间,你可以使用 AVPlayerItem 实例管理 asset 的整体呈现状态,使用 AVPlayerItemTrack 对象管理独立轨道的呈现状态。为了播放视频,你使用 AVPlayerLayer 对象
播放 Asset
一个播放器是你用来管理 asset 播放的控制器, 例如开始,停止和跳转到特定的时间点。你使用 AVPlayer 对象去播放单个 asset。 也可以使用
AVQueuePlayer 对象播放一系列的 asset (AVQueuePlayer 是AVPlayer 的子类)。在OS X上,您可以选择使用AVKit框架的AVPlayerView类来播放视图中的内容。
播放器提供有关播放状态的信息,因此,如果需要,可以将用户界面与播放器的状态同步。通常将播放器的输出引导 Core Animation layer(AVPlayerLayer 或 AVSynchronizedLayer 的实例,AVPlayerLayer和
AVSynchronizedLayer 都是CALayer的子类,属于 Core Animation)。要了解更多关于图层的信息,参考: Core Animation Programming Guide
多个播放器测layer:可以从单个 AVPlayer 实例创建多个 AVPlayerLayer 对象,但是只有最近创建的层才能在屏幕上显示视频内容。
虽然你想播放一个 asset,但是不能直接提供 asset 给 AVPlayer 对象。要提供一个 AVPlayerItem 实例。player item 管理与其相关联的asset 的呈现状态。player item 包含了轨道——AVPlayerItemTrack 实例——对应于 asset 中的轨道。各种对象之间的关系如图2-1所示。
-
Figure 2-1 Playing an asset
这个抽象意味着你可以同时使用不同的 AVPlayer 对象播放一个特定的 asset,但是每个 AVPlayer 对象都以不同的方式呈现。Figure 2-2 展示以一种可能性,两个不同的 AVPlayer 对象使用不同的设置播放同一个 asset,例如使用 AVPlayerItem 你可以在播放过程中禁用特定的 tracks(例如,你可能不想播放声音组件)。
- Figure 2-2 以不同的方式播放同一个 asset
你可以使用已存在的 asset 初始化 AVPlayerItem,或者也可以直接通过一个URL初始化 AVPlayerItem以便在特定位置播放资源(AVPlayerItem 稍后将会为资源创建并且配置一个 asset 对象)。与 Asset 一样简单地初始化 AVPlayerItem 并不一定意味着它可以立即播放。您可以使用KVO观察一个 AVPlayerItem 对象的status,以确定是否以及何时准备播放。
处理不同类型的 Asset
配置 asset 进行播放的方式可能取决于您想要播放的 asset 种类。大体上讲,有两种主要的类型:基于文件的 asset,你可以随机访问(例如本地文件,相机胶卷或媒体库)和基于流的 asset (HTTP Live Streaming格式)。
为了加载并播放一个基于文件的 asset。需要以下几步:
- 使用AVURLAsset 创建一个 asset
- 使用 asset 创建一个 AVPlayerItem 实例
- 将 AVPlayerItem 实例与 AVPlayer 实例关联
- 等待 AVPlayerItem 的属性 status 指示注备好播放了(一般使用 KVO 接受 status 改变的通知)
Putting It All Together: Playing a Video File Using AVPlayerLayer 指示了这种方式
创建和准备 HTTP live stream 进行播放。使用 URL 初始化一个 AVPlayerItem 实例 (不能直接创建AVAsset实例来表示 HTTP Live Stream 中的媒体)??? 为什么不能:
NSURL *url = [NSURL URLWithString:@"<#Live stream URL#>];
// You may find a test stream at <http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8>.
self.playerItem = [AVPlayerItem playerItemWithURL:url];
[playerItem addObserver:self forKeyPath:@"status" options:0 context:&ItemStatusContext];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
当你将这个 AVPlayerItem 实例与 AVPlayer 实例关联之后,它开始准备播放。当它准备好播放之后,AVPlayerItem 实例创建 AVAsset 和 AVAssetTrack 实例,可以用来播放 HTTP live stream。
要获取 HTTP live stream 的时长,你可以 KVO 观察 AVPlayerItem 的 duration 属性。当 AVPlayerItem 注备好播放的时候,这个属性更新为正确的值。
注意:使用 AVPlayerItem 的 duration 属性,要求 iOS 4.3 以上版本。与所有版本的iOS兼容的方法涉及观察AVPlayerItem 的 status 属性。当 status 变为 AVPlayerItemStatusReadyToPlay 的时候,可以通过如下代码获取 duration
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
如果你只是想简单的播放流媒体,则可以使用以下代码直接使用URL创建快捷方式并创建播放器:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
和 asset,AVPlayerItem 一样,初始化 player 并不意味着它可以立即播放。应该观察 player 的 status 属性,当变为
AVPlayerStatusReadyToPlay 的时候才可以播放。也可以通过观察 player 的 currentItem 属性获取为当前流创建的 AVPlayerItem 实例。
如果你不知道 URL 是哪种类型,遵循以下步骤:
- 尝试使用 URL 初始化 AVURLAsset ,然后获取他的 tracks,如果获取成功,那么你就可以为这个 asset 创建 AVPlayerItem 实例了。
- 如果1失败了,直接使用 URL 创建 AVPlayerItem 实例,观察属性 status 的变化以决定它是否可以播放了。
如果任何一个成功了,就可以将 AVPlayerItem 实例与 AVPlayer 实例绑定了。
播放一个 Item
为了开始播放,你要向 AVPlayer 实例发送一条 play 消息。
- (IBAction)play:sender {
[player play];
}
除了简单的播放之外,还可以管理播放的各个方面,如播放的速率和位置。你可以监听 player 的播放状态,一般来说这对于同步播放状态与用户界面是非常有用的,参考:Monitoring Playback 。
改变播放速度
可以通过将更改 player 的 rate 属性来改变播放的速度:
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
值为1.0意味着正常速度播放,设置速度为 0.0 意味着暂定,也可以使用 pause 暂停
支持反向Items 可以将速度设置为负值使得播放回退。你可以通过使用playerItem属性 canPlayReverse(支持-1.0的速率值),canPlaySlowReverse(支持0.0到-1.0之间的速率)和 canPlayFastReverse(支持小于-1.0的速率值)来确定支持的反向播放类型。
寻找 - 重新定位播放头
要将播放头移动到特定的时间,通常会使用 seekToTime:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];
seekToTime:
方法而不是精度调整,如果你需要精度调整,要使用:seekToTime:toleranceBefore:toleranceAfter:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
宽容度设置为0可能需要框架解码大量数据。一般只有在编写需要精确控制的复杂媒体编辑应用程序时,才应使用零。
播放完之后,player 的头部被设置到该 playerItem 的结尾并且进一步的播放调用不起作用。要将播放头放到该
item 的开始处,你可以注册接收这个item的 AVPlayerItemDidPlayToEndTimeNotification 通知。在通知的回调方法里,发送 seekToTime: 消息,参数设置为kCMTimeZero
// 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
您可以使用 AVQueuePlayer 对象按顺序播放多个 items。AVQueuePlayer 是 AVPlayer 的子类。使用 player items 的数组初始化一个 AVQueuePlayer 实例
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
可以像 AVPlayer 对象一样使用 play 方法播放视频。 queue player 将依次播放每个player item。如果想要跳过下个item,向 queue player 对象发送 advanceToNextItem 消息。
你可以使用insertItem:afterItem:,removeItem: 和removeAllItems 修改队列。当插入一个新的 item 到队列中的时候,应该使用 canInsertItem:afterItem: 检查是否能被插入。如果第二个参数传递 nil 的话是测试新的 item 能否被添加到对尾。
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
监控播放
可以监控 player 和正在被播放的 player item 的多个方面。这对于不受你直接控制的状态更改非常有用。例如:
- 如果用户使用多任务,切换到别的app,player 的 速度将会被置为 0.0
- 如果播放一个远端媒体,player item的 loadedTimeRanges 和seekableTimeRanges 属性将随着更多数据可用而更改。这些属性告诉你player item的时间线的哪些部分是可用的。
- player 的 currentItem 属性切换为一个为 HTTP live stream 创建的 player item
- 当播放一个 HTTP live stream 的时候 player item的 tracks 可能会改变,如果流为内容提供了不同的编码,则可能发生这种情况;如果 player 切换到不同的编码,tracks 会改变。
- 如果播放失败,player 或者 player item 的 status 属性会改变
你可以使用 KVO 监控这些属性值的改变
要在主线程注册和注销 KVO 监听通知。这样可以避免在另一个线程正在进行更改时收到部分通知的可能性。AV Foundation在主线程调用 observeValueForKeyPath:ofObject:change:context: ,即使是在其他线程发生的操作改变。
应对状态变化
当 player 或者 player item 的 status 变化的时候,将会触发 KVO 通知。如果一个对象因为一些原因不能播放了(例如媒体服务被重置了),status将根据情况变为 AVPlayerStatusFailed 或者AVPlayerItemStatusFailed 。这时候,对象的 error 属性将是一个 error 对象,描述了不能播放的原因。
AV Foundation不指定发送通知的线程。如果要更新用户界面,则必须确保在主线程上调用任何相关的代码。这个例子使用dispatch_async在主线程上执行代码。
- (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;
}
监控视觉显示的准备状态
可以通过观察 AVPlayerLayer 对象的 readyForDisplay 属性,在 player 有用户可见内容时得到通知。特别是,只有当有东西供用户查看然后执行转换时,才可以将播放器图层插入到图层树中。
监控时间
要跟踪AVPlayer对象中播放头位置的变化,你可以使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者ddBoundaryTimeObserverForTimes:queue:usingBlock: 。例如可以使用有关已用时间或剩余时间的信息来更新用户界面,或者执行其他用户界面同步。
addPeriodicTimeObserverForInterval:queue:usingBlock: 如果时间跳转,开始播放或停止播放,则按您指定的时间间隔调用您提供的block。
addBoundaryTimeObserverForTimes:queue:usingBlock: 传递一个包含在NSValue对象中的CMTime结构数组。无论何时遍历任何时间,提供的 block 都会被调用。
这两个方法都返回一个作为观察者的不透明对象。只要你希望 player 调用时间观察块,就必须保持对返回对象的强引用。想注销观察的时候需要调用 removeTimeObserver: 传递对应的 观察者。
使用这两种方法,AV Foundation不保证在每个间隔或边界通过时调用您的块。如果先前调用的块的执行尚未完成,则AV Foundation不调用块。因此,您必须确保您在该区域执行的工作不会对系统的负担过重。
// 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 的结尾
你可以注册一个 AVPlayerItemDidPlayToEndTimeNotification 通知,监听播放结束。
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
使用 AVPlayerLayer 播放视频
这个简短的代码示例说明了如何使用AVPlayer对象播放视频文件。如下:
- 配置一个 view 使用 AVPlayerLayer 作为底层layer
- 生成一个 AVPlayer 对象
- 为基于文件的 asset 创建一个 AVPlayerItem 对象,然后使用 KVO 观察 item 的 status
- 通过启用按钮能够点击来响应 item 准备好播放了
- 播放 item 结束后将 player 的头部放置到开始处
为了专注相关代码,这个例子从完整实例中删除了几个方面,例如内存管理,注销 KVO 观察。为了使用 AV Foundation,希望你有足够的 cocoa 编程经验以便补足遗漏的代码
有关播放的概念介绍,查看 Playing Assets
播放视图
为了播放一个 asset 的可见部分,需要一个 AVPlayerLayer 层的视图作为 AVPlayer 对象的输入。你可以创建一个简单的 UIView 的子类,如下:
#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
简单的视图控制器
假设你有一个简单的视图控制器,声明如下:
@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
方法同步player 和 button 的状态:
- (void)syncUI {
if ((self.player.currentItem != nil) &&
([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
self.playButton.enabled = YES;
}
else {
self.playButton.enabled = NO;
}
}
可以在视图控制器的viewDidLoad方法中调用syncUI,以确保在首次显示视图时保持一致的用户界面:
- (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
其他属性和方法在其余部分中进行了介绍。
创建一个 Asset
你可以利用 URL 创建一个 AVURLAsset 对象,(以下示例假定您的项目包含合适的视频资源):
- (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.
}];
}
在完成block里,创建一个 AVPlayerItem 实例,并且将 player view 设置为播放器视图。和创建 asset 一样,简单的创建了 player item 并不意味着它立即可用。为了确定是否准备好了,可以观察他的 status 属性。应该在将 player item 和 player 关联起来之前配置 layer item 的观察者。
当将 player item 和 player 关联起来的时候触发 player 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]);
}
});
响应 Player Item status 的改变
当 player item 的 status 改变的时候, 视图控制器会收到 KVO 通知。AV Foundation不会指定通知发送的具体线程。如果你想更新用户界面,你必须确保相关代码是在主线程被执行。这个例子使用 dispatch_async 保证在主线程更新用户界面。
- (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
向 player 发送 play 消息来播放 item
- (IBAction)play:sender {
[player play];
}
item 只播放一次。播放之后,player 的头部被设置为该 item 的结尾,并且进一步的播放方法的调用将不起作用。要将播放头放回到 item 的开头,你可以监听 item 的 AVPlayerItemDidPlayToEndTimeNotification