AVFoundation框架解析(五)—— 幾個(gè)關(guān)鍵問(wèn)題之AVFoundation探索(二)

版本記錄

版本號(hào) 時(shí)間
V1.0 2017.08.30

前言

AVFoundation框架是ios中很重要的框架尝偎,所有與視頻音頻相關(guān)的軟硬件控制都在這個(gè)框架里面白粉,接下來(lái)這幾篇就主要對(duì)這個(gè)框架進(jìn)行介紹和講解讯壶。感興趣的可以看我上幾篇。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實(shí)現(xiàn)視頻預(yù)覽錄制保存到相冊(cè)
3. AVFoundation框架解析(三)—— 幾個(gè)關(guān)鍵問(wèn)題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個(gè)關(guān)鍵問(wèn)題之AVFoundation探索(一)

媒體文件播放

上一節(jié)描述的資源模型是播放用例的基石状婶。 資源代表您想要播放的媒體,但只是圖片的一部分馅巷。 本節(jié)討論播放媒體所需的附加對(duì)象膛虫,并顯示如何配置播放媒體,如下圖所示钓猬。

1. AVPlayer

AVPlayer是驅(qū)動(dòng)播放用例的中心類稍刀。 播放器是控制媒體資源的播放和定時(shí)的控制器對(duì)象。 您可以使用它來(lái)播放本地敞曹,逐漸下載或流媒體账月,并以編程方式控制其演示。

注意:您一次使用AVPlayer播放單個(gè)媒體資源澳迫。 該框架還提供了AVPlayer的一個(gè)子類局齿,稱為AVQueuePlayer,用于創(chuàng)建和管理要順序播放的媒體資源隊(duì)列橄登。

2. AVPlayerItem

AVAsset僅限于媒體的靜態(tài)方面抓歼,如其持續(xù)時(shí)間或創(chuàng)建日期讥此,并且本身不適合用AVPlayer播放 。要播放資源谣妻,您可以在AVPlayerItem中創(chuàng)建一個(gè)動(dòng)態(tài)對(duì)象的實(shí)例萄喳。該對(duì)象模擬了AVPlayer播放資產(chǎn)的時(shí)間和呈現(xiàn)狀態(tài)。使用AVPlayerItem的屬性和方法蹋半,您可以在媒體中尋找不同的時(shí)間他巨,確定其演示大小,識(shí)別其當(dāng)前時(shí)間等等减江。

3. AVKit 和 AVPlayerLayer

AVPlayerAVPlayerItem是非可視對(duì)象染突,并且自己無(wú)法在屏幕上呈現(xiàn)資源的視頻。您有兩種不同的選擇可供您在app中顯示影片內(nèi)容您市。

  • AVKit

    • 在iOS或tvOS中觉痛,演示您的視頻內(nèi)容的最佳方式是使用AVKit框架的AVPlayerViewController,或者在macOS中使用AVPlayerView茵休。這些對(duì)象呈現(xiàn)視頻內(nèi)容薪棒,以及播放控件和其他媒體功能,為您提供全功能的播放體驗(yàn)榕莺。
  • AVPlayerLayer

    • 如果您為播放器構(gòu)建自定義界面俐芯,則可以使用由AVFoundation提供的稱為AVPlayerLayer的CALayer子類。播放器層可以設(shè)置為視圖的背襯層钉鸯,或者可以直接添加到層次結(jié)構(gòu)吧史。與AVPlayerViewAVPlayerViewController不同,AVPlayerLayer不提供任何播放控件唠雕,而只顯示播放器的視覺(jué)內(nèi)容贸营。 建立播放傳輸控制來(lái)播放,暫停和seek媒體是由你決定的岩睁。

4. 設(shè)置播放對(duì)象

以下示例顯示了為播放場(chǎng)景創(chuàng)建完整對(duì)象圖所需的步驟钞脂。 該示例是為iOS和tvOS編寫(xiě)的,但是同樣的基本步驟也適用于macOS捕儒。

