原文連接:chenjiang3的技術博客
控制assets的播放,你可使用AVPlayer對象。在播放的過程當中,你可使用AVPlayerItem對象來管理asset的呈現,AVPlayerItemTrack來管理track。要顯示視頻,須要使用AVPlayerLayer。html
一個播放器就是控制asset播放的對象,好比開始和結束,seek到指定的時間。可使用AVPlayer來播放單個asset,用AVQueuePlayer來播放多個連續的asset。
一個player向你提供播放的信息,若是須要,你經過player的狀態同步顯示到界面上。你也能夠直接把player的輸出顯示笑傲指定的動畫層(AVPlayerLayer或者AVSynchronizedLayer),想知道更多關於layer的信息,請查看Core Animation Programming Guideios
多個layer的狀況:你能夠建立多個AVPlayerLayer對象,可是隻有最近建立的layer纔會顯示視頻畫面。
雖然是播放asset,可是不能直接把asset傳給AVPlayer對象,你應該提供AVPlayerItem對象給AVPlayer。一個player item管理着和它相關的asset。一個player item包括player item tracks-(AVPlayerItemTrack對象,表示asset中的tracks)。他們之間的關係以下圖:
這代表你能夠同時用不一樣的player播放同一個asset,以下圖顯示,兩個不一樣的player播放同一個asset。
你能夠用一個存在asset直接初始化player,或者直接用URL初始化。和AVAsset同樣,簡單的初始化一個player並不表示能夠立刻進行播放,你須要觀察它的status(經過kvo)來決定是否能夠播放。vim
配置asset的方式由須要播放的asset的類型決定的。歸納的說,有兩種方式:基於文件的asset,基於流式的(http live streaming format)數組
· 使用AVURLAsset建立一個asset。<p>
· 使用建立的asset來建立一個AVPlayerItem對象item<p>
· item和AVPlayer關聯<p>
· 等待item的狀態,知道能夠播放。app
用url初始化一個AVPlayerItem對象。(http live stream的狀況下不能直接建立AVAsset對象)iphone
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 item到player的時候,這個播放器開始準備播放。當它能夠播放的時候,player item會建立AVAsset和AVAssetTrack對象,這些對象能夠用來檢查live stream的內容。
爲了獲取stream的時間,能夠經過kvo的方式觀察player item的duration的屬性。當能夠播放的時候,這個屬性被設置爲正確的值,這時就能夠獲取時間。async
注意:只能在iOS4.3以後使用player item的duration屬性。下面這種獲取duration的方法適用於全部的iOS系統版本:當player item的狀態變爲AVPlayerItemStatusReadyToPlay時,duration能夠經過下面代碼獲取 [[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];
若是僅僅是想播放一個live stream,能夠直接用下面的簡短代碼實現:ide
self.player = [AVPlayer playerWithURL:<#Live stream URL#>]; [player addObserver:self forKeyPath:@"status" options:0 context:&PlayerStatusContext];
正如assets和items同樣,初始化一個player以後並不代表能夠立刻播放,你須要觀察player的status屬性,當status變爲AVPlayerStatusReadyToPlay時表示能夠播放了,你也須要觀察curretItem屬性來訪問player item。性能
若是你不能肯定你用的url是什麼類型,能夠用下面的方法檢測:
一、嘗試用url初始化AVURLAsset,而後load它的tracks key,若是tracks load 成功,代表你能夠用這個asset建立player item。
二、若是第一步失敗,直接用url建立AVPlayerItem,觀察status屬性,看是否有可播放的狀態。動畫
若是想要播放,你能夠想player發送play消息,以下代碼:
- (IBAction)play:sender { [player play]; }
除了播放以外,還能夠管理player的各類信息,好比rate和播放頭,你也能夠監控player的狀態,這頗有用,好比說你須要根據播放的狀態來更新界面。
能夠改變播放的rate,代碼以下:
aPlayer.rate = 0.5; aPlayer.rate = 2.0;
rate=1.0表示正常的播放。0.0表示暫停。
player item支持逆向播放,當rate設置爲負數的時候就是逆向播放.playeritem的 canPlayReverse 表示rate爲-1.0,canPlaySlowReverse表示rate的範圍是-0.0到-1.0,canPlayFastReverse表示rate小於-1.0f。
可使用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];
當tolerance=0的時候,framework須要進行大量解碼工做,比較耗性能,因此,只有當你必須使用的時候才用這個方法,好比開發一個複雜的多媒體編輯應用,這須要精確的控制。
當播放結束後,播放頭移動到playerItem的末尾,若是此時調用play方法是沒有效果的,應該先把播放頭移到player item起始位置。若是須要實現循環播放的功能,能夠監聽通知AVPlayerItemDidPlayToEndTimeNotification,當收到這個通知的時候,調用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]; }
可使用AVQueuePlayer播放多個items,AVQueuePlayer是AVPlayer的子類,能夠用一個數組來初始化一個AVQueuePlayer對象。代碼以下:
NSArray *items = <#An array of player items#>; AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
和AVPlayer同樣,直接調用play方法來播放,queue player順序播放隊列中的item,若是想要跳過一個item,播放下一個item,能夠調用方法advanceToNextItem。
能夠對隊列進行插入和刪除操做,調用方法insertItem:afterItem:, removeItem:, 和 removeAllItems。正常狀況下當插入一個item以前,應該檢查是否能夠插入,經過使用canInsertItem:afterItem:方法,第二個參數傳nil,代碼以下:
AVPlayerItem *anItem = <#Get a player item#>; if ([queuePlayer canInsertItem:anItem afterItem:nil]) { [queuePlayer insertItem:anItem afterItem:nil]; }
能夠監測player和player item的狀態,這個很是有用。好比:
· 若是用戶切換到其餘應用程序,則須要把player的rate設爲0.0
· 若是播放的是遠程媒體,當收到更多的數據的時候,player的loadedTimeRange和seekableTimeRange屬性將會不斷改變。
· 當player播放的是http live stream的時候,player的currentItem會不斷改變。
· 播放http live stream的時候,player item的tracks屬性也不斷改變。這會發生在player改變編碼方式的時候。
· 當播放失敗的時候,player或者player item的status屬性也會改變。
可使用kvo來監測上述改變。
注意:只能在主線程註冊和取消kvo
當player或者player item的狀態status改變,系統會發送一個kvo的notification,若是一個對象因爲一些緣由不能播放,stauts會變成AVPlayerStatusFailed 或者 AVPlayerItemStatusFailed ,在這種狀況下,這個對象的error屬性會被附上一個error類型的對象,這個error對象描述了失敗的緣由。
AV Foundation不會指定這個notification是由哪一個線程發出的,因此,若是你要更新UI,就必須確保更新的代碼在主線程中調用,下面的代碼表示收到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; }
能夠監聽AVPlayerLayer的readyForDisplay屬性,當layer有可顯示的內容時,會發送一個notification。
可使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者 addBoundaryTimeObserverForTimes:queue:usingBlock:來跟蹤播放的進度,根據這個進度,你能夠更新UI,好比播放了多少時間,還剩多少時間,或者其餘的UI狀態。
· addPeriodicTimeObserverForInterval:queue:usingBlock:,這個方法傳入一個CMTime結構的時間區間,每隔這個時間段的時候,block會回調一次,開始和結束播放的時候block也會回調一次。
· addBoundaryTimeObserverForTimes:queue:usingBlock:,這個放傳入一個CMTime結構的數組,當播放到數組裏面的時間點的時候,block會回調。
這兩個方法都返回一個id類型的對象,這個對象必須一直被持有。可使用removeTimeObserver:取消這個觀察者。
對於這兩個方法,AVFoundation不會保證每次時間點到了的時候都會回調block,若是前面回調的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); }];
能夠向通知中心註冊 AVPlayerItemDidPlayToEndTimeNotification 通知,當播放結束的時候能夠收到一個結束的通知。代碼:
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#> selector:@selector(<#The selector name#>) name:AVPlayerItemDidPlayToEndTimeNotification object:<#A player item#>];
下面的代碼向你展現如何使用AVPLayer播放一個video文件,步驟以下:
一、用AVPlayerLayer配置一個view
二、建立一個AVPlayer
三、用video文件建立一個AVPlayerItem對象,而且用kvo觀察他的status
四、 當收到item的狀態變成可播放的時候,播放按鈕啓用
五、 播放,結束以後把播放頭設置到起始位置
爲了播放一個視頻,須要個view,這個view的layer是AVPlayerLayer對象。能夠建立一個子類實現這個需求。代碼:
#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,代碼以下:
@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的status來更新播放按鈕,實現以下:
- (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]; }
其餘的屬性和方法在接下來的文字說明。
經過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裏面,用asset建立一個AVPlayerItem對象和一個AVPlayer對象,而且把player設爲player view的屬性。和建立一個asset同樣,簡單的建立一個player item並不意味着能夠立刻使用,能夠用kvo觀察player item 的status來判斷是否能夠開始播放,這個kvo的設置應該在player item和player關聯以前設置。代碼以下:
// 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改變時,view controller會收到一個通知消息,AVFoundation並不指定這個消息是由哪一個線程發出的。若是你要更新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發送play消息便可。代碼以下:
- (IBAction)play:sender { [player play]; }
這樣的狀況只能播放一次,當播放結束的時候,再調用play方法是沒有效果的,若是你要從新播放,須要向通知中心註冊AVPlayerItemDidPlayToEndTimeNotification消息,當收到播放結束的消息的時候,調用seekToTime:把播放頭移動到起始位置,這樣再調用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]; }