WWDC Quick Look 💓 By SwiftGGTeam
Create a more responsive media app

Create a more responsive media app

元の動画を見る

ハイライト

AVFoundation は、WWDC22 で一連の非同期/待機 API を導入しました。これにより、ビデオ サムネイルの生成、オーディオとビデオの合成、リソース属性の読み取りなどの操作がメイン スレッドをブロックしなくなり、新しいentireLengthAvailableOnDemandローカル カスタム データ ソースの再生パフォーマンスを最適化するためのフラグ。

主要内容

サムネイル生成: ブロッキングから非同期へ

アプリにはビデオプレーヤーがあります。ユーザーが進行状況バーをドラッグしたときに、対応する時点のプレビューを進行状況バーの上に表示したいとします。この要件は非常に一般的ですが、実装方法はユーザー エクスペリエンスに直接影響します。

以前に使用したAVAssetImageGeneratorcopyCGImage(at:actualTime:)サムネイルを生成するメソッドです。このメソッドは、ビデオ フレーム データをメイン スレッドに同期的にロードします。ビデオ ファイルが大きく、リモート サーバーに保存されている場合、メイン スレッドが停止し、UI がフリーズし、ユーザーにはグルグル回転するだけが表示されます。

WWDC22が与えるものAVAssetImageGenerator追加image(at:)非同期メソッド。スレッドは呼び出されるとすぐに解放され、データのロードが完了した後に渡されます。await結果を返します。ユーザー インターフェイスは応答性を維持し、サムネイルの生成はバックグラウンドで行われます。

バッチサムネイル: 非同期シーケンスによりコードがより自然になります

タイムライン全体をプレビューする場合など、複数時点のサムネイルを一度に生成する必要があります。以前に使用したgenerateCGImagesAsynchronously(forTimes:completionHandler:)、置く必要があります[CMTime]にマッピングされる[NSValue]コールバックで結果を処理するため、コードが長くなります。

新しいAPIimages(for:)直接受け入れる[CMTime]、非同期シーケンスを返します。使用for awaitループは結果を 1 つずつ取得し、成功と失敗はループ本体で処理されます。コード構造は、配列を同期的に走査するのと同じくらい直感的です。

オーディオとビデオの合成: 隠された同期トラップ

AVMutableCompositioninsertTimeRange(_:of:at:)複合トラックに参照を挿入しているだけのように見えますが、内部的にはソース リソースのトラック情報を同期的に読み取ります。トラックデータがまだロードされていない場合、メインスレッドはここでブロックされます。

WWDC22がスタートinsertTimeRange必要なプロパティをバックグラウンドで自動的にロードする非同期バージョン。AVVideoCompositionコンストラクターと検証メソッドには非同期バージョンもあるため、リソース プロパティを手動でプリロードする必要がなくなります。

リソースプロパティの読み取り: 文字列キーに代わるタイプセーフな代替手段

昨年導入されたload(_:)このメソッドは、従来の文字列のキーと値の読み込みをタイプセーフなプロパティ識別子に置き換えます。文字列キーのスペルが間違っている場合、エラーは報告されませんが、プロパティが同期的に読み込まれ、スレッドがブロックされます。load(_:)エラーはコンパイル時に検出され、指定された型の属性値が直接返されます。

今年、Apple は文字列ベースの非同期キーと値の読み込みを正式に放棄し、すべての新しいコードでそれを使用することを推奨しました。load(_:)

カスタム データ ソース: ローカル メディアはキャッシュをスキップします

使用AVAssetResourceLoaderカスタム データ ソースをロードする場合、AVAsset はデフォルトでネットワーク通信の前提に基づいてキャッシュを管理します。データがローカルにある場合でも、キャッシュがいっぱいになるまで再生を開始するのを待ちます。

新しいプロパティentireLengthAvailableOnDemandキャッシュ メカニズムをスキップして、データをオンデマンドですぐにフェッチできることを AVAsset に伝えます。これにより、メモリの使用量が削減され、再生の起動時間が短縮されます。ただし、これは純粋にローカル データ ソースにのみ適用され、ネットワーク操作が関与すると再生の信頼性が低くなります。

詳細

単一のサムネイルを非同期的に生成する

01:41

