ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
よりレスポンシブなメディアAppの実現
AVFoundationを使用して、ユーザをローディングアニメーションではなく、メディアAppのコンテンツに集中させる方法をご確認ください。リッチなオーディオビジュアルコンポジションの作成、オーディオビジュアルアセットのロード、メディアサムネイルの準備を行いながら、Appにおける応答性と流動性のあるインターフェイスをサポートする方法を紹介します。さらに、I/Oプロセスの実行と並行しながら、Appのメインスレッドでこれらのタスクを実行する方法をはじめ、カスタムストレージからデータをロードする際に最高レベルの再生パフォーマンスを得る方法などについて解説します。 このセッションを最大限に活用するには、WWDC21の「AVFoundationの最新情報」をまずご確認ください。
リソース
関連ビデオ
WWDC21
-
ダウンロード
(音楽) Jeremyです AVFoundationを活用して よりレスポンシブなメデイアAppを どのように実現するのか お伝えします Appでメディアアセットを 使用する際に ただ再生する以上のことを したくなるかと思います サムネイルを表示したり メデイアを統合して 新たな構成にしたり そのアセットについて情報を 取得したりといった具合です そのためには データの読み込みが必須ですが 動画のような大きなファイルは 時間がかかりそうです メインスレッドで同期的に 処理を行った場合 Appのレイテンシに 問題が生じやすくなります Appをレスポンシブに保つには 非同期的にデータを読み込み 完了時に自身の UI を アップデートすることです AVFoundation のツールなら これは簡単です 今日のお話は次の通りです まずは AVFoundation の 新しい非同期 API です 次に 去年お話しした async load(_:) メソッドを使った アセットの検査についての アップデートについてです そしてキャッシュされた ローカルメディアの最適化を AVAssetResourceLoader を 使って行う方法です ではまず 新しい非同期 API についてです AVAssetImageGenerator で 動画から静止画を抜き出すのは サムネイルを作るのに最適です ですが画像生成は 瞬時ではできません
画像ジェネレータは 動画ファイルからフレームデータを 取り込む必要があります リモートサーバや ネットに保存されたメディアは 取り込みに時間がかかります だからこそ画像生成の方法が 重要なポイントなのです 同期的にデータを取り込む 方法の場合 例えばメインスレッドの copyCGImage ですが 動画の取り込みを待つ間に UI がフリーズしやすくなります 今年は image(at: time) の 非同期メソッドを追加し 画像ジェネレータの データ取り込み時に async/await を使って 呼び出したスレッドを解放します 画像ジェネレータは アセットの画像と実時間の タプルを返してきます 幾つかの理由で要求時間により 実時間に差が出ますが 画像が欲しいだけなら .image プロパティで 直接アクセス可能です 圧縮された動画のフレームは ロードし易いものもあります iFrame は独立して デコード可能ですが 他のフレームのデコードには 周りのフレームが必要です 要求した時間には 画像ジェネレータがデフォルトで 最も近くの iFrame を使って 画像を生成します トレランスをゼロにして 要求時間丁度のフレームを 掴みたいという気持ちは 分かりますが 恐らくそのフレームが 依存する周りのフレームも 画像ジェネレータは取り込む 必要があるでしょう むしろトレランスを 広く設定しても 求めている結果が出ます トレランスが広いと フレーム選択の余地が広がり データ取り込みが最小化できます 取り込むフレームが少なければ 画像を返す速度が上がります
アセットで何度か 一連の画像を得るために generateCGImagesAsynchronously (forTimes:) がありました ですが Swift ではその使用において 注意する傾向があります 今年 新たに加えたのが images(for: times) メソッドです CMTimes の配列を受け取るので 最初に NSValues にマップする 必要はありません また Async シーケンスを使って 結果を提供します シーケンスに for-in ループを使って アイテムを繰り返せます 一斉に準備できない アイテムのシーケンスは async シーケンスで繰り返しの度に 次のエレメントを待てます 上手く生成できた それぞれの画像は 元々の要求時間と実時間が 画像と一緒に 結果に含まれています 失敗した場合 結果に その理由を示すエラーが出ます
画像にしか興味がないなら その値に直接アクセスできる プロパティが結果に出て 生成が失敗した場合は エラーを示します async シーケンスの詳細は “AsyncSequenceについて”の セッションでどうぞ 画像生成のようなタスクでは データローディングへの関与を 理解しやすいですが AVFoundation の 同期エリアの中には 問題点として気づきにくい ものもあります AVMutableComposition が その1つです アセットのための時間枠の挿入は コンポジションにおいて アセット参照を追加するために そのトラック情報が必要です 同期的にトラックを 検査するので そのトラックがまだ 取り込まれていなければ 同期的に取り込まれて 新しいトラックを作成します
これまでの解決法は コンポジションへの挿入前に アセットのトラック取り込みを 待つことでした 今年は insertTimeRange の 非同期版を導入し 必要に応じてトラックを 非同期的に取り込みます
ビデオや可変ビデオの コンポジションには アセットプロパティの取り込みも 要求する追加のメソッドがあります 今年から“propertiesOf asset” コンストラクタも isValid(for:timeRange:) メソッドも 非同期版があります アセットのトラックや持続時間を 非同期的に取り込むので プリロードする必要もありません 必要なプロパティを 非同期的に取り込むことで アセットとのインタラクションが 容易になりました 自分でアセットのプロパティを 取り込む必要がある場合には 非同期のアセット検査について 振り返りましょう アセットのプロパティの 検査方法は2つあります AVFoundation 導入時には プロパティ検査の最善策は 非同期キー値の取り込みでした 去年 async load(_:) が 導入されました 取り込むプロパティの識別に 型安全なキーを使いますが 以前はハードコードした文字列を キーに使っていました 文字列キーのタイポは 気づきにくいものです キーでのタイポは非同期の ロードを妨害し 後にプロパティが使われる時 そのロードをブロックします
取り込むキーの新たな プロパティを追加し忘れたり 完全に非同期取り込みするのを 忘れるのもありがちです このような理由で今年は 非同期キー値のローディングや Swiftの同期プロパティは非推奨となり 非同期ローディングを勧めます タイポを防ぐため 型安全な識別子を使っています プロパティ値を 要求に応じて直接戻し 取り込まれていないプロパティへの アクセスを防ぎます これら全てがコンパイル時間に チェックされるので IOバウンドのパフォーマンスの 新たな問題を防げます プロパティの非同期検査には 非同期取り込みのみを奨励し AVAsset や AVAssetTrack AVMetadataItem や そのサブクラスに当てはまります ですが このいくつかは 未だに同期プロパティ検査を 提供しますが それはそのプロパティのデータが 既にメモリで利用可能だからです 別の可変コンポジションで 理由を見てみましょう
2つの既存ビデオトラックを つなぎ合わせるために 可変コンポジションを使います まずは空のコンポジションを 作成して 空のビデオトラックを追加します 最初のビデオトラックの一部を コンポジショントラックに 同期的に挿入します 裏側では何のデータも 取り込んでいません 代わりに 欲しいトラックのために トラックセグメントを追加します
そして同様に2つ目の トラックにも追加します
コンポジション自体は ファイルではなく メモリ内構造にあるため 最初に取り込む必要もなく 安全に同期的に プロパティ検査ができます このように これらのクラスは 同期的プロパティ検査は 可能なままですが 非同期検査には全クラスで 非同期ロードを使用します
AVFoundation での全ての 新たな非同期メソッドで メディアデータの取り込み中 容易にブロックを防げます 初めてAppに並行性を 導入するのは困難です 画面のセッションをご覧になって 並行性やAppへの非同期 取り込みを始めてみてください 最後は アセット用の カスタムデータローディングの 最適化についてです まず AVAsset のデフォルトの データロード方法です URL で AVAsset を作成する時 メディアはネットワーク上か 自分のデバイスに保存できます ネットワーク上の場合 AVAsset は一定量のデータを 動的にキャッシュして 再生を円滑にします デバイス内なら必要性に応じて キャッシュのバイパスや データローディングを行います 場合によっては メディアへの直接ポインタを AVAsset に付与できない 事があります 恐らく保存されているのは mp4 の生バイトなのでしょう このような場合に使うのが AVAssetResourceLoader です 特別に取り込んだメディアから 任意のバイトを 要求する方法をアセットに 提供してくれます 既にアセットはデータの 読み込みを操作していないため 個々のチャンクのロードにかかる 時間が予測できません アクセスにはネットワーク コミュニケーションが要ると予想し データのキャッシュを待ってから 再生の準備が整います 今年は メディアが ローカルにあれば entireLengthAvailableOnDemand を リソースローダーで 有効にできるのです このフラグを立てれば 要求に応じてデータを受け取れ キャッシュを スキップできるわけです
ローカルメディアには entire LengthAvailableOnDemand で プレイバック中のメモリ使用量を 軽減できますが 余分なデータのキャッシュが 不要だからです 再生開始にかかる 時間も軽減できて キャッシュが満タンになるのを 待たずに済むからです このフラッグの有効化には 注意が必要です ローディングに何らかの ネットワーク操作が必要な場合 ネットワークファイル保存を 含みますが 再生は信頼性を 欠くようです
リソースローダーの 新たな拡張ですね まとめに 次のステップについてです
メディアをバックグラウンドで 取り込む間 async/await を使って Appをレスポンシブに保ちましょう 画像ジェネレータのトレランスを 広げて速度を上げましょう ローカルにあるメディアに リソースローダーを使う場合は 要求に応じて全体を得られるようにし パフォーマンスを上げましょう 本日は以上です ご清聴ありがとうございました
-
-
1:41 - Generate a thumbnail
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) }
-
2:56 - Generate a series of thumbnails
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) } } }
-
3:49 - Generate a series of thumbnails
func timelineThumbnails(for times: [CMTime]) async { for await result in generator.images(for: times) { updateThumbnail(for: result.requestedTime, with: (try? result.image) ?? placeholder) } }
-
4:40 - AVMutableComposition
let composition = AVMutableComposition() try await composition.insertTimeRange(timeRange, of: asset, at: startTime)
-
4:57 - AVVideoComposition
let videoComposition = try await AVVideoComposition .videoComposition(withPropertiesOf: asset) try await videoComposition.isValid(for: asset, timeRange: range, validationDelegate: delegate)
-
5:33 - Asset inspection
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)
-
7:06 - Synchronously insert track segments into a composition
// videoTrack1: AVAssetTrack, videoTrack2: AVAssetTrack // Create a composition and add an empty track let composition = AVMutableComposition() guard let compositionTrack = composition .addMutableTrack(withMediaType: .video, preferredTrackID: 1) else { return } // Append the first 5 seconds of track 1 try compositionTrack .insertTimeRange(firstFiveSeconds, of: videoTrack1, at: .zero) // Append the first 5 seconds of track 2 try compositionTrack .insertTimeRange(firstFiveSeconds, of: videoTrack2, at: fiveSeconds) myFunction(thatUses: composition.duration, and: composition.tracks)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。