AVFoundation框架解析(二十一)—— 一個簡單的視頻流預(yù)覽和播放示例之解析(一)

版本記錄

版本號 時間
V1.0 2018.10.01 星期一

前言

AVFoundation框架是ios中很重要的框架,所有與視頻音頻相關(guān)的軟硬件控制都在這個框架里面,接下來這幾篇就主要對這個框架進(jìn)行介紹和講解。感興趣的可以看我上幾篇。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實現(xiàn)視頻預(yù)覽錄制保存到相冊
3. AVFoundation框架解析(三)—— 幾個關(guān)鍵問題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個關(guān)鍵問題之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 幾個關(guān)鍵問題之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 視頻音頻的合成(一)
7. AVFoundation框架解析(七)—— 視頻組合和音頻混合調(diào)試
8. AVFoundation框架解析(八)—— 優(yōu)化用戶的播放體驗
9. AVFoundation框架解析(九)—— AVFoundation的變化(一)
10. AVFoundation框架解析(十)—— AVFoundation的變化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的變化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的變化(四)
13. AVFoundation框架解析(十三)—— 構(gòu)建基本播放應(yīng)用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)
15. AVFoundation框架解析(十五)—— VAssetWriter和AVAssetReader的Timecode支持(二)
16. AVFoundation框架解析(十六)—— 一個簡單示例之播放、錄制以及混合視頻(一)
17. AVFoundation框架解析(十七)—— 一個簡單示例之播放、錄制以及混合視頻之源碼及效果展示(二)
18. AVFoundation框架解析(十八)—— AVAudioEngine之基本概覽(一)
19. AVFoundation框架解析(十九)—— AVAudioEngine之詳細(xì)說明和一個簡單示例(二)
20. AVFoundation框架解析(二十)—— AVAudioEngine之詳細(xì)說明和一個簡單示例源碼(三)

開始

首先看一下寫作環(huán)境

Swift 4.2, iOS 12, Xcode 10

注意:如果您正在使用模擬器Build,請準(zhǔn)備好關(guān)閉斷點或大量點擊跳過按鈕。 如果您看到以AudioHAL_ClientCUICatalog開頭的錯誤或警告,請隨意忽略它們并繼續(xù)。

在開始時,首先建立工程并配置可用資源,這里就不多說了,然后,打開TravelVlogs.xcodeproj并前往VideoFeedViewController.swift。


Introduction to AVKit - AVKit介紹

一個有用的開發(fā)智慧:始終支持您可用的最高抽象級別。 然后,當(dāng)您使用的不再適合您的需求時,您可以降低到較低等級的API。 根據(jù)這一建議,您將在最高級別的視頻框架中開始您的旅程。

AVKit位于AVFoundation之上,提供與視頻交互所需的所有UI。

如果您構(gòu)建并運行,您將看到一個已經(jīng)設(shè)置了一個充滿潛在視頻的表的應(yīng)用程序。

您的目標(biāo)是在用戶點擊其中一個單元格時顯示視頻播放器。

1. Adding Local Playback - 添加本地播放

實際上您可以播放兩種類型的視頻。 你要看的第一個是當(dāng)前已經(jīng)存在于手機硬盤上的類型。 稍后,您將學(xué)習(xí)如何播放從服務(wù)器流式傳輸?shù)囊曨l。

要開始使用,請導(dǎo)航到VideoFeedViewController.swift。 在UIKit導(dǎo)入下方添加以下導(dǎo)入。

import AVKit

看下面這個,你會看到你已經(jīng)有一個tableViewVideos數(shù)組定義。 這就是現(xiàn)有tableView填充數(shù)據(jù)的方式。Videos本身來自視頻管理類。 您可以查看AppDelegate.swift以了解它們是如何獲取的。

接下來,向下滾動,直到找到tableView(_ tableView:didSelectRowAt :)。 將以下代碼添加到現(xiàn)有方法:

//1
let video = videos[indexPath.row]

