如何建立一個媒體回應更即時的 App

AppDev Ooops
AppDev Ooops
Published in
8 min readJan 10, 2023

這篇文章想跟你分享:

在 App 開發的過程中,許多畫面中都需要透過網路取得媒體資源,此時若使用者開啟 App 而媒體資源尚未就緒的話,就會造成畫面呈現不如預期。這篇文章整理了 iOS App 開發時,可以使用的一系列 AVFoundation 新引入的異步處理 API 及用法。

為了節省使用者的等待時間,在開發 iOS App 可以注意以下幾點,減少媒體回應的時間:

  1. 使用 WWDC22 發表的一系列新 API,異步加載網路內容
  2. 設定影片縮圖的寬限範圍
  3. 避免不必要的 Cache,減少載入資源的時間

【 參考資料:https://developer.apple.com/videos/play/wwdc2022/110379/

使用 AVAssetImageGenerator 產生影片縮圖

【 API 】

func image(at time: CMTime) async throws -> (image: CGImage, actualTime: CMTime)

【 使用方法 】

上圖圈起來的 .image(at: time) 會回傳 CGImageCMTime 的 Tuple,分別為影片縮圖,以及產生縮圖的時間點。

設定取得縮圖的時間寬限值

一般影片中有些時間點的影格需要由前後的影格才能計算出來,因此需要花費額外計算時間。如下圖,若想取得黃色標示 Time 時間點的影格 P,則需要額外計算。此時可透過設定時間寬限值 requestedTimeTolerance,將附近的 I 影格當作縮圖,減少縮圖取得時間

可透過 AVAssetImageGenerator 的屬性:.requestedTimeToleranceBefore.requestedTimeToleranceAfter ,實作時間的寬限值。

當寬限值設為 0 時:

需要額外花費計算時間取得精確 Time 的影格

當寬限值非 0 時:

可在寬限值範圍內快速決定 Time 的影格

使用 AVAssetImageGenerator 產生多個縮圖

【 API 】

func images(for times: [CMTime]) -> AVAssetImageGenerator.Images

【 使用方法 】

新 API 使用方法

上圖新的方法不僅提供異步處理,更在寫法上進行大量改良。

在過去需要產生多個縮圖需如下圖:

舊 API 使用方法

由新、舊 API 的使用方法可以發現以下差異:

  • 可直接放CMTimes參數,不需再轉成NSValue
  • result 是 Async Sequence。
  • 成功時的回傳值,除了圖片,還有該圖片位於影片的確切時間點。
  • 失敗時會回傳詳細 Error Message。
  • 如果只需要縮圖,不需要其他資訊的話,可以直接使用result.image取得:
若只需要縮圖,可使用 result.image 直接取得

使用 insertTimeRange 異步插入 AVAsset

【 API 】

func insertTimeRange(
_ timeRange: CMTimeRange,
of asset: AVAsset,
at startTime: CMTime,
) async throws

【 使用方法 】

新 API 只需一行程式碼,即可進行加載以及插入 AVAsset

過去沒有新 API 時,要插入 AVMutableComposition 前,需要先加載原本的AVAsset,才能做成一個新的composition

舊 API 進行加載以及插入只能分開寫

異步製作 AVVideoComposition

【 API 】

let videoComposition = try await AVVideoComposition.videoComposition(withPropertiesOf: asset)

【 使用方法 】

新 API 可以用異步處理,僅用一行程式碼製作 AVVideoComposition

過去沒有新 API 時,會需要先把 AVAsset加載完畢,才能製作 AVVideoComposition

舊 API 需等待 AVAsset 加載完畢才能製作 AVVideoComposition

監看 AVAsset 資訊的異步方法

【 API 】

let (duration, tracks) = try await asset.load(.duration, .track)

【 使用方法】

新、舊 API 的比較,其中舊 API 已經 Deprecated

可以看到新 API 的改良:

  • 不再用字串當 key,避免打字錯誤而找不到值的情形。
  • 只回傳已經加載完的屬性,避免取得尚未加載完畢的屬性。

並非所有狀況都需要使用異步的方法

如果 AVAsset 的資訊已經存在記憶體中,便不需要進行異步處理取得。

假設目標要做一個新的 AVMutableComposition ,該物件的前半段是VideoTrack1,後半段是 VideoTrack2。因為VideoTrack1VideoTrack2都已存在記憶體,新的 AVMutableComposition 都指向它們,因此可以用同步的方法在記憶體中查找資訊。

若 AVAsset 相關資訊已存在於記憶體,則可使用同步取代異步處理

避免不必要的 Cache

【 API 】

convenience init(url URL: URL)

【 使用方法 】

當使用 URL 製作 AVAsset 時,如果URL為網路位置的話,會需要在 App 內建立 Cache,當儲存到達一定容量時才會播放。相反的,如果 URL 在本地端就儲存 Cache。然而有些情況,系統無法自動辨別是否為網路位置。例如:存取專案中的 MP4 原始檔時,需要使用 AVAssetResourceLoader,又因為它只有加載沒有讀取的功能,所以會像存取網路資源一樣,當 Cache 儲存夠了之後才播放。

現在有新的 .entireLengthAvailableOnDemand可以設置,當檔案位於本地端時,可透過此 Flag 控制避免載入不必要的 Cache。此功能具備以下特點:

  • 播放時節省記憶體,因為不需要額外載入快取
  • 節省播放前的等待時間,因為不需要等待快取存滿才進行下一步
  • 有任何網路相關的執行的話,都可能影響播放行為,因此當檔案完全在本地端才可以使用此 Flag
.entireLengthAvailableOnDemand 跳過儲存快取

小結

透過新的 API 可以做到異步加載網路內容、設影片縮圖取得時間寬限範圍、避免不必要快取,一切都是為了要節省使用者等待的時間,開發出媒體回應更即時的 App。如果對更多細節有興趣的話,歡迎到下方連結研究更多,也歡迎有任何問題可以留言或者寄 Email 給我們!我們下篇文章見!

資料來源:

Written By

--

--