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

控制assets的播放,你可以使用AVPlayer對象。在播放的過程中,你可以使用AVPlayerItem對象來管理asset的呈現(xiàn),AVPlayerItemTrack來管理track。要顯示視頻,需要使用AVPlayerLayer。

·播放Assets

一個播放器就是控制asset播放的對象,比如開始和結(jié)束,seek到指定的時間??梢允褂肁VPlayer來播放單個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,但是不能直接把asset傳給AVPlayer對象,你應該提供AVPlayerItem對象給AVPlayer。一個player item管理著和它相關(guān)的asset。一個player item包括player item tracks-(AVPlayerItemTrack對象,表示asset中的tracks)。他們之間的關(guān)系如下圖:


image.png

這表明你可以同時用不同的player播放同一個asset,如下圖顯示,兩個不同的player播放同一個asset。

image.png

你可以用一個存在asset直接初始化player,或者直接用URL初始化。和AVAsset一樣,簡單的初始化一個player并不表示可以馬上進行播放,你需要觀察它的status(通過kvo)來決定是否可以播放。

·處理不同類型的asset

配置asset的方式由需要播放的asset的類型決定的。概括的說,有兩種方式:基于文件的asset,基于流式的(http live streaming format)

1加載基于文件的asset,有如下幾步:
·使用AVURLAsset創(chuàng)建一個asset。
·使用創(chuàng)建的asset來創(chuàng)建一個AVPlayerItem對象item
·item和AVPlayer關(guān)聯(lián)
·等待item的狀態(tài),直到可以播放。
2創(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];

當你關(guān)聯(lián)一個player item到player的時候,這個播放器開始準備播放。當它可以播放的時候,player item會創(chuàng)建AVAsset和AVAssetTrack對象,這些對象可以用來檢查live stream的內(nèi)容。 為了獲取stream的時間,可以通過kvo的方式觀察player item的duration的屬性。當可以播放的時候,這個屬性被設置為正確的值,這時就可以獲取時間。

注意:只能在iOS4.3之后使用player item的duration屬性。下面這種獲取duration的方法適用于所有的iOS系統(tǒ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屬性,當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支持逆向播放,當rate設置為負數(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];

當tolerance=0的時候,framework需要進行大量解碼工作,比較耗性能,所以,只有當你必須使用的時候才用這個方法,比如開發(fā)一個復雜的多媒體編輯應用,這需要精確的控制。

當播放結(jié)束后,播放頭移動到playerItem的末尾,如果此時調(diào)用play方法是沒有效果的,應該先把播放頭移到player item起始位置。如果需要實現(xiàn)循環(huán)播放的功能,可以監(jiān)聽通知AVPlayerItemDidPlayToEndTimeNotification,當收到這個通知的時候,調(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];
}

·播放多個item

使用 AVQueuePlayer 類播放序列內(nèi)多個item,AVQueuePlayer 繼承自 AVPlayer 可以通過一個包含多個item的數(shù)組創(chuàng)建:

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。

可以對隊列進行插入和刪除操作,調(diào)用方法insertItem:afterItem:, removeItem:, 和 removeAllItems。正常情況下當插入一個item之前,應該檢查是否可以插入,通過使用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),這個非常有用。比如:

· 如果用戶切換到其他應用程序,則需要把player的rate設為0.0
· 如果播放的是遠程媒體,當收到更多的數(shù)據(jù)的時候,player的loadedTimeRange和seekableTimeRange屬性將會不斷改變。
· 當player播放的是http live stream的時候,player的currentItem會不斷改變。
· 播放http live stream的時候,player item的tracks屬性也不斷改變。這會發(fā)生在player改變編碼方式的時候。
· 當播放失敗的時候,player或者player item的status屬性也會改變。

可以使用kvo來監(jiān)測上述改變。

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

status改變后的處理方式

當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)聽視頻準備播放的狀態(tài)

可以監(jiān)聽AVPlayerLayer的readyForDisplay屬性,當layer有可顯示的內(nèi)容時,會發(fā)送一個notification。

跟蹤時間

可以使用addPeriodicTimeObserverForInterval:queue:usingBlock: 或者 addBoundaryTimeObserverForTimes:queue:usingBlock:來跟蹤播放的進度,根據(jù)這個進度,你可以更新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ù)組,當播放到數(shù)組里面的時間點的時候,block會回調(diào)。

這兩個方法都返回一個id類型的對象,這個對象必須一直被持有??梢允褂胷emoveTimeObserver:取消這個觀察者。

對于這兩個方法,AVFoundation不會保證每次時間點到了的時候都會回調(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通知,當播放結(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、 當收到item的狀態(tài)變成可播放的時候,播放按鈕啟用
5、 播放,結(jié)束之后把播放頭設置到起始位置

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設為player view的屬性。和創(chuàng)建一個asset一樣,簡單的創(chuàng)建一個player item并不意味著可以馬上使用,可以用kvo觀察player item 的status來判斷是否可以開始播放,這個kvo的設置應該在player item和player關(guān)聯(lián)之前設置。代碼如下:

// 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并不指定這個消息是由哪個線程發(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];
}

這樣的情況只能播放一次,當播放結(jié)束的時候,再調(diào)用play方法是沒有效果的,如果你要重新播放,需要向通知中心注冊AVPlayerItemDidPlayToEndTimeNotification消息,當收到播放結(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];
    }
參考文獻:https://developer.apple.com/library/prerelease/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html#//apple_ref/doc/uid/TP40010188-CH3-SW8
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容