//2
let videoURL = video.url
let player = AVPlayer(url: videoURL)
  • 1) 首先,您獲取視頻模型對象。
  • 2) 所有Video對象都有一個url屬性,表示視頻文件的路徑。 在這里,您獲取URL并創(chuàng)建一個AVPlayer對象。

AVPlayer是在iOS上播放視頻的核心。

播放器對象可以啟動和停止視頻,更改播放速率甚至可以上下調(diào)節(jié)音量。 您可以將播放器視為能夠一次管理一個媒體資源播放的控制器對象。

在方法結(jié)束時,添加以下行以獲取視圖控制器設(shè)置。

let playerViewController = AVPlayerViewController()
playerViewController.player = player

present(playerViewController, animated: true) {
  player.play()
}

AVPlayerViewController是一個方便的視圖控制器,需要一個player對象才有用。 一旦有了它,就將它呈現(xiàn)為全屏視頻播放器。

演示動畫完成后,您可以調(diào)用play()來啟動視頻。

這就是它的全部! 構(gòu)建并運行以查看其外觀。

視圖控制器顯示一組基本控件。 這包括一個播放器按鈕,一個靜音按鈕和15秒跳過按鈕來前進(jìn)和后退。

2. Adding Remote Playback - 添加遠(yuǎn)程播放

這很簡單。 如何從遠(yuǎn)程URL添加視頻播放? 當(dāng)然,這肯定要困難得多。

轉(zhuǎn)到AppDelegate.swift。 找到feed.videos設(shè)置的行。 而不是加載本地視頻,通過用以下內(nèi)容替換該行來加載所有視頻。

feed.videos = Video.allVideos()

就是這樣! 轉(zhuǎn)到Video.swift。 在這里你可以看到allVideos()只是加載一個額外的視頻。 唯一的區(qū)別是它的url屬性表示W(wǎng)eb上的地址而不是文件路徑。

Build并運行,然后滾動到Feed的底部,找到キツネ村(kitsune-mura)Fox Village視頻。

這就是AVPlayerViewController的美感,你需要的只是一個URL,你很高興!

實際上,轉(zhuǎn)到allVideos()并換掉這一行:

let videoURLString = 
  "https://wolverine.raywenderlich.com/content/ios/tutorials/video_streaming/foxVillage.mp4"

用下面這個

let videoURLString = 
  "https://wolverine.raywenderlich.com/content/ios/tutorials/video_streaming/foxVillage.m3u8"

Build并運行,你會看到fox village的視頻仍然有效。

唯一的區(qū)別是第二個URL代表HLS Livestream。 HLS實時流式傳輸通過將視頻分成10秒塊來實現(xiàn)。 然后,這些服務(wù)一次被提供給客戶端。 正如您在示例GIF中看到的那樣,視頻開始播放的速度比使用MP4版本時快得多。


Adding a Looping Video Preview - 添加循環(huán)視頻預(yù)覽

您可能已經(jīng)注意到右下角的黑盒子。 你將把那個黑盒子變成一個浮動的自定義視頻播放器。 它的目的是播放一組旋轉(zhuǎn)剪輯,讓用戶對所有這些視頻感到興奮。

然后你需要添加一些自定義手勢,如點擊打開聲音和雙擊以將其更改為2倍速度。 當(dāng)您想要對事物的工作方式進(jìn)行非常具體的控制時,最好編寫自己的視頻視圖。

返回VideoFeedViewController.swift并查看屬性定義。 您將看到此類的shell已存在,并且正在使用一組視頻剪輯創(chuàng)建。


Introduction to AVFoundation - AVFoundation簡介

雖然AVFoundation可能會讓人覺得有點嚇人,但是你所處理的大多數(shù)對象仍然是相當(dāng)高級的,所有事情都要考慮。

您需要熟悉的主要課程是:

  • 1) AVPlayerLayer:這個特殊的CALayer子類可以顯示給定AVPlayer對象的回放。
  • 2)AVAsset:這些是媒體資產(chǎn)的靜態(tài)表示。 資產(chǎn)對象包含持續(xù)時間和創(chuàng)建日期等信息。
  • 3)AVPlayerItemAVAsset的動態(tài)對應(yīng)對象。 此對象表示可播放視頻的當(dāng)前狀態(tài)。 這是您需要為AVPlayer提供的東西。

