AVFoundation編程指南2-用AVPlayer播放視頻

原文連接:chenjiang3的技術博客
控制assets的播放,你可使用AVPlayer對象。在播放的過程當中,你可使用AVPlayerItem對象來管理asset的呈現,AVPlayerItemTrack來管理track。要顯示視頻,須要使用AVPlayerLayer。html

播放Assets

一個播放器就是控制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的類型決定的。歸納的說,有兩種方式:基於文件的asset,基於流式的(http live streaming format)數組

加載基於文件的asset,有以下幾步:

· 使用AVURLAsset建立一個asset。<p>
· 使用建立的asset來建立一個AVPlayerItem對象item<p>
· item和AVPlayer關聯<p>
· 等待item的狀態,知道能夠播放。app

建立基於HTTP live stream的播放器。

用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屬性,看是否有可播放的狀態。動畫

播放一個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:self
        selector:@selector(playerItemDidReachEnd:)
        name:AVPlayerItemDidPlayToEndTimeNotification
        object:<#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來監測上述改變。

注意:只能在主線程註冊和取消kvo

status改變後的處理方式

當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#>];

完整的例子:用AVPlayerLayer播放一個video

下面的代碼向你展現如何使用AVPLayer播放一個video文件,步驟以下:

一、用AVPlayerLayer配置一個view

二、建立一個AVPlayer

三、用video文件建立一個AVPlayerItem對象,而且用kvo觀察他的status

四、 當收到item的狀態變成可播放的時候,播放按鈕啓用

五、 播放,結束以後把播放頭設置到起始位置

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];
}
@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方法的做用是根據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: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改變時的處理

當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];
}
參考文獻:

https://developer.apple.com/library/prerelease/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html#//apple_ref/doc/uid/TP40010188-CH3-SW8

相關文章
相關標籤/搜索