用AVPlayer播放视频
控制assets的播放,你可以使用AVPlayer對象。在播放的過程中,你可以使用AVPlayerItem對象來管理asset的呈現,AVPlayerItemTrack來管理track。要顯示視頻,需要使用AVPlayerLayer。
播放Assets
一個播放器就是控制asset播放的對象,比如開始和結束,seek到指定的時間。可以使用AVPlayer來播放單個asset,用AVQueuePlayer來播放多個連續的asset。一個player向你提供播放的信息,如果需要,你通過player的狀態同步顯示到界面上。你也可以直接把player的輸出顯示笑傲指定的動畫層(AVPlayerLayer或者AVSynchronizedLayer),想知道更多關于layer的信息,請查看Core Animation Programming Guide
多個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)來決定是否可以播放。
處理不同類型的asset
配置asset的方式由需要播放的asset的類型決定的。概括的說,有兩種方式:基于文件的asset,基于流式的(http live streaming format)
加載基于文件的asset,有如下幾步:
· 使用AVURLAsset創建一個asset。
· 使用創建的asset來創建一個AVPlayerItem對象item
· item和AVPlayer關聯
· 等待item的狀態,知道可以播放。
創建基于HTTP live stream的播放器。
用url初始化一個AVPlayerItem對象。(http live stream的情況下不能直接創建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];當你關聯一個player item到player的時候,這個播放器開始準備播放。當它可以播放的時候,player item會創建AVAsset和AVAssetTrack對象,這些對象可以用來檢查live stream的內容。為了獲取stream的時間,可以通過kvo的方式觀察player item的duration的屬性。當可以播放的時候,這個屬性被設置為正確的值,這時就可以獲取時間。
注意:只能在iOS4.3之后使用player item的duration屬性。下面這種獲取duration的方法適用于所有的iOS系統版本:當player item的狀態變為AVPlayerItemStatusReadyToPlay時,duration可以通過下面代碼獲取 [[[[[playerItem tracks] objectAtIndex:0] assetTrack] asset] duration];如果僅僅是想播放一個live stream,可以直接用下面的簡短代碼實現:
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是什么類型,可以用下面的方法檢測:1、嘗試用url初始化AVURLAsset,然后load它的tracks key,如果tracks load 成功,表明你可以用這個asset創建player item。2、如果第一步失敗,直接用url創建AVPlayerItem,觀察status屬性,看是否有可播放的狀態。
播放一個item
如果想要播放,你可以想player發送play消息,如下代碼:
- (IBAction)play:sender { [player play]; }除了播放之外,還可以管理player的各種信息,比如rate和播放頭,你也可以監控player的狀態,這很有用,比如說你需要根據播放的狀態來更新界面。
改變播放的rate
可以改變播放的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。
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];當tolerance=0的時候,framework需要進行大量解碼工作,比較耗性能,所以,只有當你必須使用的時候才用這個方法,比如開發一個復雜的多媒體編輯應用,這需要精確的控制。
當播放結束后,播放頭移動到playerItem的末尾,如果此時調用play方法是沒有效果的,應該先把播放頭移到player item起始位置。如果需要實現循環播放的功能,可以監聽通知AVPlayerItemDidPlayToEndTimeNotification,當收到這個通知的時候,調用seekToTime:把播放頭移動到起始位置,代碼如下:
// Register with the notification center after creating the player item.[[NSNotificationCenter defaultCenter]addObserver:selfselector:@selector(playerItemDidReachEnd:)name:AVPlayerItemDidPlayToEndTimeNotificationobject:<#The player item#>];- (void)playerItemDidReachEnd:(NSNotification *)notification {[player seekToTime:kCMTimeZero]; }播放多個items
可以使用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來監測上述改變。
注意:只能在主線程注冊和取消kvostatus改變后的處理方式
當player或者player item的狀態status改變,系統會發送一個kvo的notification,如果一個對象由于一些原因不能播放,stauts會變成AVPlayerStatusFailed 或者 AVPlayerItemStatusFailed ,在這種情況下,這個對象的error屬性會被附上一個error類型的對象,這個error對象描述了失敗的原因。
AV Foundation不會指定這個notification是由哪個線程發出的,所以,如果你要更新UI,就必須確保更新的代碼在主線程中調用,下面的代碼表示收到status更新的處理方式:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(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:objectchange: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:AVPlayerItemDidPlayToEndTimeNotificationobject:<#A player item#>];完整的例子:用AVPlayerLayer播放一個video
下面的代碼向你展示如何使用AVPLayer播放一個video文件,步驟如下:
1、用AVPlayerLayer配置一個view
2、創建一個AVPlayer
3、用video文件創建一個AVPlayerItem對象,并且用kvo觀察他的status
4、 當收到item的狀態變成可播放的時候,播放按鈕啟用
5、 播放,結束之后把播放頭設置到起始位置
player view
為了播放一個視頻,需要個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]; } @endView 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; @endsyncUI方法的作用是根據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]; }其他的屬性和方法在接下來的文字說明。
創建一個asset
通過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:selfselector:@selector(playerItemDidReachEnd:)name:AVPlayerItemDidPlayToEndTimeNotificationobject: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改變時的處理
當player item的status改變時,view controller會收到一個通知消息,AVFoundation并不指定這個消息是由哪個線程發出的。如果你要更新ui,必須確保更新ui的代碼在主線程中。下面的代碼顯示更新ui的邏輯:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)change context:(void *)context {if (context == &ItemStatusContext) {dispatch_async(dispatch_get_main_queue(),^{[self syncUI];});return;}[super observeValueForKeyPath:keyPath ofObject:objectchange: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:selfselector:@selector(playerItemDidReachEnd:)name:AVPlayerItemDidPlayToEndTimeNotificationobject:[self.player currentItem]];- (void)playerItemDidReachEnd:(NSNotification *)notification {[self.player seekToTime:kCMTimeZero]; }總結
以上是生活随笔為你收集整理的用AVPlayer播放视频的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用vue + fastapi在hero
- 下一篇: 使用RMAN备份与恢复数据库(1)——R