func thumbnail() async throws -> UIImage {
    let generator = AVAssetImageGenerator(asset: asset)
    generator.requestedTimeToleranceBefore = .zero
    generator.requestedTimeToleranceAfter = CMTime(seconds: 3, preferredTimescale: 600)
    let thumbnail = try await generator.image(at: time).image
    return UIImage(cgImage: thumbnail)
}

キーポイント:

  • AVAssetImageGenerator(asset:)ジェネレーターを作成し、ターゲットのビデオリソースにバインドします。
  • requestedTimeToleranceBeforeそしてrequestedTimeToleranceAfter時間許容誤差を設定して、ジェネレーターにより多くのフレーム選択スペースを与えます。
  • generator.image(at: time)を返す新しい非同期メソッドです。(image: CGImage, actualTime: CMTime)タプル
  • .imageCGImage を直接取得し、タプルを解凍する手順を省略します。
  • CMTime(seconds: 3, preferredTimescale: 600)3 秒の許容範囲を示します。タイムスケール 600 はビデオ編集の一般的な精度です。

許容度について: ビデオ圧縮フレームは、iFrame (独立してデコードできるキー フレーム) と依存フレーム (デコードするには隣接するフレームが必要) に分割されます。時間許容誤差をゼロに設定すると、ジェネレーターは正確な時点を検索するようになります。そのフレームは依存フレームである可能性が高く、ジェネレーターは周囲の複数のフレームをロードする必要があります。許容値を緩和すると、ジェネレーターは最も近いキーフレームを選択し、ロードするデータを減らし、より速く戻ることができます。

サムネイルをバッチで生成する

02:56

func timelineThumbnails(for times: [CMTime]) async {
    for await result in generator.images(for: times) {
        switch result {
        case .success(requestedTime: let requestedTime, image: let image, actualTime: _):
            updateThumbnail(for: requestedTime, with: image)
        case .failed(requestedTime: let requestedTime, error: _):
            updateThumbnail(for: requestedTime, with: placeholder)
        }
    }
}

キーポイント:

  • images(for:)受け入れる[CMTime]に変換する必要はありません。[NSValue]
  • を使用して非同期シーケンスを返します。for await結果を 1 つずつ消費する
  • 各結果には以下が含まれますrequestedTime(要求された時点)、image(生成された画像)、actualTime(実際に使用されるフレーム時間)
  • 失敗したら行く.failedブランチ、プレースホルダー マップにフォールバックできます

より簡潔な書き方:

03:49

func timelineThumbnails(for times: [CMTime]) async {
    for await result in generator.images(for: times) {
        updateThumbnail(for: result.requestedTime, with: (try? result.image) ?? placeholder)
    }
}

ここresult.imageエラーがスローされる可能性があります。使用してくださいtry?失敗時に使用される任意の値に変換します?? placeholderすべての詳細を明らかにします。

非同期オーディオおよびビデオ合成

04:40

let composition = AVMutableComposition()
try await composition.insertTimeRange(timeRange, of: asset, at: startTime)

キーポイント:

  • insertTimeRange今はありますawaitバージョン
  • 内部自動非同期ロードasset軌道情報
  • 最初に手動で行う必要はありませんtry await asset.load(.tracks)

ビデオ構成検証は非同期もサポートしています。

04:57

let videoComposition = try await AVVideoComposition.videoComposition(withPropertiesOf: asset)
try await videoComposition.isValid(for: asset, timeRange: range, validationDelegate: delegate)

タイプセーフなリソース属性のロード

05:33

古い書き方 (時代遅れ):

asset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) {
    guard asset.statusOfValue(forKey: "duration", error: &error) == .loaded else { ... }
    guard asset.statusOfValue(forKey: "tracks", error: &error) == .loaded else { ... }
    myFunction(thatUses: asset.duration, and: asset.tracks)
}

新しい書き方:

let (duration, tracks) = try await asset.load(.duration, .tracks)
myFunction(thatUses: duration, and: tracks)

キーポイント:

  • 古い書き方では文字列を使用します"duration""tracks"キーを作成する場合、コンパイラはスペル ミスのエラーを報告しません。また、実行時の同期読み込みにより遅延が発生します。
  • 新しい書き方.duration.tracksタイプセーフですAVAsyncProperty、コンパイル中にスペルミスが見つかる可能性があります
  • 一度load呼び出しでは複数のプロパティをロードし、結果をタプルとして受け取ることができます
  • 適用対象AVAssetAVAssetTrackAVMetadataItemとそのサブカテゴリ

