Playback
为了控制资源(assets)的播放(playback),我们可以使用AVPlayer 对象,在播放期间,我们能够使用AVPlayerItem实例来管理所播放资源的呈现状态,
AVPlayerItemTrack
对象能够管理一个单独轨道(individual track)的呈现状态。为了显示视频,我们需要使用AVPlayerLayer 对象。
Playing Assets
播放器(player )是一个控制对象(controller object),我们使用该控制对象管理资源的播放,例如:开始(starting)和停止播放(stopping playback),而且移动到(seeking to)具体的时间。我们能够使用
AVPlayer
来播放单一的资源。我们能够使用AVQueuePlayer
(AVQueuePlayer是AVPlayer的子类)播放序列(sequence)中的一系列对象。
播放器(player )提供了我们播放的状态信息,如果需要,我们能够根据播放的状态同步用户界面。代表性的就是播放器能够在专门的Core Animation layer进行输出播放。为了了解更多layers相关,可以看Core Animation Programming Guide
这里注意:我们能够使用单一的AVPlayer实例创建多个AVPlayerLayer对象,但是只有最后创建的layer能够显示video。
虽然我们的目的是想播放资源,但是我们不能够直接为AVPlayer对象提供assets。相反我们需要使用
AVPlayerItem
对象。因为player item对象管理着与它相关联的资源呈现的状态。一个player item包含多个player item tracks,是AVPlayerItemTrack的实例对象,相当于在资源(asset)中的tracks.下图显示了我们所描述的关系:
Figure 2-1 Playing an asset
我们能够使用不同的players同时播放给定的资源(asset),但是每一个player必须使用不同的渲染方式。Figure 2-2显示了一种可能性,两个不同的播放者使用不同的设置播放相同的资源。例如:在播放期间使特定的itemTrack失效(如:你不想播放声音部分)。
Figure 2-2 Playing the same asset in different ways
我们能够使用存在的资源(asset)初始化一个player item对象,或者直接使用URL初始化player item以致于我们能够在特定的位置进行播放资源(resource),这里AVPlayerItem将根据给定点的resource创建并配置一个asset。就AVAsset来说,简单的初始化player item对象,并不一定意味着准备好立马进行播放,我们可以使用观测者来观察player item的status属性来确定是否player item已经准备好播放。
Handling Different Types of Asset
配置资源(asset)进行播放的方式取决于我们使用的是哪种类型的资源(asset),一般来说,这里有两种类型:基于文件的资源(file-based assets),这些文件我们可以随意访问(比如:本地文件,照相机,媒体库),另外一种类型是基于流的资源(stream-based assets)(HTTP Live Streaming format).
为了加载并播放file-based assets,这里有一些步骤:
1:使用AVURLAsset创建一个asset
2:使用刚刚创建的asset来创建AVPlayerItem实例
3:使用AVPlayerItem实例来初始化AVPlayer
4:等待AVPlayerItem的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];
当我们为player关联player item的时候,已经开始准备播放了,当准备开始播放的时候,player item将创建AVAsset和AVAssetTrack实例,可以使用这些事例检测流媒体(live stream)的内容。
为了获取流媒体对象(streaming item)时间,我们能够观察player item的duration属性。当player item准备播放,该属性将为流媒体更新到正确的值。
注意:使用player item的duration属性要求iOS4.3或之后,可以使用观察者模式,观察属性
status,
当player item的 status
属性状态变为AVPlayerItemStatusReadyToPlay,duration能够使用下面代码获取:
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
如果我们想简单的播放流媒体(live stream),我们能够直接使用URL创建player对象进行播放,如下:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
就assets和player items来说,初始化player并不意味着准备播放,我们应该观察
status
属性,当属性变为AVPlayerItemStatusReadyToPlay就说明准备播放了,我们也可以观察currentItem
属性来获取为流媒体(stream)创建的player item对象。
如果你并不知道你有什么类型的URL,可以使用如下步骤:
1:尝试着使用URL初始化AVURLAsset,这时候加载AVURLAsset的tracks key值,如果加载tracks成功,这时候能够为asset创建一个player item对象。
2:如果第一步失败,直接使用URL创建AVPlayerItem并观察
status
属性来确定是否可以播放
以上两种方法任意一种成功,我们将使用player item对象创建player。
Playing an Item
为了开始播放,我们可以调用player的play方法:
- (IBAction)play:(UIButton *)sender {
[player play];
}
除了简单播放之外,我们可以管理播放的各个方面,比如:播放是速度和播放头的位置。我们也可以监控player播放的状态情况。更多可以看: Monitoring Playback
Changing the Playback Rate
我们能够通过设置player的
rate
属性来改变播放的速度:
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
该值为1.0意味这current item是以正常的速度进行播放,设置rate为0.0相当于停止播放,也就是使用AVplayer的pause方法。
AVplayeritem支持倒退播放(reverse playback),我们能够设置rate属性为负值来设置倒退播放的速度。我们能够使用playerItem的相关属性确定倒退播放的类型。如:
canPlayReverse
(supports a rate value of -1.0), canPlaySlowReverse
(supports rates between 0.0 and 1.0) and canPlayFastReverse (supports rate values less than -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];
使用容忍度为0可能要求框架编码大量的数据,我们使用zero的情况:例如,写复杂的媒体编辑应用并要求精准的控制。
在播放之后,播放头将显示在player item的最后并且进一步调用play播放方法无效。为了使用播放头的位置回到最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];
}
Playing Multiple Items
我们能够使用一个
AVQueuePlayer
对象来播放序列中的多个items。AVQueuePlayer是AVplayer的子类,我们使用player item数组来初始化queue player:
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
这时候我们能够使用play方法播放queue,就像一个AVPlayer对象,queue player将轮流播放每一个item,如果我们想跳过下一个item,我们可以向列队发送
advanceToNextItem
信息。
我们还可以使用
insertItem:afterItem:
, removeItem:
, and removeAllItems等
方法来修改列队(queue),当我们添加一个新的item的时候,首先我们需要确认该item是否能够被插入到列队中,使用canInsertItem:afterItem:
.方法,我们为第二个参数传入nil来测试新的item是否能够加入列队。
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
Monitoring Playback
我们能够监控许多方面,包括播放者(player)呈现的状态和player item播放时的状态,状态的改变是非常有用但是这种改变并不是在我们的控制之下。例如:
1:如果用户使用多任务并在不同的应用之间进行切换,player的
rate
属性将下降到0.0
2:如果我们正在播放远程媒体内容,随着越来越多的可用数据,player item的loadedTimeRanges和seekableTimeRanges属性将随着更多数据变得可使用而发生改变。这些属性告诉我们player item的时间轴(timeline)是可以使用了。
3:player item创建使用了HTTP流媒体(HTTP live stream),player的currentItem属性将改变
4:当正在播放HTTP live stream的时候,player item的tracks属性可能改变。比如:当流媒体(stream)为内容提供了不同的编码;或者player切换到不同的编码,tracks将改变
5:如果playback由于某些原因失败,player或者player item的
status
属性可能改变。
我们能够使用key-value observing来检测属性的改变。
注意:我们应该在主线程注册KVO改变通知和移除KVO改变通知,如果改变是发生在其他线程并发主线程,这可以避免接收部分通知的可能性。AV Foundation将在主线程调用
observeValueForKeyPath:ofObject:change:context:,即使是在其它线程发生改变。
Responding to a Change in Status
当player或者player item的状态改变,将发送观察改变通知,如果一个对象由于某些原因不能够播放(例如:媒体服务被重置),播放状态将改变为
AVPlayerStatusFailed
或 AVPlayerItemStatusFailed
. 在这种情况,对象的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;
}
Tracking Readiness for Visual Display
我们能够观察AVPlayerLayer对象的
readyForDisplay
属性接受当layer已经显示用户视觉内容的通知,在特定情景中,我们能够插入player layer到layer tree中,执行相应的过渡动画到用户所关注的内容。
Tracking Time
为了跟踪AVPlayer对象播放头位置的改变,我们能够使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者addBoundaryTimeObserverForTimes:queue:usingBlock:方法。例如:根据时间的消耗和时间的剩余更新用户的界面,或者执行一些其它用户界面同步操作。
addPeriodicTimeObserverForInterval:queue:usingBlock:
方法中的block将在我们所指定的具体时间调用,如果时间跳过,播放开始或停止
addBoundaryTimeObserverForTimes:queue:usingBlock:
方法,我们传递一个数组,数组中的值是包含结构体CMTime的NSValue对象,block将在任意指定值到达时进行。
上面两个方法都将返回一个不透明对象作为观察者。只要我们想player能够调用时间观察者的block,我们必须保持强引用返回值。我们也必须在适当的时候调用removeTimeObserver:方法进行去除。
对于上面两个方法,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);
}];
Reaching the End of an Item
我们能够注册通知
AVPlayerItemDidPlayToEndTimeNotification
notification,当player item完成播放。
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
Putting It All Together: Playing a Video File Using AVPlayerLayer
简短的代码就能够解释怎样使用AVPlayer对象播放video file,明白几点:
1:配置一个view来使用AVPlayerLayer layer
2:创建一个AVPlayer对象
3:创建一个AVPlayerItem对象基于文件的asset并且使用key-value observing 观察status属性状态变化
2:创建一个AVPlayer对象
3:创建一个AVPlayerItem对象基于文件的asset并且使用key-value observing 观察status属性状态变化
4:根据player item的响应准备播放
5:播放item并在播放完成使播放头回到最开始位置
注意:为了集中于大多数相关代码,该例子将省略完整应用的几个部分,比如:内存管理和去除观察者。
The Player View
为了播放视觉内容,我们需要一个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
同步UI方法用于根据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];
}
Creating the Asset
使用
AVURLAsset根据URL创建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.
}];
}
在回调block中,我们可以根据asset创建AVPlayerItem
实例,并且使用该AVPlayerItem
对象初始化AVplay,在为view的player赋值。当我们创建了asset,简单的创建player item并不意味着准备使用,我们需要确定什么时候可以播放,所有观察status属性。我们应该在为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]);
}
});
Responding to the Player Item’s Status Change
当player item的状态发生改变,视图控制器将接收到通知,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;
}
Playing the Item
使用player的paly方法播放视频
- (IBAction)play:(UIButton *)sender {
[player play];
}
一旦item被播放,在播放结束,player的播放头会被设置在item的最后位置,进一步调用play方法无效,为了调整播放头的位置到item的开始位置,我们需要注册
AVPlayerItemDidPlayToEndTimeNotification通知,并在通知回调方法中,执行
seekToTime:方法,参数为 kCMTimeZero
// 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];
}