
-
アプリでのシネマティックビデオの撮影
Cinematic Video APIを使って、シネマスタイルのビデオをアプリで簡単に撮影する方法を紹介します。シネマティック撮影セッションの設定方法や、ビデオ撮影UIの構築の基本について説明します。被写界深度のエフェクトを適用してトラッキングとRack Focus(ピン送り)を実現するなど、高度なシネマティック機能についても解説します。
関連する章
- 0:00 - イントロダクション
- 0:33 - シネマティックビデオ
- 3:44 - 優れたシネマティック撮影体験の構築
リソース
関連ビデオ
WWDC25
WWDC24
WWDC23
-
このビデオを検索
こんにちは Royです カメラソフトウェアチームの エンジニアです 今日は みなさんのアプリで Cinematic Video APIを使って簡単に プロレベルのシネマスタイルビデオを 撮影する方法についてお話しします
iPhone 13と13 Proでは シネマティックモードが導入されました
直感的なユーザーインターフェイスと パワフルなアルゴリズムで iPhoneはビデオ撮影における 最強のツールに変身しました
このセッションでは シネマティックビデオが 魅力的な理由を探ります
そして いくつかのコードを見て 優れたシネマティックキャプチャ体験を 構築する方法について学びます
シネマティックビデオとは何でしょうか
その本質は よく知られた ストーリーテリング用のツールです 矩形フォーカスや トラッキングフォーカスなどです
被写界深度が浅い場合 監督は シーンの主な被写体に 見る人の注意を引きつけ 物語のインパクトを高めます 映画でよくあるように 被写体が動くときは トラッキングフォーカスで 鮮明に捉えておくことができます
効果は抜群ですが 現実世界では このようなフォーカステクニックには 多くの専門知識が必要です
そのため 映画のセットには フォーカスプラーがいて このパワフルながら難しい撮影を 主な仕事にしています
シネマティックビデオは インテリジェントに フォーカス決定を行うことで これを劇的に簡素化します
例えば 被写体がフレームインすると アルゴリズムは自動的にフォーカスを合わせ トラッキングを開始します 被写体が目をそらすと フォーカスは自動的に 別のポイントに移動し 必要に応じて被写体に戻ります
今年 この素晴らしい機能が Cinematic Video APIとして 利用可能になったことで みなさんのアプリで 素晴らしい シネマスタイルのビデオを簡単に撮影できます 新しいAPIを使って シネマティックビデオの 優れたキャプチャ体験を構築する 方法を見ていきましょう ビデオアプリの一般的な キャプチャセッションから見ていきましょう
まず ムービーを撮影する デバイスを選択します
それをデバイス入力に追加します
ユースケースに応じて 複数の出力を セッションに追加できます
この出力がキャプチャセッションに 追加されると 接続オブジェクトが作成されます
単純な設定ではありません でも シネマティックビデオキャプチャは とても簡単に有効化できます
iOS 26では AVCaptureDeviceInputクラスに 新しいプロパティ isCinematicVideoCaptureEnabledが追加されています これをtrueに設定すると キャプチャセッション全体が シネマティックビデオを出力する設定になり シネマティック処理が それぞれの出力に 適用されるようになります
ムービーファイル出力で 制作されたムービーファイルは シネマティックになります
視差データ メタデータと 非破壊編集用の オリジナルの ビデオが含まれています レンダリングされたボケで再生したり ボケ効果を編集したりするには 2023年に導入されたCinematic フレームワークを使用できます
このフレームワークについて詳しくは WWDC23セッション 「Support Cinematic mode videos in your app」をご覧ください ビデオデータ出力は 浅い被写界深度エフェクトが ベイクされたフレームを生成します これは フレームに直接アクセスする 必要があるとき便利です
例えば リモートデバイスに 送信する場合です
同じように プレビューレイヤには ボケがリアルタイムでレンダリングされます この方法でファインダーを 簡単に構築できます
このアーキテクチャの概要を 念頭に置きつつ 以下の領域のコードを いくつか見ていきましょう
シネマティックキャプチャに必要な すべてのコンポーネントで AVCaptureSessionを構成します
次に SwiftUIを使ってビデオキャプチャ用の インターフェイスを構築します
顔検出などのメタデータを 取得したり 画面に描画したりする 方法について説明します
さまざまな方法で フォーカス駆動を手動で行って シネマティックビデオの パワーをフル活用します
最後は さらに洗練された アプリにするための いくつかの 高度な機能で締めくくります
キャプチャセッションから始めましょう
まず ムービーをキャプチャする ビデオデバイスを見つけます デバイスを見つけるには AVCaptureDevice.DiscoverySession オブジェクトを作成します
シネマティックビデオは 背面のデュアル 広角カメラと 前面のTrueDepthカメラの 両方でサポートされています
この場合は デバイスタイプの配列で .builtInDualWideCameraを指定します ムービーを撮影しているので mediaTypeは.videoを使用します
デバイスの背面にあるカメラを リクエストします
今回は単一のデバイスタイプを リクエストしているため 検出セッションのデバイス配列の 最初の要素のみを取得できます
シネマティックビデオキャプチャを 有効にするには この機能をサポートする形式が必要です
このような形式を見つけるには すべてのデバイスの形式を 反復処理して isCinematicVideoCaptureSupported プロパティがtrueを返すものを使用します
次の形式がサポートされています
デュアル広角カメラとTrueDepthカメラ どちらも 毎秒30フレームで1080pと4Kの 両方をサポートしています
SDRやEDRコンテンツの録画に 興味があれば 420ビデオレンジまたはフルレンジの いずれかを使用できます
10ビットHDRビデオコンテンツがよければ 代わりにx420を使用します
無声映画を作っているわけではないので 音声もほしいですね 同じDiscoverySession APIを使用して マイクを検索します
デバイスが手元にある状態で それぞれに入力を作成します 次にこれらの入力を キャプチャセッションに追加します
この時点で ビデオ入力で シネマティック ビデオキャプチャをオンにできます
シネマティック体験を向上させるために 空間オーディオをキャプチャできます 一次アンビソニックスを multichannelAudioModeとして 設定するだけです
空間オーディオについて詳しくは 今年のセッション「Enhance your app's audio content creation capabilities」をご覧ください 出力に移りましょう AVCaptureMovieFileOutputオブジェクトを 作成し セッションに追加します
手だと三脚ほど安定性がないので 手ぶれ補正を有効にすることをお勧めします
そのためには まず movieFileOutputの ビデオ接続を見つけて そのpreferredVideoStabilizationModeを 設定します この場合は cinematicExtendedEnhancedを使用します
最後に プレビューレイヤを キャプチャセッションに関連付けます
キャプチャセッションは ひとまず完了です ユーザーインターフェイスに 移りましょう
AVCaptureVideoPreviewLayerは CALayerのサブクラスです SwiftUIの一部ではありません やり取りするためには UIViewRepresentable プロトコルに準拠する構造体に プレビューレイヤを ラップする必要があります
この構造体内に UIViewサブクラスの CameraPreviewUIViewを作成します
そのlayerClassプロパティを オーバーライドして previewLayerをビューの バッキングレイヤにします
previewLayerプロパティを作成して AVCaptureVideoPreviewLayerタイプとして 簡単にアクセスできるようにします
次に プレビュービューを ZStackに配置できます
ここでカメラコントロールなどの 他のUI要素と 簡単に構成できます
冒頭で述べたように 浅い被写界深度は ストーリーテリングのための重要なツールです デバイス入力のsimulatedAperture プロパティを変更して ボケ効果の全体的な強さを調整できます
右側に表示されているように スライダでプロパティを動かして ぼかしの全体的な強さを変更します
この値は業界標準の絞り値で表されます
焦点距離と 絞りの直径の比です
入れ替えると 絞りは 焦点距離をF値で 割ったものになります
そのため F値が小さいほど絞りが大きくなり ボケが強くなります
形式から 絞り値の最小値 最大値 デフォルトのシミュレーション値を 見つけられます
これを使用して スライダなどの適切なUI要素を 設定します
次に ユーザーがシネマティックビデオを 手動で操作する仕組みを いくつか構築しましょう
ユーザーが手動でフォーカス駆動するには 顔などのフォーカス候補に視覚的 インジケータを表示する必要があります そのためには 検出メタデータが必要です
AVCaptureMetadataOutputを使用して これらの検出を取得し 画面に境界を描画して ユーザーが操作できるようにします シネマティックビデオアルゴリズムが 最適に機能するには 特定の metadataObjectTypesが必要です
これは 新しいプロパティ requiredMetadataObjectTypesForCinematicVideoCaptureで 伝達されます シネマティックビデオが有効になったとき 指定されたmetadataObjectTypesが リストと異なる場合は例外が発生します
最後に デリゲートを提供して メタデータと デリゲートが呼び出される キューを受け取る必要があります
メタデータオブジェクトはメタデータ出力 デリゲートコールバックで受け取ります
このメタデータをSwiftUIの ビューレイヤに簡単に伝達するために Observableクラスを使用します
プロパティを更新すると Observingビューが 自動的に更新されます
ビューレイヤでは Observableオブジェクトが更新されるたびに ビューが自動的に再描画されます
各metadataObjectに対して 矩形を描画します
矩形を作成する際には メタデータの境界をプレビューレイヤの 座標空間に変換することが重要です
layerRectConverted fromMetadataOutputRectメソッドを使用します
なお 位置メソッドのXとYは AVFoundationで使用される左上隅ではなく ビューの中心を指していることに 注意してください それで 矩形のmidXとmidYを使用して 適切に調整する必要があります
メタデータの矩形が画面に描画されると その矩形で 手動で フォーカス駆動できます
Cinematic Video APIには フォーカスを手動設定する方法が3つあります 1つずつ見ていきましょう
setCinematicVideoTrackingFocus detectedObjectID focusModeメソッドを使用して メタデータ出力から取得する AVMetadataオブジェクトで利用可能な detectedObjectIDが識別した 特定の被写体に ラックフォーカスできます
focusModeはシネマティックビデオの トラッキング動作を設定します
CinematicVideoFocusMode列挙型には 次の3つのケースがあります none strong weakです
strongはシネマティックビデオに 被写体を トラッキングし続けるよう指示します 自動的に選ばれそうな ほかの フォーカス候補があったとしてもです
この場合 猫はフレーム内で より目立っていますが 黄色の実線の矩形が示すように 強いフォーカスは 後ろの被写体に固定されたままです
一方 弱いフォーカスはアルゴリズムに フォーカスコントロールを任せます アルゴリズムは適切だと判断すると 自動的にラックフォーカスします
この場合 猫が振り向くと より重要だとみなされ 弱いフォーカスは自動的に猫に移ります それが破線の矩形で表示されています
noneの場合は 現時点で メタデータオブジェクトに フォーカスが合っているかどうかを 判断する時にのみ役立ちます フォーカスを設定する際には 使用しないでください
2番目のフォーカスメソッドは 別の最初のパラメータを受け取ります 検出されたオブジェクトIDの代わりに ビュー内のポイントを取得します
指定されたポイントで 興味深い オブジェクトを探すように指示します 見つかると タイプが際立つ オブジェクトで新しい メタデータオブジェクトを作成します それで 画面上でその周りに 矩形を描画できます
3番目のフォーカスメソッドは setCinematicVideoFixedFocusです ポイントとフォーカスモードを取得します フォーカスを 深度などのシグナルを使って 内部的に計算される固定距離に設定します このメソッドは 強いフォーカスモードと 組み合わせることで シーン内の特定の平面に フォーカスを効果的に固定します フォアグラウンドでの他の動きも無視します
それぞれのユースケースで意味のある フォーカスロジックを どんなアプリにも実装できます このアプリでは次のことを行います
フォーカスされていない 検出矩形をタップすると 弱いフォーカスでラックフォーカスします これで 被写体の間でフォーカスを あちこち切り替えて フォーカスイン アウトできます
すでに弱いフォーカスが合っている メタデータオブジェクトをタップすると 強いフォーカスに変わります
黄色の実線の矩形で表示されます
既存の検出がないポイントでタップすると シネマティックビデオは 目立つオブジェクトを見つけて 弱いフォーカスを合わせます
長押しで 強い固定フォーカスを設定します このロジックは 次の方法で コードに実装できます まず 2つのジェスチャを 作成する必要があります
通常のタップジェスチャは SpatialTapGestureで簡単に行えます これは フォーカス設定に必要な タップの位置を提供します
タップすると カメラモデルオブジェクトで focusTapメソッドを呼び出します これは 基盤となるAVCaptureDeviceに アクセスできる場所です
一方 長押しはもう少し複雑です 組み込みのlongPressGestureからは DragGestureを使用して 長押しを シミュレートするためのタップ位置が 提供されないからです
押すと 0.3秒タイマーで開始します
タイマーが作動すると カメラモデルで focusLongPressメソッドを 呼び出します
次に ジェスチャを受け取る 矩形ビューを作成します これはZStackの最後に挿入されて ユーザーのジェスチャ入力が ブロックされないように すべての検出矩形の一番上に配置されます
以前のビデオですでに見たように フォーカスされた矩形を視覚的に 区別することが重要です 弱いフォーカス 強いフォーカス フォーカスなしを区別して ユーザーが正しいアクションを 実行できるようにします
これを行うには AVMetadataObjectを 受け取り フォーカスされた 矩形ビューを返すメソッドを実装します
また メタデータの境界を メタデータ出力の座標空間から プレビューレイヤの座標空間に 変換する必要もあります
線のスタイルと色を違うものに設定することで フォーカスモードごとに 視覚的に異なる矩形を簡単に作成できます
ビューレイヤから渡されたポイントを使って どのフォーカスメソッドを使用するか 決定できます
まず 知りたいのは ユーザーがメタデータの矩形を タップしたかどうかです これはヘルパメソッド findTappedMetadataObjectで行います
ここでは フレームごとにキャッシュする すべてのメタデータを反復処理して 指定されたポイントが境界のどれか1つに 収まるかどうかをチェックします
ここでも ポイントと矩形が 同じ座標空間にあることを確認します
focusTapメソッドに戻って メタデータオブジェクトが見つかり すでに弱いフォーカスが合っている場合 強いフォーカスに変わります
まだフォーカスが合っていない場合 弱いフォーカスを適用します
ユーザーがメタデータの矩形を タップしていない場合 フレームワークに この時点で際立つ オブジェクトを見つけるように指示します
長押しで 指定したポイントに 強い固定フォーカスを設定します
これで シネマティックビデオが キャプチャできる 完全に機能するアプリが完成しました 細かいところに磨きをかけていきましょう
今 ビデオキャプチャのグラフは こんな感じです ムービーをキャプチャして メタデータ プレビューを受け取る入力が 3つあります
録画中の静止画キャプチャを サポートする場合は AVCapturePhotoOutputを セッションに追加するだけです
グラフはすでにシネマティックになるように 構成されているため 写真出力には自動的に シネマティック処理が適用されます
写真出力によって返される画像には ボケ効果が焼き付けられています
最後に シネマティックビデオアルゴリズムが 正しく機能するためには 十分な光量が必要です 暗すぎる部屋やカメラが覆われている場合 その問題をUIでユーザーに通知します
この状態が発生したときに 通知を受け取るためには 新しいプロパティを使用して キー値監視できます AVCaptureDeviceクラスの cinematicVideoCaptureSceneMonitoringStatusesです
現在 シネマティックビデオは 光量不足のステータスのみサポートしています
光量不足が発生した場合 KVOハンドラで UIを適切に更新できます
空集合はすべてが 正常に戻ったことを意味します
今日のセッションでは シネマティック ビデオで プロレベルの魅力的な ムービーをキャプチャする 方法についてまとめました ペットと過ごすような日常の瞬間も キャプチャできます
Cinematic Video APIを使用して 優れたシネマティックキャプチャ体験を 構築する方法についても詳しく説明しました
皆さんがこの機能をどのように アプリで活用して より豊かで シネマティックなコンテンツを提供するのか とても楽しみです
ご視聴ありがとうございました
-
-
4:26 - Select a video device
// Select a video device let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualWideCamera], mediaType: .video, position: .back) guard let camera = deviceDiscoverySession.devices.first else { print("Failed to find the capture device") return }
-
5:07 - Select a format that supports Cinematic Video capture
// Select a format that supports Cinematic Video capture for format in camera.formats { if format.isCinematicVideoCaptureSupported { try! camera.lockForConfiguration() camera.activeFormat = format camera.unlockForConfiguration() break } }
-
5:51 - Select a microphone
// Select a microphone let audioDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes [.microphone], mediaType: .audio, position: .unspecified) guard let microphone = audioDeviceDiscoverySession.devices.first else { print("Failed to find a microphone") return }
-
6:00 - Add devices to input & add inputs to the capture session & enable Cinematic Video capture
// Add devices to inputs let videoInput = try! AVCaptureDeviceInput(device: camera) guard captureSession.canAddInput(videoInput) else { print("Can't add the video input to the session") return } let audioInput = try! AVCaptureDeviceInput(device: microphone) guard captureSession.canAddInput(audioInput) else { print("Can't add the audio input to the session") return } // Add inputs to the capture session captureSession.addInput(videoInput) captureSession.addInput(audioInput) // Enable Cinematic Video capture if (videoInput.isCinematicVideoCaptureSupported) { videoInput.isCinematicVideoCaptureEnabled = true }
-
6:17 - Capture spatial audio
// Configure spatial audio if audioInput.isMultichannelAudioModeSupported(.firstOrderAmbisonics) { audioInput.multichannelAudioMode = .firstOrderAmbisonics }
-
// Add outputs to the session let movieFileOutput = AVCaptureMovieFileOutput() guard captureSession.canAddOutput(movieFileOutput) else { print("Can't add the movie file output to the session") return } captureSession.addOutput(movieFileOutput) // Configure video stabilization if let connection = movieFileOutput.connection(with: .video), connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .cinematicExtendedEnhanced } // Add a preview layer as the view finder let previewLayer = AVCaptureVideoPreviewLayer() previewLayer.session = captureSession
-
7:11 - Display the preview layer with SwiftUI
// Display the preview layer with SwiftUI struct CameraPreviewView: UIViewRepresentable { func makeUIView(context: Context) -> PreviewView { return PreviewView() } class CameraPreviewUIView: UIView { override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } ... } ... }
-
7:54 - Display the preview layer with SwiftUI
// Display the preview layer with SwiftUI @MainActor struct CameraView: View { var body: some View { ZStack { CameraPreviewView() CameraControlsView() } } }
-
8:05 - Adjust bokeh strength with simulated aperture
// Adjust bokeh strength with simulated aperture open class AVCaptureDeviceInput : AVCaptureInput { open var simulatedAperture: Float ... }
-
8:40 - Find min, max, and default simulated aperture
// Adjust bokeh strength with simulated aperture extension AVCaptureDeviceFormat { open var minSimulatedAperture: Float { get } open var maxSimulatedAperture: Float { get } open var defaultSimulatedAperture: Float { get } ... }
-
9:12 - Add a metadata output
// Add a metadata output let metadataOutput = AVCaptureMetadataOutput() guard captureSession.canAddOutput(metadataOutput) else { print("Can't add the metadata output to the session") return } captureSession.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataOutput.requiredMetadataObjectTypesForCinematicVideoCapture metadataOutput.setMetadataObjectsDelegate(self, queue: sessionQueue)
-
9:50 - Update the observed manager object
// Update the observed manager object func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { self.metadataManager.metadataObjects = metadataObjects } // Pass metadata to SwiftUI @Observable class CinematicMetadataManager { var metadataObjects: [AVMetadataObject] = [] }
-
10:12 - Observe changes and update the view
// Observe changes and update the view struct FocusOverlayView : View { var body: some View { ForEach( metadataManager.metadataObjects, id:\.objectID) { metadataObject in rectangle(for: metadataObject) } } }
-
10:18 - Make a rectangle for a metadata
// Make a rectangle for a metadata private func rectangle(for metadata: AVMetadataObjects) -> some View { let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds) return Rectangle() .frame(width:transformedRect.width, height:transformedRect.height) .position( x:transformedRect.midX, y:transformedRect.midY) }
-
10:53 - Focus methods
open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode) open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode) open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
-
10:59 - Focus method 1 & CinematicVideoFocusMode
// Focus methods open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode) public enum CinematicVideoFocusMode : Int, @unchecked Sendable { case none = 0 case strong = 1 case weak = 2 } extension AVMetadataObject { open var cinematicVideoFocusMode: Int32 { get } }
-
12:19 - Focus method no.2
// Focus method no.2 open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
-
12:41 - Focus method no.3
// Focus method no.3 open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
-
13:54 - Create the spatial tap gesture
var body: some View { let spatialTapGesture = SpatialTapGesture() .onEnded { event in Task { await camera.focusTap(at: event.location) } } ... }
-
14:15 - Simulate a long press gesture with a drag gesture
@State private var pressLocation: CGPoint = .zero @State private var isPressing = false private let longPressDuration: TimeInterval = 0.3 var body: some View { ... let longPressGesture = DragGesture(minimumDistance: 0).onChanged { value in if !isPressing { isPressing = true pressLocation = value.location startLoopPressTimer() } }.onEnded { _ in isPressing = false } ... } private func startLoopPressTimer() { DispatchQueue.main.asyncAfter(deadline: .now() + longPressDuration) { if isPressing { Task { await camera.focusLongPress(at: pressLocation) } } } }
-
14:36 - Create a rectangle view to receive gestures.
var body: some View { let spatialTapGesture = ... let longPressGesture = ... ZStack { ForEach( metadataManager.metadataObjects, id:\.objectID) { metadataObject in rectangle(for: metadataObject) } Rectangle() .fill(Color.clear) .contentShape(Rectangle()) .gesture(spatialTapGesture) .gesture(longPressGesture)} } }
-
15:03 - Create the rectangle view
private func rectangle(for metadata: AVMetadataObject) -> some View { let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds) var color: Color var strokeStyle: StrokeStyle switch metadata.focusMode { case .weak: color = .yellow strokeStyle = StrokeStyle(lineWidth: 2, dash: [5,4]) case .strong: color = .yellow strokeStyle = StrokeStyle(lineWidth: 2) case .none: color = .white strokeStyle = StrokeStyle(lineWidth: 2) } return Rectangle() .stroke(color, style: strokeStyle) .contentShape(Rectangle()) .frame(width: transformedRect.width, height: transformedRect.height) .position(x: transformedRect.midX, y: transformedRect.midY) }
-
15:30 - Implement focusTap
func focusTap(at point:CGPoint) { try! camera.lockForConfiguration() if let metadataObject = findTappedMetadataObject(at: point) { if metadataObject.cinematicVideoFocusMode == .weak { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong) } else { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak) } } else { let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak) } camera.unlockForConfiguration() }
-
15:42 - Implement findTappedMetadataObject
private func findTappedMetadataObject(at point: CGPoint) -> AVMetadataObject? { var metadataObjectToReturn: AVMetadataObject? for metadataObject in metadataObjectsArray { let layerRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadataObject.bounds) if layerRect.contains(point) { metadataObjectToReturn = metadataObject break } } return metadataObjectToReturn }
-
16:01 - focusTap implementation continued
func focusTap(at point:CGPoint) { try! camera.lockForConfiguration() if let metadataObject = findTappedMetadataObject(at: point) { if metadataObject.cinematicVideoFocusMode == .weak { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong) } else { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak) } } else { let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak) } camera.unlockForConfiguration() }
-
16:23 - Implement focusLongPress
func focusLongPress(at point:CGPoint) { try! camera.lockForConfiguration() let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect:CGRect(origin: point, size: CGSizeZero)).origin camera.setCinematicVideoFixedFocus(at: pointInMetadataOutputSpace, focusMode: .strong) camera.unlockForConfiguration() }
-
17:10 - Introduce cinematicVideoCaptureSceneMonitoringStatuses
extension AVCaptureDevice { open var cinematicVideoCaptureSceneMonitoringStatuses: Set<AVCaptureSceneMonitoringStatus> { get } } extension AVCaptureSceneMonitoringStatus { public static let notEnoughLight: AVCaptureSceneMonitoringStatus }
-
17:42 - KVO handler for cinematicVideoCaptureSceneMonitoringStatuses
private var observation: NSKeyValueObservation? observation = camera.observe(\.cinematicVideoCaptureSceneMonitoringStatuses, options: [.new, .old]) { _, value in if let newStatuses = value.newValue { if newStatuses.contains(.notEnoughLight) { // Update UI (e.g., "Not enough light") } else if newStatuses.count == 0 { // Back to normal. } } }
-