原文:AVFoundation Programming Guide
寫在前面
簡單翻譯一下
AVFoundation
的相關(guān)文檔鸟辅,本人英語水平一般,有不對的歡迎大家留言指正。
播放
可以使用AVPlayer
對象來控制assets
的播放宅此。在播放中,你可以使用AVPlayerItem實例來管理整個asset
的呈現(xiàn)狀態(tài)爬范;使用AVPlayerItemTrack對象來管理一個獨立的track
的呈現(xiàn)狀態(tài)父腕。你可以使用AVPlayerLayer對象來顯示視頻。
播放Assets
一個player是一個控制器對象青瀑,你可以使用它來管理asset
的播放璧亮,例如控制開始和結(jié)束以及查找一個指定的時間位置萧诫。你使用一個AVPlayer實例來播放一個單獨的asset
。你可以使用一個AVQueuePlayer(AVQueuePlayer
是AVPlayer
子類)對象來播放一系列的項目枝嘶。在OS X上你可以選擇使用AVKit framework
的AVPlayerView
帘饶。
一個player
為你提供了有關(guān)播放狀態(tài)的信息。因此群扶,如果你需要的話及刻,你可以根據(jù)player的狀態(tài)來同步的處理你的用戶界面。你通常會直接將一個player輸出到一個特殊的核心動畫層(一個AVPlayerLayer對象或AVSynchronizedLayer對象)竞阐。更多的可以參考Core Animation Programming Guide
多播放層:你可以給一個單獨的AVPlayer實例創(chuàng)建多個AVPlayerLayer對象缴饭,但是只有最后一個創(chuàng)建的才會顯示視頻內(nèi)容。
盡管最終你想播放一個asset馁菜,但你不需要給AVPlayer對象直接提供assets茴扁。你需要提供一個AVPlayerItem對象實例。一個player item
管理他所包含的asset的呈現(xiàn)狀態(tài)汪疮。一個player item
包含player item tracks
(AVPlayerItemTrack實例)它們對應(yīng)了asset中的tracks峭火。關(guān)系可參考Figure 2-1
這代表你可以通過不同的player同時的播放一個給定的asset,在每一個player中呈現(xiàn)不同的效果智嚷。Figure 2-2展示了一種可能性卖丸,兩個不同的player通過不同的設(shè)置來播放同一個asset。使用item tracks盏道,你可以在播放中禁用一個特殊的track(如你可能不想播放聲音稍浆。)
你可以使用存在的asset來初始化一個player item,或者你也可以使用一個URL來初始猜嘱。同AVAsset一樣衅枫,簡單的初始化一個player item并不意味著它們可以播放了。你可以檢測(使用KVO)item的狀態(tài)屬性status來判斷它們是否可以播放的朗伶。
操作不同類型的Asset
你怎么配置一個asset的播放取決于你想播放的asset類型弦撩。一般的說,主要有兩種類型:基于文件的assets论皆,那些你可以隨意訪問的(如本地相冊或者媒體庫中的本地文件)益楼,和基于流的assets(HLS格式的)。
加載和播放本地asset点晴。步驟如下:
- 使用AVURLAsset創(chuàng)建asset感凤。
- 使用asset創(chuàng)建一個
AVPlayerItem
對象實例。 - 關(guān)聯(lián)player item到一個
AVPlayer
對象粒督。 - 等待item的狀態(tài)表明它是可以播放的(使用KVO來監(jiān)聽狀態(tài)變化)
可以參照Putting It All Together: Playing a Video File Using AVPlayerLayer.
創(chuàng)建和準備播放HLS陪竿。可以使用URL初始化一個AVPlayerItem
的實例屠橄。(你不能直接創(chuàng)建一個AVAsset實例來表示HLS中的媒體對象族跛。)
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];
當你關(guān)聯(lián)一個player item到一個player的時候捐康,它開始準備變?yōu)榭梢砸圆シ诺臓顟B(tài)。當它可以播放的時候庸蔼,player item會創(chuàng)建AVAsset
和AVAssetTrack
實例解总,你可以用來檢查直播流的內(nèi)容。
你可以監(jiān)聽player item的duration屬性來得到流對象的時長姐仅。當item可以播放的時候花枫,這個屬性會更新成正確的值。
注意:使用duration
屬性需要iOS 4.3或者更高的系統(tǒng)才可以使用掏膏。一個在所有iOS版本中都支持的方法是監(jiān)聽player item的status屬性劳翰。當status變?yōu)?a target="_blank" rel="nofollow">AVPlayerItemStatusReadyToPlay的時候,你可以使用下面代碼獲得它的時長馒疹。
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
如果你只是想簡單的播放直播流佳簸,你可以使用一個快捷的方法來直接通過一個URL來創(chuàng)建player 代碼如下:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
使用assets和items來初始化player都不意味著他們是可以播放的。你應(yīng)該監(jiān)聽player的status屬性颖变,當它變?yōu)?a target="_blank" rel="nofollow">AVPlayerStatusReadyToPlay的時候就表明它是可以播放的了生均。你也可以監(jiān)聽currentItem屬性來訪問通過流創(chuàng)建的player item。
如果你不知道你使用的URL是那種類型的腥刹,你可以使用如下步驟:
1.嘗試通過URL來創(chuàng)建AVURLAsset
马胧,然后加載它的tracks。
如果tracks加載成功的話衔峰,你就可以使用這個asset來創(chuàng)建一個player item了佩脊。
2.如果1失敗了,直接使用URL來創(chuàng)建AVPlayerItem
垫卤。
監(jiān)聽player的status屬性來判斷他是否是可以播放的威彰。
如果任何一個成功了,你就可以結(jié)束判斷然后將palyer item關(guān)聯(lián)到player上了穴肘。
播放Item
你可以通過發(fā)送一個play的消息來開始播放歇盼。
- (IBAction)play:sender {
[player play];
}
除了簡單的播放外,你還可以管理播放的其他方面梢褐,如播放速率和播放頭的位置旺遮。你也可以檢測player的播放狀態(tài)赵讯;如果你需要的話盈咳,這是很有用的,如根據(jù)asset的狀態(tài)同步改變用戶界面的呈現(xiàn)狀態(tài)边翼,可以參考Monitoring Playback鱼响。
改變播放速率
你可以通過設(shè)置player的rate屬性來改變播放速率。
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
1.0表示”以當前Item的正常速率來播放“组底。設(shè)置rate為0.0相當于暫停播放丈积。你當然也可以使用pause方法筐骇。
items支持逆向播放,你可以通過設(shè)置rate屬性為負數(shù)來實現(xiàn)江滨。你可以使用playerItem的canPlayReverse(支持設(shè)置rate為-1.0)铛纬,canPlaySlowReverse(支持設(shè)置rate為0.0~-1.0),canPlayFastReverse(支持設(shè)置rate小于-1.0)屬性來判斷是否支持逆向播放唬滑。
查找--重定播放頭位置
你可以使用seekToTime來重新設(shè)置播放的開始位置到一個特定的時間告唆。
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公差可能會使framework解碼大量的數(shù)據(jù)。你應(yīng)該只有當你需要使用的時候再使用稻艰。例如:編寫一個復(fù)雜的媒體編輯程序需要精確的控制懂牧。
播放完成后,player的播放頭會被設(shè)置到item的末尾尊勿,此時再次調(diào)用play方法會沒有任何反應(yīng)僧凤。你需要將播放頭重新設(shè)置到item的開始位置。你可以監(jiān)聽AVPlayerItemDidPlayToEndTimeNotification這個通知元扔。在這個通知的回調(diào)方法中你可以使用seekToTime:
方法拼弃,參數(shù)設(shè)置為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來初始化一個queue player
。
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
你可以使用play方法來播放AVQueuePlayer咏连,就像一個AVPlayer
一樣盯孙。隊列會依次播放每一個item。如果你想跳到下一個item祟滴,你可以發(fā)送advanceToNextItem消息振惰。
你可以使用insertItem:afterItem:, removeItem:和removeAllItems來改變隊列。當你添加一個新的item的時候垄懂,你應(yīng)該檢查它是否可以添加到隊列中骑晶,通過使用方法canInsertItem:afterItem:來判斷。你可以給第二個參數(shù)設(shè)置為nil來判斷新的item是否可以添加到隊列草慧。
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
監(jiān)控播放
你可以監(jiān)測多個方面的內(nèi)容桶蛔,包括player的呈現(xiàn)狀態(tài)和播放中的player item。當狀態(tài)的改變不是在你直接控制下的時候漫谷,這是非常有用的仔雷。如:
- 當用戶使用多任務(wù)來切換不同的應(yīng)用的時候,player的rate屬性會調(diào)整為0.0。
- 如果你正在播放遠程媒體碟婆,player item的loadedTimeRanges和seekableTimeRanges屬性會隨著數(shù)據(jù)的增加而變化电抚。
這些屬性告訴你player item的時間軸中哪部分是可以使用的。 - player的currentItem屬性會改變竖共,當使用HTTP直播流創(chuàng)建一個player item的時候蝙叛。
- player item的tracks屬性在播放一個HTTP直播流的時候可能會改變。
這個可能發(fā)生在播放流支持不同的編碼的時候公给;當選擇不同的編碼的時候tracks會發(fā)生變化甥温。 - player或者player item的狀態(tài)屬性status當因為某些原因播放失敗的時候會發(fā)生變化。
你可以使用KVO來監(jiān)測這些屬性的變化妓布。
重要事項:
你應(yīng)該在主線程中注冊和注銷KVO變化通知姻蚓。這個避免了接收部分通知如果這個改變發(fā)生在其他線程中。AV Foundation會在主線程中觸發(fā)observeValueForKeyPath:ofObject:change:context: 方法匣沼,即使這些變化發(fā)生在其他線程狰挡。
響應(yīng)狀態(tài)變化
當player或者player item的狀態(tài)發(fā)生變化的時候,它發(fā)出了一個KVO變化的通知释涛。如果一個對象由于某些原因不能播放(如媒體服務(wù)被重置)加叁,狀態(tài)會變成AVPlayerStatusFailed或者AVPlayerItemStatusFailed。在這種情況下唇撬,這個對象的error屬性會變?yōu)槊枋鰧ο鬄槭裁床荒懿シ诺膃rror對象它匕。
AV Foundation 并不指定發(fā)出通知的線程。如果你想更新用戶的界面窖认,你必須確保相關(guān)的代碼在主線程中被執(zhí)行豫柬。下面的代碼使用dispatch_async在主線程中執(zhí)行代碼。
- (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有用戶可以觀看的內(nèi)容的時候會發(fā)生變化烧给。通常,你應(yīng)該只有當有內(nèi)容可以供用戶觀看的時候才將player的layer插入到layer層中喝噪。
跟蹤時間
你可以使用addPeriodicTimeObserverForInterval:queue:usingBlock或者addBoundaryTimeObserverForTimes:queue:usingBlock:來跟蹤AVPlayer對象的播放頭位置的變化础嫡。你可以更新用戶界面的信息如已經(jīng)播放的時間和剩余的時間,或者做一下其他的用戶界面的同步處理酝惧。
addPeriodicTimeObserverForInterval:queue:usingBlock中的block會在你提供的特殊間隔中調(diào)用榴鼎,如時間跳躍,播放開始或結(jié)束的時候晚唇。
addBoundaryTimeObserverForTimes:queue:usingBlock:方法中你傳遞一組
CMTime
巫财,它的block會在這些時間經(jīng)過的時候觸發(fā)。
上面兩個方法都返回一個不透明對象可以作為一個觀察者缺亮。你必須對返回值保持一個強引用直到你想要player觸發(fā)block的時間翁涤。你必須相應(yīng)的調(diào)用removeTimeObserver:方法。
對于這些方法萌踱, AV Foundation并不能保證在每一個時間間隔或者邊界都調(diào)用相應(yīng)的block葵礼。AV Foundation不會在前一個block沒有完成的時候再次調(diào)用block。你必須確保你要執(zhí)行的block不會太消耗系統(tǒng)并鸵。
// 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);
}];
播放結(jié)束
Item在播放完成的時候會發(fā)生一個AVPlayerItemDidPlayToEndTimeNotification通知鸳粉。
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
使用AVPlayerLayer播放視頻文件
這個簡單的代碼示例說明了如何使用一個AVPlayer對象播放一個視頻文件。步驟如下:
- 使用
AVPlayerLayer
layer配置view - 創(chuàng)建一個AVPlayer對象
- 使用基于文件的asset創(chuàng)建一個AVPlayerItem對象园担,并且使用KVC監(jiān)聽status屬性
- 當item可以播放的時候激活一個button
- 播放item届谈,然后重置到開始位置
注意:為了關(guān)注最主要的代碼,這個示例忽略了一些東西弯汰,如內(nèi)存管理艰山、注銷一個觀察者等。為了使用AV Foundation咏闪,你需要對使用Cocoa框架有豐富的經(jīng)驗來推斷出忽略的代碼曙搬。關(guān)于playback的概念可以參考Playing Assets。
The Player View
為了播放一個asset鸽嫂,你需要有一個view來包含一個AVPlayerLayer層來輸出纵装,你可以簡單的使用一個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
假設(shè)你有一個簡單的ViewController,如下:
@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方法用來根據(jù)player的狀態(tài)來同步改變button的狀態(tài)据某。
- (void)syncUI {
if ((self.player.currentItem != nil) &&
([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
self.playButton.enabled = YES;
}
else {
self.playButton.enabled = NO;
}
}
你可以在viewDidLoad中調(diào)用syncUI方法來確保在view第一次顯示的時候用戶界面的一致性橡娄。
- (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
其他的一些屬性和方法在剩下的內(nèi)容中介紹。
Creating the Asset
你可以使用AVURLAsset的方法來通過一個URL來創(chuàng)建asset癣籽。下面的方法假設(shè)你的工程中包含一個合適的視頻文件挽唉。
- (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中你可以創(chuàng)建一個AVPlayerItem對象,并將它設(shè)置到player view上筷狼。簡單的創(chuàng)建一個asset并不意味著它是可用的橱夭。為了判斷什么時候它是可用的,你可以監(jiān)聽item的status屬性桑逝。
// 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的狀態(tài)變化
當player item的狀態(tài)發(fā)生變化的時候棘劣,view controller接收到一個KVO變化的通知。AV Foundation并不指定發(fā)送消息的線程楞遏。如果你需要更新UI茬暇,你必須確保你的方法是在主線程中被執(zhí)行的。這個示例使用了dispatch_async來實現(xiàn)在主線程中更新UI寡喝。
- (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
- (IBAction)play:sender {
[player play];
}
這個item只會播放一次糙俗。在播放完成后,player的播放頭被設(shè)置到了item的末尾预鬓,以后再次調(diào)用play方法都不會再有作用了巧骚。可以重置播放頭到item的開始位置,你可以以通過接收AVPlayerItemDidPlayToEndTimeNotification
來處理劈彪。在通知的回調(diào)方法中竣蹦,調(diào)用seekToTime:
方法,參數(shù)設(shè)置為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];
}