class PlayerViewController: UIViewController {
 
    @IBOutlet weak var playerViewController: AVPlayerViewController!
 
    var player: AVPlayer!
    var playerItem: AVPlayerItem!
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        // 1) Define asset URL
        let url: URL = // URL to local or streamed media
 
        // 2) Create asset instance
        let asset = AVAsset(url: url)
 
        // 3) Create player item
        playerItem = AVPlayerItem(asset: asset)
 
        // 4) Create player instance
        player = AVPlayer(playerItem: playerItem)
 
        // 5) Associate player with view controller
        playerViewController.player = player
    }
 
}

創(chuàng)建播放對(duì)象后冰啃,您可以調(diào)用播放器的播放方式開(kāi)始播放。

AVPlayerAVPlayerItem提供了播放器項(xiàng)目的媒體可以使用時(shí)控制播放的各種方式刘莹。 下一步是查看如何觀察播放對(duì)象的狀態(tài)阎毅,以便您可以確定播放準(zhǔn)備狀態(tài)。


播放狀態(tài)的觀察

AVPlayer和AVPlayerItem是其狀態(tài)頻繁變化的動(dòng)態(tài)對(duì)象点弯。 您經(jīng)常想采取行動(dòng)來(lái)回應(yīng)這些變化扇调,而您的方式是通過(guò)使用鍵值觀察(KVO)。使用KVO抢肛,一個(gè)對(duì)象可以注冊(cè)以觀察另一個(gè)對(duì)象的狀態(tài)肃拜。 觀察對(duì)象狀態(tài)發(fā)生變化時(shí)痴腌,將通知狀態(tài)變化的細(xì)節(jié)。使用KVO燃领,您可以輕松地觀察AVPlayer和AVPlayerItem的狀態(tài)更改士聪,并采取行動(dòng)作為響應(yīng)。

要觀察的最重要的AVPlayerItem屬性之一是其狀態(tài)猛蔽。 該狀態(tài)指示播放器項(xiàng)目是否準(zhǔn)備好播放并且通嘲颍可以使用。 當(dāng)您首次創(chuàng)建播放器項(xiàng)目時(shí)曼库,其狀態(tài)具有AVPlayerItemStatusUnknown的值区岗,這意味著其媒體尚未加載或入隊(duì)進(jìn)行播放。當(dāng)您將播放器項(xiàng)目與AVPlayer相關(guān)聯(lián)時(shí)毁枯,它會(huì)立即開(kāi)始對(duì)項(xiàng)目的媒體進(jìn)行排隊(duì)并準(zhǔn)備播放慈缔。 當(dāng)其狀態(tài)更改為AVPlayerItemStatusReadyToPlay時(shí),播放器項(xiàng)目就可以使用了种玛。 以下示例顯示如何觀察此狀態(tài)更改藐鹤。

let url: URL = // Asset URL
 
var asset: AVAsset!
var player: AVPlayer!
var playerItem: AVPlayerItem!
 
// Key-value observing context
private var playerItemContext = 0
 
let requiredAssetKeys = [
    "playable",
    "hasProtectedContent"
]
 
func prepareToPlay() {
    // Create the asset to play
    asset = AVAsset(url: url)
 
    // Create a new AVPlayerItem with the asset and an
    // array of asset keys to be automatically loaded
    playerItem = AVPlayerItem(asset: asset,
                              automaticallyLoadedAssetKeys: requiredAssetKeys)
 
    // Register as an observer of the player item's status property
    playerItem.addObserver(self,
                           forKeyPath: #keyPath(AVPlayerItem.status),
                           options: [.old, .new],
                           context: &playerItemContext)
 
    // Associate the player item with the player
    player = AVPlayer(playerItem: playerItem)
}

prepareToPlay方法注冊(cè)以使用addObserver:forKeyPath:options:context:method觀察播放器的狀態(tài)屬性。 在將播放器項(xiàng)目與播放器關(guān)聯(lián)之前調(diào)用此方法赂韵,以確保將所有狀態(tài)更改捕獲到項(xiàng)目的狀態(tài)娱节。

