Playback
要控制资源的播放,请使用AVPlayer对象。在回放过程中,您可以使用AVPlayerItem实例管理整个资源的呈现状态,并使用AVPlayerItemTrack对象来管理单个轨道的呈现状态。要显示视频,请使用AVPlayerLayer对象。
一.Playing Assets(播放资源)
播放器是用于管理资产播放的控制器对象,例如启动和停止播放以及查找特定时间。您使用AVPlayer的一个实例来播放单个资源。您可以使用AVQueuePlayer对象按顺序播放多个资源(AVQueuePlayer是AVPlayer的一个子类)。在OS X上,您可以选择使用AVKit框架的AVPlayerView类在视图内播放内容。 播放器为您提供有关播放状态的信息,因此,如果需要,您可以将用户界面与播放器的状态同步。您通常将播放器的输出引导至专用的Core Animation(核心动画)层(AVPlayerLayer或AVSynchronizedLayer的实例)。要了解更多关于图层的信息,请参阅Core Animation Programming Guide.。
虽然最终你想播放一个资源,但你不直接提供资源给AVPlayer对象。相反,您提供了AVPlayerItem的实例。一个player item管理与其相关联的资源的呈现状态。一个player item包含播放器 item 轨道 - AVPlayerItemTrack的实例 - 对应于资源中的轨道。各种对象之间的关系如图2-1所示。
这个抽象概念意味着你可以同时使用不同的播放器来播放一个给定的资源,但是每个播放器可以以不同的方式呈现。图2-2显示了一种可能性,即两个不同的播放器使用不同的设置播放相同的资源。使用item轨道,例如,您可以在播放过程中禁用特定音轨(例如,您可能不想播放声音组件)。
您可以使用现有的资源初始化播放器item,或者你也可以直接从URL初始化播放器item,这样您可以在特定的位置上播放资源(AVPlayerItem将为资源创建和配置资源)。然而,与AVAsset一样,只是初始化播放器item并不一定意味着它可以立即播放。您可以观察(使用键值观察)一个item的status (状态属性)来确定是否准备播放以及何时准备播放。
二.Handling Different Types of Asset(处理不同类型的资源)
您配置资源进行播放的方式可能取决于您想要播放的资源种类。一般来说,有两种主要类型:基于文件的资源(您可以随机访问(例如本地文件,相机胶卷或媒体库)和基于流的资源(HTTP Live Streaming格式))。
加载和播放基于文件的资源。播放基于文件的资源有几个步骤: 1>.使用AVURLAsset创建资源。 2>.使用资源创建AVPlayerItem的实例。 3>.将该 item 与AVPlayer的实例相关联。 4>.等待,直到 item 的状态属性表明它已经准备好了(通常,当状态更改时,您将使用键值观察来接收通知)。
总结:将这一切结合起来说明了这种方法:使用AVPlayerLayer播放视频文件。
创建和准备HTTP实况流进行播放。使用URL初始化AVPlayerItem的实例。(您不能直接创建AVAsset实例来表示HTTP实时流中的媒体。)
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];
当你将playerItem 与播放器联系起来时,播放器就开始准备播放了。准备好播放时,player item 会创建AVAsset和AVAssetTrack实例,您可以使用它们来检查实时流的内容。
要获取流媒体 item 的持续时间,您可以观察 player item 的duration(持续时间)属性。 当 the item 准备好播放时,此属性将更新为流的正确的值。
注意:使用player item 的duration属性需要iOS 4.3或更高版本。与所有版本的iOS兼容的方法涉及观察播放器项目的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];
与资源和items一样,初始化播放器并不意味着它已准备好播放。您应该观察播放器的status
状态属性,该属性在准备播放时会更改为AVPlayerStatusReadyToPlay。您还可以观察currentItem属性来访问为该流创建的the player item。
如果您不知道您拥有哪种类型的网址,请按以下步骤操作:
1>.尝试使用URL初始化AVURLAsset,然后加载其关键轨道,如果轨道加载成功,则为资源创建一个 player item。
2>.如果1失败,请直接从URL创建一个AVPlayerItem,观察播放器的状态属性以确定它是否可以播放。
如果任一路线成功,您最终会得到一个player item,然后您可以将其与一个播放器关联起来。
三.Playing an Item (播放一个 item)
要开始播放,请向播放器发送播放信息
- (IBAction)play:sender {
[player play];
}
四.Changing the Playback Rate(改变播放速度)
通过设置播放器的速率属性来改变回放的速率。
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
值为1.0意味着“按当前current item的自然速度播放”。将速率设置为0.0与暂停播放相同,您也可以使用暂停。支持反向播放的item可以使用负数的rate属性来设置反向播放速率。您可以通过使用playerItem属性canPlayReverse(支持-1.0的速率值),canPlaySlowReverse(支持0.0到-1.0之间的速率)和canPlayFastReverse(支持小于-1.0的速率值)来确定支持的反向播放类型。
五.Seeking—Repositioning the Playhead (寻找 - 重新定位播放头 )
要将播放头移动到特定时间,通常使用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];
使用零公差可能需要框架解码大量数据。例如,只有在编写复杂的、需要精确控制的媒体编辑应用程序时,才应该使用zero。播放后,播放器的头部被设置为该item的结尾,并且进一步的播放调用不起作用。要将播放头放回到该 item 的开头,您可以注册以从该item接收AVPlayerItemDidPlayToEndTimeNotification通知。在通知的回调方法中,您使用参数kCMTimeZero调用seekToTime:。
// 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];
}
六.Playing Multiple Items(播放多条资源)
您可以使用AVQueuePlayer对象按顺序播放多个项目。AVQueuePlayer类是AVPlayer的一个子类。您使用一个player item数组来初始化一个Queue Player。NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
然后,您可以使用play方法来播放队列,就像您将使用AVPlayer对象一样。队列播放器依次播放每一个资源。如果您想跳到下一个播放的item,您将向队列播放器发送一个advanceToNextItem消息。您可以使用insertItem:afterItem:,removeItem:和removeAllItems 方法 修改队列。通常当添加一个新的项目,你应该检查它是否可以插入到队列,使用canInsertItem:afterItem:。您传递nil作为第二个参数,以测试新项是否可以添加到队列中。
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
七.Monitoring Playback(监控播放)
您可以监视一个播放器的显示状态和正在播放的 player item的多个方面。这对于不受您直接控制和修改状态特别有用,例如:
1>如果用户使用多任务切换到不同的应用程序,播放器的速率属性将下降到0.0。
2>.如果您正在播放远程媒体,player item的loadedTimeRanges和seekableTimeRanges属性将随着更多数据的可用而改变。这些属性会告诉你player item时间轴的哪些部分可用。
3>.当player item被创建为一个HTTP直播流时,播放器的currentItem
属性发生变化。
4>.player item的轨道属性可能在播放HTTP实时流时发生变化。如果流为内容提供不同的编码,可能会发生这种情况;如果播放器切换到不同的编码,轨道会改变。
5>.由于某种原因播放失败时,播放器或player item的状态属性可能会发生变化。 您可以使用键值观察来监视对这些属性值的更改。
重要提示:您应该在主线程上注册KVO更改通知并取消注册KVO更改通知。这样可以避免在另一个线程正在进行更改时收到部分通知的可能性。AV Foundation在主线程上调用observeValueForKeyPath:ofObject:change:context:即使更改操作是在另一个线程上进行的。
八.Responding to a Change in Status(应对状态变化)
当播放器或player item的状态改变时,它会发出 键值观察改变的 通知。如果某个对象因某种原因无法播放(例如,媒体服务已重置),则视情况而定,状态将更改为AVPlayerStatusFailed或AVPlayerItemStatusFailed。在这种情况下,对象的错误属性的值将更改为描述对象无法播放的原因的错误对象。
- (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;
}
九.Tracking Readiness for Visual Display (视觉显示跟踪准备就绪)
您可以观察到AVPlayerLayer对象的readyForDisplay属性在图层具有用户可见内容时收到通知,特别是,只有当有东西供用户查看然后执行转换时,您才可能会将播放器图层插入到图层树中。
要跟踪AVPlayer对象中播放头位置的更改,可以使用addPeriodicTimeObserverForInterval:queue:usingBlock:或 addBoundaryTimeObserverForTimes:queue:usingBlock :. 例如,您可以这样做,例如,使用有关已用时间或剩余时间的信息来更新用户界面,或执行其他用户界面同步。
十.Tracking Time (跟踪时间)
要跟踪AVPlayer对象中播放头位置的更改,可以使用addPeriodicTimeObserverForInterval:queue:usingBlock:或 addBoundaryTimeObserverForTimes:queue:usingBlock :.例如,您可以这样做,使用有关已用时间或剩余时间的信息来更新用户界面,或执行其他用户界面同步。
1>.使用addPeriodicTimeObserverForInterval:queue:usingBlock:时,如果时间跳转,并且播放开始或停止时,您提供的block将按照您指定的时间间隔进行调用。
2>.使用addBoundaryTimeObserverForTimes:queue:usingBlock:时,您传递包含在NSValue对象中的CMTime结构数组。无论何时遍历任何时间,都会调用您提供的block。
这两种方法都返回一个不透明的对象作为观察者。只要您希望播放器调用时间观察block,您就必须保持对返回对象的强烈引用。您还必须将这些方法的每次调用与相应的removeTimeObserver:调用进行平衡.
使用这两种方法,AV Foundation并不保证每隔一段时间就调用您的块。如果先前调用的块的执行没有完成,则AV Foundation不会调用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);
}];
十一.Playing a Video File Using AVPlayerLayer(使用AVPlayerLayer播放视频文件)
1>.配置视图以使用AVPlayerLayer图层
2>.创建一个AVPlayer对象
3>.为基于文件的资源创建AVPlayerItem对象,并使用键值观察来观察其状态
4>.通过启用按钮来响应该Item准备播放
5>.播放该Item,然后将播放器的头部恢复到开头。
十二.The Player View (播放器视图)
要播放资产的可视组件,您需要一个包含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
十三.A Simple 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;
}
}
您可以在视图控制器的viewDidLoad方法中调用syncUI,以确保在视图第一次显示时具有一致的用户界面。
- (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
其余部分介绍了其他属性和方法。
十四.Creating the Asset
您可以使用AVURLAsset从URL创建资源。(以下示例假定您的项目包含合适的视频资源。)- (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实例,并将其设置为播放器视图的播放器。与创建资源一样,只需创建播放器 item并不意味着它已准备好使用。要确定何时可以播放,您可以观察该item的状态属性。在将player item实例与播放器本身关联之前,您应该配置此观察。当你把player item与播放器联系在一起时,你就触发了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]);
}
})