コンポジットトラックの同期挿入 (特殊なケース)

07:06

let composition = AVMutableComposition()
guard let compositionTrack = composition
    .addMutableTrack(withMediaType: .video, preferredTrackID: 1) else { return }

try compositionTrack.insertTimeRange(firstFiveSeconds,
                                     of: videoTrack1, at: .zero)

try compositionTrack.insertTimeRange(firstFiveSeconds,
                                     of: videoTrack2, at: fiveSeconds)

キーポイント:

  • シンセトラックの挿入は同期して実行できます。AVMutableCompositionメモリ内のデータ構造です
  • ファイル I/O は関係せず、メインスレッドのブロックもありません
  • これは、同期されたプロパティ アクセスを保持する数少ないクラスの 1 つです

ローカルのカスタム データ ソースを最適化する

09:09

let resourceLoader = asset.resourceLoader
resourceLoader.setDelegate(myDelegate, queue: myQueue)
// 在 delegate 中设置
// resourceLoader.entireLengthAvailableOnDemand = true

キーポイント:

  • AVAssetResourceLoaderアプリにデータ読み込みロジックをカスタマイズさせます
  • entireLengthAvailableOnDemandデータがすぐに利用可能であることをシステムに伝えます
  • キャッシュメカニズムをスキップし、メモリ使用量を削減し、再生の起動を高速化します。
  • 純粋にローカル データにのみ適用されます。ネットワーク データ ソースをオンにすると、再生が不安定になります。

重要ポイント

1.ビデオエディターのリアルタイムタイムラインプレビュー

  • 手順: 動画編集アプリのタイムライン上で、マウスをホバーしたときに対応する時点のサムネイル プレビューを表示します。
  • 実行する価値がある理由:images(for:)非同期シーケンスは、タイムラインのサムネイルを生成するためのストリーミングに当然適しています。requestedTimeToleranceAfterユーザーがドラッグしたときにすぐに応答できるように、適切な許容値を設定します。
  • 開始方法: を使用しますAVAssetImageGeneratorimages(for:)一定間隔(1秒ごとなど)でサムネイルを生成し、保存する方法NSCache再利用

2.ローカル暗号化ビデオプレーヤー

  • 対処方法: 教育アプリのオフライン コース パッケージなど、カスタム コンテナに保存されている暗号化されたビデオを再生する
  • 実行する価値がある理由:AVAssetResourceLoader + entireLengthAvailableOnDemand復号化と再生がシームレスになり、キャッシュをスキップした後、ローカル データの開始が速くなります。
  • 開始方法: 実装AVAssetResourceLoaderDelegate、存在するresourceLoader(_:shouldWaitForLoadingOfRequestedResource:)データを復号化して返し、設定しますentireLengthAvailableOnDemand = true

3.ビデオスプライシング/ミックスカットツール

  • 対処方法: ユーザーが複数のビデオ クリップを選択し、それらを順番に 1 つのビデオに結合できるようにし、トランジション効果の追加をサポートします。
  • 実行する価値がある理由: 非同期insertTimeRangeそしてAVVideoComposition非同期構築手法により、マルチセグメントビデオ合成が UI に留まることはなくなり、合成結果をリアルタイムでプレビューできるようになります。
  • 開始方法: 作成するAVMutableComposition、非同期を使用するinsertTimeRange各フラグメントを挿入するには、次を使用しますAVVideoComposition.videoComposition(withPropertiesOf:)トランジションを生成するには、次を使用します。AVPlayerItemプレビュー

4.ビデオ情報クイックスキャンツール

  • 手順: アルバムまたはフォルダー内のビデオをバッチスキャンして、再生時間、解像度、ビットレートなどのメタデータを抽出します。
  • 実行する価値がある理由:asset.load(.duration, .tracks)タイプセーフ API によりバッチ処理コードの信頼性が向上し、async/await により同時実行制御が容易になります。
  • 開始方法: を使用しますTaskGroup複数のビデオのプロパティを同時に読み込むには、次を使用します。load(.formatDescriptions, .naturalSize)技術パラメータを取得する

関連セッション

コメント

GitHub Issues · utterances