要通知狀態(tài)更改,您將實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法祭示。 每當(dāng)狀態(tài)發(fā)生變化時(shí)肄满,都會(huì)調(diào)用此方法,讓您有機(jī)會(huì)采取一些措施质涛。

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
 
    // Only handle observations for the playerItemContext
    guard context == &playerItemContext else {
        super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
        return
    }
 
    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItemStatus
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }
        // Switch over status value
        switch status {
        case .readyToPlay:
            // Player item is ready to play.
        case .failed:
            // Player item failed. See error.
        case .unknown:
            // Player item is not yet ready.
        }
    }
}

該示例從更改字典中檢索新的狀態(tài)值并切換其值稠歉。 如果播放器的狀態(tài)為AVPlayerItemStatusReadyToPlay,則可以使用汇陆。 如果在嘗試加載播放器項(xiàng)目的媒體時(shí)遇到問(wèn)題轧抗,則狀態(tài)為AVPlayerItemStatusFailed。 如果發(fā)生故障瞬测,您可以通過(guò)查詢播放器項(xiàng)的錯(cuò)誤屬性來(lái)檢索提供故障詳細(xì)信息的NSError對(duì)象。


執(zhí)行基于時(shí)間的操作

媒體播放是一種基于時(shí)間的活動(dòng)纠炮,您可以在一定時(shí)間內(nèi)以固定的速率提供定時(shí)媒體樣本月趟。 基于時(shí)間的操作,例如通過(guò)媒體進(jìn)行搜索恢口,在構(gòu)建媒體播放app時(shí)發(fā)揮核心作用孝宗。 AVPlayer和AVPlayerItem的許多關(guān)鍵特性與控制媒體時(shí)序有關(guān)。 要學(xué)習(xí)有效地使用這些功能耕肩,您應(yīng)該了解AVFoundation中如何表現(xiàn)時(shí)間因妇。

幾個(gè)Apple框架问潭,包括AVFoundation的一些部分,表示時(shí)間作為表示秒的浮點(diǎn)NSTimeInterval值婚被。 在許多情況下狡忙,這提供了一種思考和表達(dá)時(shí)間的自然方式,但是在執(zhí)行定時(shí)的媒體操作時(shí)通常會(huì)遇到問(wèn)題址芯。 在使用媒體時(shí)保持采樣準(zhǔn)確的時(shí)序非常重要灾茁,而浮點(diǎn)不精確通常會(huì)導(dǎo)致定時(shí)漂移。 為了解決這些不精確谷炸,AVFoundation表示使用Core Media框架的CMTime數(shù)據(jù)類型的時(shí)間北专。

public struct CMTime {
    public var value: CMTimeValue
    public var timescale: CMTimeScale
    public var flags: CMTimeFlags
    public var epoch: CMTimeEpoch
}

該結(jié)構(gòu)定義了時(shí)間的理性或分?jǐn)?shù)表示。 CMTime定義的兩個(gè)最重要的字段是它的值和時(shí)間尺度旬陡。 CMTimeValue是定義分?jǐn)?shù)時(shí)間分子的64位整數(shù)拓颓,CMTimeScale是一個(gè)定義分母的32位整數(shù)。 這個(gè)結(jié)構(gòu)使得很容易表示以媒體幀率或采樣率表示的時(shí)間描孟。

// 0.25 seconds
let quarterSecond = CMTime(value: 1, timescale: 4)
 
// 10 second mark in a 44.1 kHz audio file
let tenSeconds = CMTime(value: 441000, timescale: 44100)
 
// 3 seconds into a 30fps video
let cursor = CMTime(value: 90, timescale: 30)