AVFoundation是一個巨大的框架,遠(yuǎn)遠(yuǎn)超出這幾個類。 幸運的是,這就是創(chuàng)建循環(huán)視頻播放器所需的全部內(nèi)容。

你將依次回到這些中,所以不要擔(dān)心記憶它們或任何東西。

1. Writing a Custom Video View with AVPlayerLayer - 使用AVPlayerLayer寫一個自定義視頻視圖

您需要考慮的第一個類是AVPlayerLayer。 此CALayer子類與任何其他圖層類似:它在屏幕上顯示其contents屬性中的任何內(nèi)容。、

這個圖層恰好用你通過player屬性給出的視頻中的幀填充其內(nèi)容。

轉(zhuǎn)到VideoPlayerView.swift,您將在其中找到一個用于顯示視頻的空視圖。

您需要做的第一件事是添加正確的import語句,這次是AVFoundation。

import AVFoundation

現(xiàn)在你可以將AVPlayerLayer融入其中。

UIView實際上只是一個封裝CALayer的包裝器。 它提供了觸摸處理和可訪問性功能,但不是子類。 相反,它擁有并管理底層layer屬性。 一個很好的技巧是你可以實際指定你希望視圖子類擁有哪種類型的層。

添加以下屬性覆蓋以通知此類它應(yīng)使用AVPlayerLayer而不是普通CALayer。

override class var layerClass: AnyClass {
  return AVPlayerLayer.self
}

由于您要在視圖中包裝player layer,因此您需要暴露player屬性。

為此,首先添加以下計算屬性,這樣您就不需要一直強制轉(zhuǎn)換圖層子類。

var playerLayer: AVPlayerLayer {
  return layer as! AVPlayerLayer
}

接下來,使用gettersetter添加實際的player定義。

var player: AVPlayer? {
  get {
    return playerLayer.player
  }

  set {
    playerLayer.player = newValue
  }
}

在這里,您只是設(shè)置并獲取您的playerLayerplayer對象。 UIView真的只是中間媒介。

當(dāng)你開始與player本身互動時,真正的魔力再次出現(xiàn)。

構(gòu)建并運行以查看...

你已經(jīng)到了一半,即使你看不到任何新東西!

2. Writing the Looping Video View - 編寫循環(huán)視頻視圖

接下來,轉(zhuǎn)到VideoLooperView.swift并準(zhǔn)備好使用VideoPlayerView。 這個類已經(jīng)有一組VideoClip,正在初始化一個VideoPlayerView屬性。

您需要做的就是獲取這些片段并想辦法如何在連續(xù)循環(huán)中播放它們。

首先,添加以下player屬性。

private let player = AVQueuePlayer()

這里你會看到這不是普通的AVPlayer實例。 沒錯,這是一個名為AVQueuePlayer的特殊子類。 正如您可以通過名稱猜測的那樣,此類允許您提供要播放的項目隊列。

添加以下方法以開始設(shè)置播放器。

private func initializePlayer() {
  videoPlayerView.player = player
}

在這里,您將player傳遞給videoPlayerView以將其連接到底層AVPlayerLayer。

現(xiàn)在是時候?qū)⒁曨l片段列表添加到player中,以便它可以開始播放。

添加以下方法來執(zhí)行此操作。

private func addAllVideosToPlayer() {
  for video in clips {
    //1
    let asset = AVURLAsset(url: video.url)
    let item = AVPlayerItem(asset: asset)

    //2
    player.insert(item, after: player.items().last)
  }
}

在這里,你循環(huán)遍歷所有clips。 對于每一個,您:

  • 1) 從每個視頻clip對象的URL創(chuàng)建AVURLAsset。
  • 2) 然后,使用播放器可用于控制播放的資源創(chuàng)建AVPlayerItem
  • 3) 最后,使用insert(_after :)方法將每個項添加到隊列中。

