版本記錄
版本號(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
AVPlayer
和AVPlayerItem
是非可視對(duì)象染突,并且自己無(wú)法在屏幕上呈現(xiàn)資源的視頻。您有兩種不同的選擇可供您在app中顯示影片內(nèi)容您市。
-
AVKit
- 在iOS或tvOS中觉痛,演示您的視頻內(nèi)容的最佳方式是使用AVKit框架的
AVPlayerViewController
,或者在macOS中使用AVPlayerView茵休。這些對(duì)象呈現(xiàn)視頻內(nèi)容薪棒,以及播放控件和其他媒體功能,為您提供全功能的播放體驗(yàn)榕莺。
- 在iOS或tvOS中觉痛,演示您的視頻內(nèi)容的最佳方式是使用AVKit框架的
-
AVPlayerLayer
- 如果您為播放器構(gòu)建自定義界面俐芯,則可以使用由
AVFoundatio
n提供的稱為AVPlayerLayer
的CALayer子類。播放器層可以設(shè)置為視圖的背襯層钉鸯,或者可以直接添加到層次結(jié)構(gòu)吧史。與AVPlayerView
或AVPlayerViewController
不同,AVPlayerLayer不提供任何播放控件唠雕,而只顯示播放器的視覺(jué)內(nèi)容贸营。 建立播放傳輸控制來(lái)播放,暫停和seek媒體是由你決定的岩睁。
- 如果您為播放器構(gòu)建自定義界面俐芯,則可以使用由
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)始播放。
AVPlayer
和AVPlayerItem
提供了播放器項(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ù)~~~