Core Media提供了許多創(chuàng)建CMTime值的方法驶睦,并對(duì)它們進(jìn)行算術(shù),比較画拾,驗(yàn)證和轉(zhuǎn)換操作啥繁。 如果您使用Swift,Core Media還會(huì)向CMTime添加一些擴(kuò)展和運(yùn)算符重載青抛,從而使執(zhí)行許多常見(jiàn)操作變得簡(jiǎn)單而自然旗闽。

1. 觀察時(shí)間

您通常希望觀察播放時(shí)間,以便您可以更新播放位置或以其他方式同步用戶界面的狀態(tài)蜜另。 早些時(shí)候适室,您看到了如何使用KVO觀察播放對(duì)象的狀態(tài)。KVO適用于一般狀態(tài)觀察举瑰,但不是觀察播放器時(shí)間的正確選擇捣辆,因?yàn)樗贿m合觀察連續(xù)狀態(tài)變化。 相反此迅,AVPlayer提供了兩種不同的方式來(lái)觀察播放器時(shí)間變化:定期觀察和邊界觀察汽畴。

定期觀察

您可以按照定期,周期性的間隔觀察時(shí)間耸序。 如果您正在構(gòu)建自定義播放器忍些,定期觀察的最常見(jiàn)用例是更新用戶界面中的時(shí)間顯示。

要觀察周期性時(shí)間坎怪,您可以使用播放器的addPeriodicTimeObserverForInterval:queue:usingBlock:方法罢坝。 該方法采用表示時(shí)間間隔的CMTime,串行調(diào)度隊(duì)列和在指定時(shí)間間隔內(nèi)調(diào)用的回調(diào)塊搅窿。 以下示例顯示如何在正常播放期間每半秒設(shè)置一個(gè)被調(diào)用的塊嘁酿。

var player: AVPlayer!
var playerItem: AVPlayerItem!
var timeObserverToken: Any?
 
func addPeriodicTimeObserver() {
    // Notify every half second
    let timeScale = CMTimeScale(NSEC_PER_SEC)
    let time = CMTime(seconds: 0.5, preferredTimescale: timeScale)
    timeObserverToken = player.addPeriodicTimeObserver(forInterval: time,
                                                       queue: .main) {
        [weak self] time in
        // update player transport UI
    }
}
 
func removePeriodicTimeObserver() {
    if let timeObserverToken = timeObserverToken {
        player.removeTimeObserver(timeObserverToken)
        self.timeObserverToken = nil
    }
}

邊界觀察

另一種方式可以觀察時(shí)間是邊界隙券。 您可以在媒體時(shí)間軸內(nèi)定義各種感興趣的興趣點(diǎn),并且框架會(huì)在正常播放期間隨著時(shí)間的推移而呼叫您闹司。 邊界觀測(cè)比定期觀察使用的頻率低娱仔,但在某些情況下仍然可以證明有用。 例如开仰,如果您正在呈現(xiàn)無(wú)播放控件的視頻拟枚,則可能會(huì)使用邊界觀察,并希望同時(shí)顯示或顯示補(bǔ)充內(nèi)容的元素同步時(shí)間众弓。

要觀察邊界時(shí)間恩溅,您可以使用播放器的addBoundaryTimeObserverForTimes:queue:usingBlock:方法。 該方法使用一個(gè)包含定義邊界時(shí)間的CMTime值的NSValue對(duì)象數(shù)組谓娃,一個(gè)串行調(diào)度隊(duì)列和一個(gè)回調(diào)塊脚乡。 以下示例顯示如何定義每四分之一回放的邊界時(shí)間。

var asset: AVAsset!
var player: AVPlayer!
var playerItem: AVPlayerItem!
var timeObserverToken: Any?

func addBoundaryTimeObserver() {

   // Divide the asset's duration into quarters.
   let interval = CMTimeMultiplyByFloat64(asset.duration, 0.25)
   var currentTime = kCMTimeZero
   var times = [NSValue]()

   // Calculate boundary times
   while currentTime < asset.duration {
       currentTime = currentTime + interval
       times.append(NSValue(time:currentTime))
   }

   timeObserverToken = player.addBoundaryTimeObserver(forTimes: times,
                                                      queue: .main) {
       // Update UI
   }
}