現(xiàn)在,返回initializePlayer()并調(diào)用該方法。

player.volume = 0.0
player.play()

這會將循環(huán)clip顯示設(shè)置為默認(rèn)自動播放和音頻關(guān)閉。

最后,您需要調(diào)用您一直在使用的方法。 轉(zhuǎn)到init(clips :)方法并在底部添加此行。

initializePlayer()

構(gòu)建并運行以查看完整的節(jié)目!

不幸的是,當(dāng)最后一個片段播放完畢后,視頻播放器會漸變?yōu)楹谏?/p>

3. Doing the Actual Looping - 進(jìn)行實際循環(huán)

Apple編寫了一個名為AVPlayerLooper的漂亮新類。 這個類將采用單個播放器項目,并負(fù)責(zé)在循環(huán)中播放該項目所需的所有邏輯。 不幸的是,這對你沒有幫助!

你想要的是能夠循環(huán)播放所有這些視頻。 看起來你必須以手動方式做事。 您需要做的就是跟蹤您的播放器和當(dāng)前播放的項目。 當(dāng)它到達(dá)最后一個視頻時,您將再次將所有剪輯添加到隊列中。

當(dāng)談到“跟蹤”播放器的信息時,唯一的途徑是使用鍵值觀察KVO。

是的,這是蘋果公司提出的更為令人難以置信的API之一。 即便如此,如果你小心,它是一種實時觀察和響應(yīng)狀態(tài)變化的有效方式。 如果您對KVO完全不熟悉,下面就是其簡要的了解。 基本思想是您在特定屬性的值發(fā)生變化時注冊通知。 在這種情況下,您想知道player‘s currentItem何時發(fā)生變化。 每次收到通知時,您都會知道播放器已進(jìn)入下一個視頻。

您需要做的第一件事是更改之前定義的player屬性。 轉(zhuǎn)到文件頂部并將舊定義替換為:

@objc private let player = AVQueuePlayer()

唯一的區(qū)別是你添加了@objc指令。 這告訴Swift你想要將屬性暴露給像KVO這樣的Objective-C之類的東西。 要在Swift中使用KVO - 比Objective-C好得多 - 你需要保留對觀察者的引用。 在player之后添加以下屬性:

private var token: NSKeyValueObservation?

要開始觀察屬性,請返回initializePlayer()并在結(jié)尾處添加以下內(nèi)容:

token = player.observe(\.currentItem) { [weak self] player, _ in
  if player.items().count == 1 {
    self?.addAllVideosToPlayer()
  }
}

在這里,您每次注冊playercurrentItem屬性時都會注冊一個塊。 當(dāng)前視頻更改時,您需要檢查播放器是否已移至最終視頻。 如果有,那么是時候?qū)⑺幸曨l剪輯添加回隊列。

下面,構(gòu)建并運行以查看無限期循環(huán)的clips。

4. Playing Video Efficiently - 有效播放視頻

在繼續(xù)之前需要注意的一點是,播放視頻是一項資源密集型任務(wù)。 事實上,即使您開始觀看全屏視頻,您的應(yīng)用也會繼續(xù)播放這些片段。

要修復(fù)它,首先將以下兩個方法添加到VideoLooperView.swift的底部。

func pause() {
  player.pause()
}

func play() {
  player.play()
}

如您所見,您正在公開play()pause()方法,并將消息傳遞給此視圖的播放器。

現(xiàn)在,轉(zhuǎn)到VideoFeedViewController.swift并找到viewWillDisappear(_ :)。 在那里,添加以下調(diào)用以暫停視頻循環(huán)器。

videoPreviewLooper.pause()

然后,轉(zhuǎn)到viewWillAppear(_ :)并添加匹配的調(diào)用以在用戶返回時恢復(fù)播放。

videoPreviewLooper.play()

構(gòu)建并運行,并轉(zhuǎn)到全屏視頻。 當(dāng)您返回Feed時,預(yù)覽將從中斷處繼續(xù)。

5. Playing with Player Controls - 播放器控制

