版本記錄
| 版本號 | 時間 |
|---|---|
| V1.0 | 2017.08.30 |
前言
AVFoundation框架是ios中很重要的框架,所有與視頻音頻相關(guān)的軟硬件控制都在這個框架里面,接下來這幾篇就主要對這個框架進(jìn)行介紹和講解。感興趣的可以看我上幾篇。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實(shí)現(xiàn)視頻預(yù)覽錄制保存到相冊
3. AVFoundation框架解析(三)—— 幾個關(guān)鍵問題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個關(guān)鍵問題之AVFoundation探索(一)
媒體文件播放
上一節(jié)描述的資源模型是播放用例的基石。 資源代表您想要播放的媒體,但只是圖片的一部分。 本節(jié)討論播放媒體所需的附加對象,并顯示如何配置播放媒體,如下圖所示。

1. AVPlayer
AVPlayer是驅(qū)動播放用例的中心類。 播放器是控制媒體資源的播放和定時的控制器對象。 您可以使用它來播放本地,逐漸下載或流媒體,并以編程方式控制其演示。
注意:您一次使用AVPlayer播放單個媒體資源。 該框架還提供了AVPlayer的一個子類,稱為AVQueuePlayer,用于創(chuàng)建和管理要順序播放的媒體資源隊列。
2. AVPlayerItem
AVAsset僅限于媒體的靜態(tài)方面,如其持續(xù)時間或創(chuàng)建日期,并且本身不適合用AVPlayer播放 。要播放資源,您可以在AVPlayerItem中創(chuàng)建一個動態(tài)對象的實(shí)例。該對象模擬了AVPlayer播放資產(chǎn)的時間和呈現(xiàn)狀態(tài)。使用AVPlayerItem的屬性和方法,您可以在媒體中尋找不同的時間,確定其演示大小,識別其當(dāng)前時間等等。
3. AVKit 和 AVPlayerLayer
AVPlayer和AVPlayerItem是非可視對象,并且自己無法在屏幕上呈現(xiàn)資源的視頻。您有兩種不同的選擇可供您在app中顯示影片內(nèi)容。
-
AVKit- 在iOS或tvOS中,演示您的視頻內(nèi)容的最佳方式是使用AVKit框架的
AVPlayerViewController,或者在macOS中使用AVPlayerView。這些對象呈現(xiàn)視頻內(nèi)容,以及播放控件和其他媒體功能,為您提供全功能的播放體驗(yàn)。
- 在iOS或tvOS中,演示您的視頻內(nèi)容的最佳方式是使用AVKit框架的
-
AVPlayerLayer- 如果您為播放器構(gòu)建自定義界面,則可以使用由
AVFoundation提供的稱為AVPlayerLayer的CALayer子類。播放器層可以設(shè)置為視圖的背襯層,或者可以直接添加到層次結(jié)構(gòu)。與AVPlayerView或AVPlayerViewController不同,AVPlayerLayer不提供任何播放控件,而只顯示播放器的視覺內(nèi)容。 建立播放傳輸控制來播放,暫停和seek媒體是由你決定的。
- 如果您為播放器構(gòu)建自定義界面,則可以使用由
4. 設(shè)置播放對象
以下示例顯示了為播放場景創(chuàng)建完整對象圖所需的步驟。 該示例是為iOS和tvOS編寫的,但是同樣的基本步驟也適用于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)建播放對象后,您可以調(diào)用播放器的播放方式開始播放。
AVPlayer和AVPlayerItem提供了播放器項(xiàng)目的媒體可以使用時控制播放的各種方式。 下一步是查看如何觀察播放對象的狀態(tài),以便您可以確定播放準(zhǔn)備狀態(tài)。
播放狀態(tài)的觀察
AVPlayer和AVPlayerItem是其狀態(tài)頻繁變化的動態(tài)對象。 您經(jīng)常想采取行動來回應(yīng)這些變化,而您的方式是通過使用鍵值觀察(KVO)。使用KVO,一個對象可以注冊以觀察另一個對象的狀態(tài)。 觀察對象狀態(tài)發(fā)生變化時,將通知狀態(tài)變化的細(xì)節(jié)。使用KVO,您可以輕松地觀察AVPlayer和AVPlayerItem的狀態(tài)更改,并采取行動作為響應(yīng)。
要觀察的最重要的AVPlayerItem屬性之一是其狀態(tài)。 該狀態(tài)指示播放器項(xiàng)目是否準(zhǔn)備好播放并且通??梢允褂?。 當(dāng)您首次創(chuàng)建播放器項(xiàng)目時,其狀態(tài)具有AVPlayerItemStatusUnknown的值,這意味著其媒體尚未加載或入隊進(jìn)行播放。當(dāng)您將播放器項(xiàng)目與AVPlayer相關(guān)聯(lián)時,它會立即開始對項(xiàng)目的媒體進(jìn)行排隊并準(zhǔn)備播放。 當(dāng)其狀態(tài)更改為AVPlayerItemStatusReadyToPlay時,播放器項(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方法注冊以使用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ā)生變化時,都會調(diào)用此方法,讓您有機(jī)會采取一些措施。
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)目的媒體時遇到問題,則狀態(tài)為AVPlayerItemStatusFailed。 如果發(fā)生故障,您可以通過查詢播放器項(xiàng)的錯誤屬性來檢索提供故障詳細(xì)信息的NSError對象。
執(zhí)行基于時間的操作
媒體播放是一種基于時間的活動,您可以在一定時間內(nèi)以固定的速率提供定時媒體樣本。 基于時間的操作,例如通過媒體進(jìn)行搜索,在構(gòu)建媒體播放app時發(fā)揮核心作用。 AVPlayer和AVPlayerItem的許多關(guān)鍵特性與控制媒體時序有關(guān)。 要學(xué)習(xí)有效地使用這些功能,您應(yīng)該了解AVFoundation中如何表現(xiàn)時間。
幾個Apple框架,包括AVFoundation的一些部分,表示時間作為表示秒的浮點(diǎn)NSTimeInterval值。 在許多情況下,這提供了一種思考和表達(dá)時間的自然方式,但是在執(zhí)行定時的媒體操作時通常會遇到問題。 在使用媒體時保持采樣準(zhǔn)確的時序非常重要,而浮點(diǎn)不精確通常會導(dǎo)致定時漂移。 為了解決這些不精確,AVFoundation表示使用Core Media框架的CMTime數(shù)據(jù)類型的時間。
public struct CMTime {
public var value: CMTimeValue
public var timescale: CMTimeScale
public var flags: CMTimeFlags
public var epoch: CMTimeEpoch
}
該結(jié)構(gòu)定義了時間的理性或分?jǐn)?shù)表示。 CMTime定義的兩個最重要的字段是它的值和時間尺度。 CMTimeValue是定義分?jǐn)?shù)時間分子的64位整數(shù),CMTimeScale是一個定義分母的32位整數(shù)。 這個結(jié)構(gòu)使得很容易表示以媒體幀率或采樣率表示的時間。
// 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值的方法,并對它們進(jìn)行算術(shù),比較,驗(yàn)證和轉(zhuǎn)換操作。 如果您使用Swift,Core Media還會向CMTime添加一些擴(kuò)展和運(yùn)算符重載,從而使執(zhí)行許多常見操作變得簡單而自然。
1. 觀察時間
您通常希望觀察播放時間,以便您可以更新播放位置或以其他方式同步用戶界面的狀態(tài)。 早些時候,您看到了如何使用KVO觀察播放對象的狀態(tài)。KVO適用于一般狀態(tài)觀察,但不是觀察播放器時間的正確選擇,因?yàn)樗贿m合觀察連續(xù)狀態(tài)變化。 相反,AVPlayer提供了兩種不同的方式來觀察播放器時間變化:定期觀察和邊界觀察。
定期觀察
您可以按照定期,周期性的間隔觀察時間。 如果您正在構(gòu)建自定義播放器,定期觀察的最常見用例是更新用戶界面中的時間顯示。
要觀察周期性時間,您可以使用播放器的addPeriodicTimeObserverForInterval:queue:usingBlock:方法。 該方法采用表示時間間隔的CMTime,串行調(diào)度隊列和在指定時間間隔內(nèi)調(diào)用的回調(diào)塊。 以下示例顯示如何在正常播放期間每半秒設(shè)置一個被調(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
}
}
邊界觀察
另一種方式可以觀察時間是邊界。 您可以在媒體時間軸內(nèi)定義各種感興趣的興趣點(diǎn),并且框架會在正常播放期間隨著時間的推移而呼叫您。 邊界觀測比定期觀察使用的頻率低,但在某些情況下仍然可以證明有用。 例如,如果您正在呈現(xiàn)無播放控件的視頻,則可能會使用邊界觀察,并希望同時顯示或顯示補(bǔ)充內(nèi)容的元素同步時間。
要觀察邊界時間,您可以使用播放器的addBoundaryTimeObserverForTimes:queue:usingBlock:方法。 該方法使用一個包含定義邊界時間的CMTime值的NSValue對象數(shù)組,一個串行調(diào)度隊列和一個回調(diào)塊。 以下示例顯示如何定義每四分之一回放的邊界時間。
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自動為您提供刷新控件(如果媒體支持),但如果您正在構(gòu)建自定義播放器,則需要自己構(gòu)建此功能。 即使在使用AVKit的情況下,您仍然可能希望提供補(bǔ)充用戶界面,例如表視圖或集合視圖,可讓用戶快速跳到媒體中的各個位置。
您可以通過多種方式使用AVPlayer和AVPlayerItem的方法進(jìn)行seek。 最常見的方法是使用播放器的seekToTime:方法,傳遞一個目標(biāo)CMTime值,如下所示:
// Seek to the 2 minute mark
let time = CMTime(value: 120, timescale: 1)
player.seek(to: time)
seekToTime:方法是一種快速seek的方便方法,但它更適合速度而不是精確度。 這意味著播放器移動的實(shí)際時間可能與您請求的時間不同。 如果您需要實(shí)現(xiàn)精確的搜索行為,請使用seekToTime:toleranceBefore:toleranceAfter:方法,它允許您指定與目標(biāo)時間(前后)的容忍偏差量。 如果您需要提供樣本精確的搜索行為,您可以指出允許零容限。
// 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:具有小或零值容差的方法可能會產(chǎn)生額外的解碼延遲,這會影響app的seek行為。
了解如何表達(dá)和使用時間,觀察播放器的時序,并通過媒體進(jìn)行搜索,現(xiàn)在是時候來看看AVKit提供的更多平臺特性。
后記
未完,待續(xù)~~~
