控制assets的播放汰翠,你可以使用AVPlayer對象踪栋。在播放的過程中倦青,你可以使用AVPlayerItem對象來管理asset的呈現(xiàn)祭饭,AVPlayerItemTrack來管理track芜茵。要顯示視頻,需要使用AVPlayerLayer倡蝙。
播放Assets
一個播放器就是控制asset播放的對象九串,比如開始和結(jié)束,seek到指定的時間悠咱≌袅荆可以使用AVPlayer來播放單個asset,用AVQueuePlayer來播放多個連續(xù)的asset析既。
一個player向你提供播放的信息躬贡,如果需要,你通過player的狀態(tài)同步顯示到界面上眼坏。你也可以直接把player的輸出顯示笑傲指定的動畫層(AVPlayerLayer或者AVSynchronizedLayer)拂玻,想知道更多關(guān)于layer的信息酸些,請查看Core Animation Programming Guide
多個layer的情況:你可以創(chuàng)建多個AVPlayerLayer對象,但是只有最近創(chuàng)建的layer才會顯示視頻畫面檐蚜。
雖然是播放asset魄懂,但是不能直接把a(bǔ)sset傳給AVPlayer對象,你應(yīng)該提供AVPlayerItem對象給AVPlayer闯第。一個player item管理著和它相關(guān)的asset市栗。一個player item包括player item tracks-(AVPlayerItemTrack對象,表示asset中的tracks)咳短。他們之間的關(guān)系如下圖:
這表明你可以同時用不同的player播放同一個asset填帽,如上圖顯示,兩個不同的player播放同一個asset咙好。你可以用一個存在asset直接初始化player篡腌,或者直接用URL初始化。和AVAsset一樣勾效,簡單的初始化一個player并不表示可以馬上進(jìn)行播放嘹悼,你需要觀察它的status(通過kvo)來決定是否可以播放。
處理不同類型的asset
配置asset的方式由需要播放的asset的類型決定的层宫。概括的說杨伙,有兩種方式:基于文件的asset,基于流式的(http live streaming format)
加載基于文件的asset卒密,有如下幾步:
· 使用AVURLAsset創(chuàng)建一個asset缀台。
· 使用創(chuàng)建的asset來創(chuàng)建一個AVPlayerItem對象item
· item和AVPlayer關(guān)聯(lián)
· 等待item的狀態(tài),知道可以播放哮奇。
創(chuàng)建基于HTTP live stream的播放器。
用url初始化一個AVPlayerItem對象睛约。(http live stream的情況下不能直接創(chuàng)建AVAsset對象)
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];
當(dāng)你關(guān)聯(lián)一個player item到player的時候鼎俘,這個播放器開始準(zhǔn)備播放。當(dāng)它可以播放的時候辩涝,player item會創(chuàng)建AVAsset和AVAssetTrack對象贸伐,這些對象可以用來檢查live stream的內(nèi)容。
為了獲取stream的時間怔揩,可以通過kvo的方式觀察player item的duration的屬性捉邢。當(dāng)可以播放的時候,這個屬性被設(shè)置為正確的值商膊,這時就可以獲取時間伏伐。
注意:只能在iOS4.3之后使用player item的duration屬性。下面這種獲取duration的方法適用于所有的iOS系統(tǒng)版本:當(dāng)player item的狀態(tài)變?yōu)锳VPlayerItemStatusReadyToPlay時晕拆,duration可以通過下面代碼獲取
[[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
如果僅僅是想播放一個live stream藐翎,可以直接用下面的簡短代碼實現(xiàn):
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
正如assets和items一樣,初始化一個player之后并不表明可以馬上播放,你需要觀察player的status屬性吝镣,當(dāng)status變?yōu)锳VPlayerStatusReadyToPlay時表示可以播放了堤器,你也需要觀察curretItem屬性來訪問player item。
如果你不能確定你用的url是什么類型末贾,可以用下面的方法檢測:
1闸溃、嘗試用url初始化AVURLAsset,然后load它的tracks key拱撵,如果tracks load 成功圈暗,表明你可以用這個asset創(chuàng)建player item。
2裕膀、如果第一步失敗员串,直接用url創(chuàng)建AVPlayerItem,觀察status屬性昼扛,看是否有可播放的狀態(tài)寸齐。
播放一個item
如果想要播放,你可以想player發(fā)送play消息抄谐,如下代碼:
-(IBAction)play:sender {
[player play];
}
除了播放之外渺鹦,還可以管理player的各種信息,比如rate和播放頭蛹含,你也可以監(jiān)控player的狀態(tài)毅厚,這很有用,比如說你需要根據(jù)播放的狀態(tài)來更新界面浦箱。
改變播放的rate
可以改變播放的rate吸耿,代碼如下:
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
rate=1.0表示正常的播放。0.0表示暫停酷窥。
player item支持逆向播放咽安,當(dāng)rate設(shè)置為負(fù)數(shù)的時候就是逆向播放.
playeritem的 canPlayReverse 表示rate為-1.0,
canPlaySlowReverse表示rate的范圍是-0.0到-1.0蓬推,
canPlayFastReverse表示rate小于-1.0f妆棒。
seeking-重定位播放頭
可以使用seekToTime:重定位播放頭到指定的時間,如下代碼:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];
seekTime:不能精確定位沸伏,如果需要精確定位糕珊,可以使用seekToTime:toleranceBefore:toleranceAfter:,代碼如下:
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
當(dāng)tolerance=0的時候毅糟,framework需要進(jìn)行大量解碼工作红选,比較耗性能,所以留特,只有當(dāng)你必須使用的時候才用這個方法纠脾,比如開發(fā)一個復(fù)雜的多媒體編輯應(yīng)用玛瘸,這需要精確的控制。
播放結(jié)束后苟蹈,播放頭移動到playerItem的末尾糊渊,如果此時調(diào)用play方法是沒有效果的,應(yīng)該先把播放頭移到player item起始位置慧脱。如果需要實現(xiàn)循環(huán)播放的功能渺绒,可以監(jiān)聽通知AVPlayerItemDidPlayToEndTimeNotification,當(dāng)收到這個通知的時候菱鸥,調(diào)用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];
}
播放多個items
可以使用AVQueuePlayer播放多個items,AVQueuePlayer是AVPlayer的子類氮采,可以用一個數(shù)組來初始化一個AVQueuePlayer對象殷绍。代碼如下:
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
和AVPlayer一樣,直接調(diào)用play方法來播放鹊漠,queue player順序播放隊列中的item主到,如果想要跳過一個item,播放下一個item躯概,可以調(diào)用方法advanceToNextItem登钥。
可以對隊列進(jìn)行插入和刪除操作,調(diào)用方法insertItem:afterItem:, removeItem:, 和 removeAllItems娶靡。正常情況下當(dāng)插入一個item之前牧牢,應(yīng)該檢查是否可以插入,通過使用canInsertItem:afterItem:方法姿锭,第二個參數(shù)傳nil塔鳍,代碼如下:
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
監(jiān)測播放狀態(tài)
可以監(jiān)測player和player item的狀態(tài),這個非常有用艾凯。比如:
· 如果用戶切換到其他應(yīng)用程序献幔,則需要把player的rate設(shè)為0.0
· 如果播放的是遠(yuǎn)程媒體,當(dāng)收到更多的數(shù)據(jù)的時候趾诗,player的loadedTimeRange和seekableTimeRange屬性將會不斷改變。
· 當(dāng)player播放的是http live stream的時候蹬蚁,player的currentItem會不斷改變恃泪。
· 播放http live stream的時候,player item的tracks屬性也不斷改變犀斋。這會發(fā)生在player改變編碼方式的時候贝乎。
· 當(dāng)播放失敗的時候,player或者player item的status屬性也會改變叽粹。
可以使用kvo來監(jiān)測上述改變览效。
注意:只能在主線程注冊和取消kvo
status改變后的處理方式
當(dāng)player或者player item的狀態(tài)status改變却舀,系統(tǒng)會發(fā)送一個kvo的notification,如果一個對象由于一些原因不能播放锤灿,stauts會變成AVPlayerStatusFailed 或者 AVPlayerItemStatusFailed 挽拔,在這種情況下,這個對象的error屬性會被附上一個error類型的對象但校,這個error對象描述了失敗的原因螃诅。
AV Foundation不會指定這個notification是由哪個線程發(fā)出的,所以状囱,如果你要更新UI术裸,就必須確保更新的代碼在主線程中調(diào)用,下面的代碼表示收到status更新的處理方式:
-(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;
}
監(jiān)聽視頻準(zhǔn)備播放的狀態(tài)
可以監(jiān)聽AVPlayerLayer的readyForDisplay屬性亭枷,當(dāng)layer有可顯示的內(nèi)容時袭艺,會發(fā)送一個notification。
跟蹤時間
可以使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者 addBoundaryTimeObserverForTimes:queue:usingBlock:來跟蹤播放的進(jìn)度叨粘,根據(jù)這個進(jìn)度猾编,你可以更新UI,比如播放了多少時間宣鄙,還剩多少時間袍镀,或者其他的UI狀態(tài)。
· addPeriodicTimeObserverForInterval:queue:usingBlock:冻晤,這個方法傳入一個CMTime結(jié)構(gòu)的時間區(qū)間苇羡,每隔這個時間段的時候,block會回調(diào)一次鼻弧,開始和結(jié)束播放的時候block也會回調(diào)一次设江。
· addBoundaryTimeObserverForTimes:queue:usingBlock:,這個放傳入一個CMTime結(jié)構(gòu)的數(shù)組,當(dāng)播放到數(shù)組里面的時間點(diǎn)的時候攘轩,block會回調(diào)叉存。
這兩個方法都返回一個id類型的對象,這個對象必須一直被持有度帮〖吣螅可以使用removeTimeObserver:取消這個觀察者。
對于這兩個方法笨篷,AVFoundation不會保證每次時間點(diǎn)到了的時候都會回調(diào)block瞳秽,如果前面回調(diào)的block沒有執(zhí)行完的時候,下一次就不會回調(diào)率翅。所以练俐,必須保證在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);
}];
播放結(jié)束
可以向通知中心注冊 AVPlayerItemDidPlayToEndTimeNotification 通知冕臭,當(dāng)播放結(jié)束的時候可以收到一個結(jié)束的通知腺晾。代碼:
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
完整的例子:用AVPlayerLayer播放一個video
下面的代碼向你展示如何使用AVPLayer播放一個video文件燕锥,步驟如下:
1、用AVPlayerLayer配置一個view
2悯蝉、創(chuàng)建一個AVPlayer
3归形、用video文件創(chuàng)建一個AVPlayerItem對象,并且用kvo觀察他的status
4泉粉、 當(dāng)收到item的狀態(tài)變成可播放的時候连霉,播放按鈕啟用
5、 播放嗡靡,結(jié)束之后把播放頭設(shè)置到起始位置
player view
為了播放一個視頻跺撼,需要個view,這個view的layer是AVPlayerLayer對象讨彼∏妇可以創(chuàng)建一個子類實現(xiàn)這個需求。代碼:
#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
同時你需要一個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方法的作用是根據(jù)player的status來更新播放按鈕哩至,實現(xiàn)如下:
-(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,確保剛開始顯示的頁面的時候按鈕是不可用的蜜自。代碼如下:
-(void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
其他的屬性和方法在接下來的文字說明菩貌。
創(chuàng)建一個asset
通過URL創(chuàng)建一個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里面重荠,用asset創(chuàng)建一個AVPlayerItem對象和一個AVPlayer對象箭阶,并且把player設(shè)為player view的屬性。和創(chuàng)建一個asset一樣戈鲁,簡單的創(chuàng)建一個player item并不意味著可以馬上使用仇参,可以用kvo觀察player item 的status來判斷是否可以開始播放,這個kvo的設(shè)置應(yīng)該在player item和player關(guān)聯(lián)之前設(shè)置婆殿。代碼如下:
// 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改變時的處理
當(dāng)player item的status改變時诈乒,view controller會收到一個通知消息,AVFoundation并不指定這個消息是由哪個線程發(fā)出的婆芦。如果你要更新ui怕磨,必須確保更新ui的代碼在主線程中。下面的代碼顯示更新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;
}
播放
播放很簡單消约,直接向player發(fā)送play消息即可癌压。代碼如下:
- (IBAction)play:sender {
[player play];
}
這樣的情況只能播放一次,當(dāng)播放結(jié)束的時候荆陆,再調(diào)用play方法是沒有效果的,如果你要重新播放集侯,需要向通知中心注冊AVPlayerItemDidPlayToEndTimeNotification消息被啼,當(dāng)收到播放結(jié)束的消息的時候帜消,調(diào)用seekToTime:把播放頭移動到起始位置,這樣再調(diào)用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];
}
參考文獻(xiàn):
https://developer.apple.com/library/prerelease/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html#//apple_ref/doc/uid/TP40010188-CH3-SW8
https://segmentfault.com/a/1190000004054258