func removeBoundaryTimeObserver() {
   if let timeObserverToken = timeObserverToken {
       player.removeTimeObserver(timeObserverToken)
       self.timeObserverToken = nil
   }
}

2.媒體 seek

除了正常的線性播放之外滨达,用戶還希望能夠以非線性方式尋找或刷新以快速獲得媒體內(nèi)的各種興趣點(diǎn)奶稠。 AVKit自動(dòng)為您提供刷新控件(如果媒體支持),但如果您正在構(gòu)建自定義播放器捡遍,則需要自己構(gòu)建此功能锌订。 即使在使用AVKit的情況下,您仍然可能希望提供補(bǔ)充用戶界面画株,例如表視圖或集合視圖辆飘,可讓用戶快速跳到媒體中的各個(gè)位置。

您可以通過(guò)多種方式使用AVPlayer和AVPlayerItem的方法進(jìn)行seek谓传。 最常見(jiàn)的方法是使用播放器的seekToTime:方法蜈项,傳遞一個(gè)目標(biāo)CMTime值,如下所示:

// Seek to the 2 minute mark
let time = CMTime(value: 120, timescale: 1)
player.seek(to: time)

seekToTime:方法是一種快速seek的方便方法续挟,但它更適合速度而不是精確度紧卒。 這意味著播放器移動(dòng)的實(shí)際時(shí)間可能與您請(qǐng)求的時(shí)間不同。 如果您需要實(shí)現(xiàn)精確的搜索行為诗祸,請(qǐng)使用seekToTime:toleranceBefore:toleranceAfter:方法跑芳,它允許您指定與目標(biāo)時(shí)間(前后)的容忍偏差量。 如果您需要提供樣本精確的搜索行為直颅,您可以指出允許零容限博个。

// Seek to the first frame at 3:25 mark
let seekTime = CMTime(seconds: 205, preferredTimescale: Int32(NSEC_PER_SEC))
player.seek(to: seekTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

這里要注意的是:調(diào)用seekToTime:toleranceBefore:toleranceAfter:具有小或零值容差的方法可能會(huì)產(chǎn)生額外的解碼延遲,這會(huì)影響app的seek行為际乘。

了解如何表達(dá)和使用時(shí)間,觀察播放器的時(shí)序漂佩,并通過(guò)媒體進(jìn)行搜索脖含,現(xiàn)在是時(shí)候來(lái)看看AVKit提供的更多平臺(tái)特性罪塔。

后記

未完,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末养葵,一起剝皮案震驚了整個(gè)濱河市征堪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌关拒,老刑警劉巖佃蚜,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異着绊,居然都是意外死亡谐算,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)归露,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洲脂,“玉大人,你說(shuō)我怎么就攤上這事剧包】纸酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵疆液,是天一觀的道長(zhǎng)一铅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)堕油,這世上最難降的妖魔是什么潘飘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮馍迄,結(jié)果婚禮上福也,老公的妹妹穿的比我還像新娘。我一直安慰自己攀圈,他們只是感情好暴凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赘来,像睡著了一般现喳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犬辰,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天嗦篱,我揣著相機(jī)與錄音,去河邊找鬼幌缝。 笑死灸促,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浴栽,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼荒叼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了典鸡?” 一聲冷哼從身側(cè)響起被廓,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萝玷,沒(méi)想到半個(gè)月后嫁乘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡球碉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年蜓斧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汁尺。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡法精,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痴突,到底是詐尸還是另有隱情搂蜓,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布辽装,位于F島的核電站帮碰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拾积。R本人自食惡果不足惜殉挽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拓巧。 院中可真熱鬧斯碌,春花似錦、人聲如沸肛度。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)承耿。三九已至冠骄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間加袋,已是汗流浹背凛辣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留职烧,地道東北人扁誓。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓防泵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蝗敢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子择克,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容