使用 AVPlayer 進行多視頻播放
鏈接:http://ios.jobbole.com/84287/
從前……不。我在 nKey 從事一個為 Hyundai(現代) 進行的項目,當進入某個場景時,兩個視頻需要同時播放,以便用戶能夠看到當汽車是否存在某種特性(比如電子穩定控制系統)時會如何運作。作為一名經驗豐富的開發者,我立即告訴客戶我們應該合并兩個視頻,以便能在 iOS 上“同時播放”。我向他解釋了,為了在 iOS 上播放視頻,蘋果已經在很久之前發布了 MediaPlayer.framework,根據相關文檔(并且實際上 :P )在同一時間不能夠播放一個以上的視頻(盡管你可以有兩個 MPMoviePlayerController 實例)。
他說好的,然后我們就像之前那樣做了播放功能。但是,需求發生了改變,并且我們不得不添加一個在循環播放的背景視頻…… 結果我在如何協調多個視頻時遇到了問題,我要同時只播放一個視頻并且不引起用戶的注意。
幸運的是在這個星期一,nKey 把派我到在巴西圣保羅瓜魯柳斯機場舉行的 iOS Tech Talk,我加入了 Media Technologies Evangelist 討論中, Eryk Vershen 正在討論 AVFoundation.framework 以及 MediaPlayer.framework (又稱 MPMoviePlayerController )是如何用它來播放視頻的。在伴隨著紅酒和奶酪的交談后,我開始和 Eryk 講述我的問題并向他闡述準備如何解決這個問題。他的回答類似于“當然!大膽去干! iOS 肯定能在同一時間播放多個視的!……呃……大約 4 個是極限。”這個回答讓我很高興并感到好奇,所以我又問他,既然不受框架限制,為什么 MediaPlayer.framework 不能同時播放多個視頻……他告訴我 MPMoviePlayerController 之前是被用來在游戲中做場景切換的。。。這就是為什么之前的 iOS 版本只能全屏播放,該局限是個歷史遺留問題。
當我回到我的筆記本前,我用 AVFoundation.framework 寫了一個非常基礎的視頻播放版本。顯然,當我回到公司后,我需要寫一個更加詳細的版本才能用在項目中。
好了,故事講完了。讓我們回到工作中來!
AVFoundation 框架提供了 AVPlayer 對象來實現單視頻或多視頻播放的控制器和用戶接口。由 AVPlayer 對象生成的可視結果可以顯示在 AVPlayerLayer 類的 CoreAnimation 層上。 在 AVFoundation 中,AVAsset 對象用來表示定時影音媒體,比如視頻和音頻。根據相關文檔,每個資源包含一個用來一起呈現或處理的軌道集合,每個統一媒體類型,包括不僅限于音頻、視頻、文本、隱藏式字幕、字幕。因為定時影音媒體的性質,在成功初始化一個資源后,某些或全部鍵值可能不會立即可用。為了避免阻塞主線程,你可以在特定鍵注冊你感興趣的內容,以在可用時被通知到。
考慮到這一點,繼承 UIViewController 并命名為 VideoPlayerViewController。就像 MPMoviePlayerController ,讓我們添加一個 NSURL 屬性,用于告訴我們應該從哪里抓取視頻。就像上面描述的那樣,添加下面的代碼,一旦 URL 被賦值 AVAsset 就會被加載。
#pragma mark - Public methods
- (void)setURL:(NSURL*)URL {
[_URL release];
_URL = [URL copy];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_URL options:nil];
NSArray *requestedKeys = [NSArray arrayWithObjects:kTracksKey,
kPlayableKey, nil];
[asset loadValuesAsynchronouslyForKeys:requestedKeys
completionHandler: ^{ dispatch_async(
dispatch_get_main_queue(), ^{
[self prepareToPlayAsset:asset
withKeys:requestedKeys];
});
}];
}
- (NSURL*)URL {
return _URL;
}
所以,一旦視頻的 URL 被賦值后,創建一個資源來檢查被指定的URL引用的源并且異步的加載這個資源的 “tracks” 和 “playable” 鍵值。等加載結束后,我們就可以在主線程操作 AVPlayer(當播放狀態動態改變時,主線程可以確保安全的獲取播放器的非原子屬性)。
#pragma mark - Private methods
- (void)prepareToPlayAsset: (AVURLAsset *)asset withKeys:
(NSArray *)requestedKeys {
for (NSString *thisKey in requestedKeys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset
statusOfValueForKey:thisKey
error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
return;
}
}
if (!asset.playable) {
return;
}
if (self.playerItem) {
[self.playerItem removeObserver:self forKeyPath:kStatusKey];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
}
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
[self.playerItem addObserver:self forKeyPath:kStatusKey
options:NSKeyValueObservingOptionInitial |
NSKeyValueObservingOptionNew
context:
AVPlayerDemoPlaybackViewControllerStatusObservationContext];
if (![self player]) {
[self setPlayer:[AVPlayer playerWithPlayerItem:self.playerItem]];
[self.player addObserver:self forKeyPath:kCurrentItemKey
options:NSKeyValueObservingOptionInitial |
NSKeyValueObservingOptionNew
context:
AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext];
}
if (self.player.currentItem != self.playerItem) {
[[self player] replaceCurrentItemWithPlayerItem:self.playerItem];
}
}
在資源所有需要的鍵值加載完成后,我們檢查是否加載成功以及該資源是否可以播放。如果這樣,我們初始化一個 AVPlayerItem (用來表示能被 AVPlayer 對象播放的資源的表示狀態)和一個 AVPlayer 來播放的資源。請注意,我在這一點上沒有添加任何錯誤處理。在這里,我們應該創建一個委托并讓視圖控制器或正在使用你的播放器的用戶,決定如何以最好的方式來處理可能出現的錯誤。
我們也添加了一些鍵值監聽以便于當我們的視圖被綁定到播放器時和 AVPlayerItem 準備好播放時收到通知。
#pragma mark - Key Valye Observing
- (void)observeValueForKeyPath: (NSString*) path
? ? ? ? ? ? ? ? ? ? ?ofObject: (id)object
? ? ? ? ? ? ? ? ? ? ? ?change: (NSDictionary*)change
? ? ? ? ? ? ? ? ? ? ? context: (void*)context {
? ?if (context == AVPlayerDemoPlaybackViewControllerStatusObservation
? ? ? ? ? ? Context) {
? ? ? ? ? ? ?AVPlayerStatus status = [[change objectForKey:
? ? ? ? ? ? ? ?NSKeyValueChangeNewKey] integerValue];
? ? ? ? ? ? ?if (status == AVPlayerStatusReadyToPlay) {
? ? ? ? ? ? ? ? ? [self.player play];
? ? ? ? ? ? ?}
? ?} else if (context == AVPlayerDemoPlaybackViewControllerCurrentItem
? ? ? ? ? ? ObservationContext) {
? ? ? ? ? ? ?AVPlayerItem *newPlayerItem = [change objectForKey:
? ? ? ? ? ? ? ? NSKeyValueChangeNewKey];
if (newPlayerItem) {
[self.playerView setPlayer:self.player];
[self.playerView setVideoFillMode:
AVLayerVideoGravityResizeAspect];
}
} else {
[super observeValueForKeyPath:path ofObject: object
change:change context:context];
}
}
一旦 AVPlayerItem 設置好后,我們可以自由的將 AVPlayer 添加到用來展示可視輸出的播放器層。我們也會確保保留視頻的長寬比和適合視頻圖層的邊界內。
一旦 AVPlayer 準備好了,就讓它開始播放!讓 iOS 來完成艱巨的任務 :)
正如我前面所說,為了播放資源可視組件,您需要一個包含 AVPlayerLayer 的視圖,來指揮 AVPlayer 對象的輸出。下面演示了如何子類化 UIView 來滿足要求︰
@implementation VideoPlayerView
+ (Class)layerClass {
return [AVPlayerLayer class];
}
- (AVPlayer*)player {
return [(AVPlayerLayer*)[self layer] player];
}
- (void)setPlayer: (AVPlayer*)player {
[(AVPlayerLayer*)[self layer] setPlayer:player];
}
- (void)setVideoFillMode: (NSString *)fillMode {
AVPlayerLayer *playerLayer = (AVPlayerLayer*)[self layer];
playerLayer.videoGravity = fillMode;
}
@end
到這里就結束了!
當然,我沒有貼上所有用來編譯和運行的項目代碼,但我不會讓你失望 !轉到 GitHub 并下載完整的源代碼 !
?