接下來,是時候添加一些控制了。 你的任務(wù)是:

  • 1) 單擊發(fā)生時取消靜音視頻。
  • 2) 當(dāng)雙擊發(fā)生時,在1x和2x速度之間切換。

您將從實現(xiàn)這些事情所需的實際方法開始。 首先,返回VideoLooperView.swift并找到添加播放和暫停方法的位置。

添加以下單擊分頁處理程序,將在0.01.0之間切換卷。

@objc func wasTapped() {
  player.volume = player.volume == 1.0 ? 0.0 : 1.0
}

接下來,添加雙擊處理程序。

@objc func wasDoubleTapped() {
  player.rate = player.rate == 1.0 ? 2.0 : 1.0
}

這一點類似于它在1.0和2.0之間切換播放速率。

接下來,添加以下方法定義,以創(chuàng)建兩個手勢識別器。

func addGestureRecognizers() {
  // 1
  let tap = UITapGestureRecognizer(target: self, action: #selector(VideoLooperView.wasTapped))
  let doubleTap = UITapGestureRecognizer(target: self,
                                         action: #selector(VideoLooperView.wasDoubleTapped))
  doubleTap.numberOfTapsRequired = 2
  
  // 2
  tap.require(toFail: doubleTap)

  // 3
  addGestureRecognizer(tap)
  addGestureRecognizer(doubleTap)
}

下面一步步的看:

  • 1) 首先,您創(chuàng)建兩個手勢識別器并告訴他們調(diào)用哪些方法。 你還告訴雙擊它需要兩次點擊。
  • 2) 接下來,您進(jìn)行單擊等待以確保不會發(fā)生雙擊。 如果您不這樣做,將始終立即調(diào)用單擊方法。
  • 3) 然后,將手勢識別器添加到視頻視圖中。

要完成任務(wù),請轉(zhuǎn)到init(clip :)并在底部添加以下方法調(diào)用。

addGestureRecognizers()

再次構(gòu)建和運行,您將能夠點擊并雙擊以播放剪輯的速度和音量。 這表明添加自定義控制以便與自定義視頻視圖進(jìn)行交互是多么容易。

現(xiàn)在,您可以將音量調(diào)高了!

6. Trying Not to Steal the Show - 不要讓別的因素影響你的展示

最后需要注意的是,如果您打算創(chuàng)建一個包含視頻的應(yīng)用,那么考慮一下您的應(yīng)用對用戶的影響非常重要。

是的,我知道,這聽起來非常明顯。 但是你有多少次使用的應(yīng)用程序可以啟動靜音視頻但關(guān)閉你的音樂?

打開一些音樂然后運行應(yīng)用程序。 當(dāng)你這樣做時,你會注意到你的音樂已關(guān)閉,即使視頻循環(huán)沒有發(fā)出任何噪音!

我的觀點是,您應(yīng)該允許您的用戶關(guān)閉他們自己的音樂,而不是做出如此大膽的假設(shè)。 幸運的是,通過調(diào)整AVAudioSession的設(shè)置來解決這個問題并不是很難。

轉(zhuǎn)到AppDelegate.swift并將以下導(dǎo)入添加到文件的頂部。

import AVFoundation

接下來,在application(_:didFinishLaunchingWithOptions:)的頂部,添加以下行。

try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient,
                                                 mode: AVAudioSessionModeMoviePlayback,
                                                 options: [.mixWithOthers])

在這里,您告訴共享AVAudioSession您希望音頻屬于AVAudioSessionCategoryAmbient類別。 默認(rèn)值為AVAudioSessionCategorySoloAmbient,它就這樣說明了關(guān)閉其他應(yīng)用程序的音頻。

您還指定您的應(yīng)用程序正在使用音頻進(jìn)行“電影播放”,并且您可以將聲音與來自其他來源的聲音混合使用。

對于最終構(gòu)建和運行,請重新啟動音樂并再次啟動應(yīng)用程序。

祝大家國慶節(jié)快樂?。?!

后記

本篇主要講述了一個簡單的視頻流預(yù)覽和播放示例,感興趣的給個贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容