使用 AVPlayer 進(jìn)行多視頻播放
鏈接:http://ios.jobbole.com/84287/
從前……不赎离。我在 nKey 從事一個為 Hyundai(現(xiàn)代) 進(jìn)行的項(xiàng)目,當(dāng)進(jìn)入某個場景時罐寨,兩個視頻需要同時播放陪每,以便用戶能夠看到當(dāng)汽車是否存在某種特性(比如電子穩(wěn)定控制系統(tǒng))時會如何運(yùn)作。作為一名經(jīng)驗(yàn)豐富的開發(fā)者,我立即告訴客戶我們應(yīng)該合并兩個視頻放妈,以便能在 iOS 上“同時播放”湾盒。我向他解釋了湿右,為了在 iOS 上播放視頻,蘋果已經(jīng)在很久之前發(fā)布了 MediaPlayer.framework罚勾,根據(jù)相關(guān)文檔(并且實(shí)際上 :P )在同一時間不能夠播放一個以上的視頻(盡管你可以有兩個 MPMoviePlayerController 實(shí)例)毅人。
他說好的,然后我們就像之前那樣做了播放功能尖殃。但是丈莺,需求發(fā)生了改變,并且我們不得不添加一個在循環(huán)播放的背景視頻…… 結(jié)果我在如何協(xié)調(diào)多個視頻時遇到了問題送丰,我要同時只播放一個視頻并且不引起用戶的注意缔俄。
幸運(yùn)的是在這個星期一,nKey 把派我到在巴西圣保羅瓜魯柳斯機(jī)場舉行的 iOS Tech Talk器躏,我加入了 Media Technologies Evangelist 討論中俐载, Eryk Vershen 正在討論 AVFoundation.framework 以及 MediaPlayer.framework (又稱 MPMoviePlayerController )是如何用它來播放視頻的。在伴隨著紅酒和奶酪的交談后登失,我開始和 Eryk 講述我的問題并向他闡述準(zhǔn)備如何解決這個問題遏佣。他的回答類似于“當(dāng)然!大膽去干揽浙! iOS 肯定能在同一時間播放多個視的状婶!……呃……大約 4 個是極限∧笃迹”這個回答讓我很高興并感到好奇太抓,所以我又問他,既然不受框架限制令杈,為什么 MediaPlayer.framework 不能同時播放多個視頻……他告訴我 MPMoviePlayerController 之前是被用來在游戲中做場景切換的走敌。。逗噩。這就是為什么之前的 iOS 版本只能全屏播放掉丽,該局限是個歷史遺留問題。
當(dāng)我回到我的筆記本前异雁,我用 AVFoundation.framework 寫了一個非炒氛希基礎(chǔ)的視頻播放版本。顯然纲刀,當(dāng)我回到公司后项炼,我需要寫一個更加詳細(xì)的版本才能用在項(xiàng)目中。
好了,故事講完了锭部。讓我們回到工作中來暂论!
AVFoundation 框架提供了 AVPlayer 對象來實(shí)現(xiàn)單視頻或多視頻播放的控制器和用戶接口。由 AVPlayer 對象生成的可視結(jié)果可以顯示在 AVPlayerLayer 類的 CoreAnimation 層上拌禾。 在 AVFoundation 中取胎,AVAsset 對象用來表示定時影音媒體,比如視頻和音頻湃窍。根據(jù)相關(guān)文檔闻蛀,每個資源包含一個用來一起呈現(xiàn)或處理的軌道集合,每個統(tǒng)一媒體類型您市,包括不僅限于音頻觉痛、視頻、文本墨坚、隱藏式字幕秧饮、字幕映挂。因?yàn)槎〞r影音媒體的性質(zhì)泽篮,在成功初始化一個資源后,某些或全部鍵值可能不會立即可用柑船。為了避免阻塞主線程帽撑,你可以在特定鍵注冊你感興趣的內(nèi)容,以在可用時被通知到鞍时。
考慮到這一點(diǎn)亏拉,繼承 UIViewController 并命名為 VideoPlayerViewController。就像 MPMoviePlayerController 逆巍,讓我們添加一個 NSURL 屬性及塘,用于告訴我們應(yīng)該從哪里抓取視頻。就像上面描述的那樣锐极,添加下面的代碼笙僚,一旦 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 被賦值后肋层,創(chuàng)建一個資源來檢查被指定的URL引用的源并且異步的加載這個資源的 “tracks” 和 “playable” 鍵值。等加載結(jié)束后翎迁,我們就可以在主線程操作 AVPlayer(當(dāng)播放狀態(tài)動態(tài)改變時栋猖,主線程可以確保安全的獲取播放器的非原子屬性)。
#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 對象播放的資源的表示狀態(tài))和一個 AVPlayer 來播放的資源。請注意雌团,我在這一點(diǎn)上沒有添加任何錯誤處理爆班。在這里,我們應(yīng)該創(chuàng)建一個委托并讓視圖控制器或正在使用你的播放器的用戶辱姨,決定如何以最好的方式來處理可能出現(xiàn)的錯誤柿菩。
我們也添加了一些鍵值監(jiān)聽以便于當(dāng)我們的視圖被綁定到播放器時和 AVPlayerItem 準(zhǔn)備好播放時收到通知。
#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 設(shè)置好后雨涛,我們可以自由的將 AVPlayer 添加到用來展示可視輸出的播放器層枢舶。我們也會確保保留視頻的長寬比和適合視頻圖層的邊界內(nèi)。
一旦 AVPlayer 準(zhǔn)備好了替久,就讓它開始播放凉泄!讓 iOS 來完成艱巨的任務(wù) :)
正如我前面所說,為了播放資源可視組件蚯根,您需要一個包含 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
到這里就結(jié)束了颅拦!
當(dāng)然蒂誉,我沒有貼上所有用來編譯和運(yùn)行的項(xiàng)目代碼,但我不會讓你失望 距帅!轉(zhuǎn)到 GitHub 并下載完整的源代碼 右锨!
?