Capturing Cinematic video
At a glance
| Item | Summary |
|---|---|
| Purpose | Record Cinematic video while visualizing detected focus objects and letting the user change tracking/fixed focus and simulated aperture. |
| App architecture | SwiftUI MVVM-style app: a Camera protocol feeds generic views, CameraModel owns presentation state, and CaptureService owns AVFoundation work on a serial actor executor. |
| Main patterns | Protocol substitution, actor service, output-service composition, observable metadata store, delegate-to-async recording. |
| Project style | Main model/service files plus Capture/, Model/, Views/, Support/, and preview-only model content. |
Project structure
CinematicCaptureSample/
├── CinematicCaptureSampleApp.swift
├── CameraModel.swift
├── CameraView.swift
├── CaptureService.swift
├── Capture/
│ ├── DeviceLookup.swift
│ ├── MovieCapture.swift
│ └── SPCObserver.swift
├── Model/
│ ├── Camera.swift
│ ├── DataTypes.swift
│ └── MediaLibrary.swift
├── Views/
│ ├── CameraPreview.swift
│ ├── FocusOverlayView.swift
│ ├── Controls/SimulatedApertureView.swift
│ ├── Overlays/
│ └── Toolbars/
├── Support/
└── Preview Content/PreviewCameraModel.swift
Structure observations
- The sample follows the same high-level separation as AVCam but removes photo capture and specializes the service/view layer for Cinematic metadata and focus controls.
CaptureService.swiftincludes the capture actor and its small observableCinematicMetadataManager, keeping metadata production beside the AVFoundation delegates.- Views depend on the
Cameraprotocol, while preview content provides a hardware-free conformer.
Overall architecture
flowchart LR
App["CinematicCaptureSampleApp"] --> View["CameraView<Camera>"]
App --> Model["CameraModel"]
View --> Model
Model --> Service["CaptureService actor"]
Model --> Library["MediaLibrary actor"]
Service --> Session["AVCaptureSession"]
Service --> Movie["MovieCapture"]
Service --> Metadata["CinematicMetadataManager"]
Metadata --> Overlay["FocusOverlayView"]
Service --> Preview["AVSampleBufferDisplayLayer"]
Library --> Photos["PhotoKit"]
Reference code
CinematicCaptureSample/CinematicCaptureSampleApp.swift:13 — composition root
@State private var camera = CameraModel()
WindowGroup {
CameraView(camera: camera)
.task {
await camera.start()
}
}Interpretation
The app constructs one long-lived model. The model presents a main-actor interface to SwiftUI, while the capture actor owns session configuration, video/metadata callbacks, and movie capture. A separate actor saves the completed movie. Calling this MVVM is an interpretation supported by the explicit model mediation and source comments.
Ownership and state
classDiagram
CinematicCaptureSampleApp *-- CameraModel : State creates
CameraView --> CameraModel : observes and sends intents
CameraModel *-- AVSampleBufferDisplayLayer : creates
CameraModel *-- CaptureService : creates with preview layer
CameraModel *-- MediaLibrary : creates
CaptureService *-- AVCaptureSession : creates
CaptureService *-- MovieCapture : creates
CaptureService *-- CinematicMetadataManager : creates
FocusOverlayView --> CinematicMetadataManager : observes
Ownership evidence
CinematicCaptureSample/CameraModel.swift:58 — model composition
private let mediaLibrary = MediaLibrary()
private let captureService: CaptureService
init() {
captureService = CaptureService(
previewLayer: preview as! AVSampleBufferDisplayLayer
)
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
| App | CameraModel |
Creates and retains with SwiftUI @State |
Model mutates observable UI state on MainActor. |
CameraModel |
Preview layer, capture service, media library | Creates all three and injects preview into service | Model coordinates; actors own service internals. |
CaptureService |
Session, movie output service, metadata output/cache | Creates and retains capture graph | Capture actor on sessionQueue. |
CaptureService |
CinematicMetadataManager |
Creates and updates its metadata array | Source updates come from capture actor; the property setter is technically module-internal. |
MovieCapture |
Movie output/delegate/timer | Owns recording lifecycle | MovieCapture. |
Class and protocol design
classDiagram
class Camera {
<<protocol>>
}
class OutputService {
<<protocol>>
}
CameraModel ..|> Camera
PreviewCameraModel ..|> Camera
CameraView --> Camera
FocusOverlayView --> Camera
MovieCapture ..|> OutputService
CaptureService --> OutputService
Reference code
CinematicCaptureSample/Model/Camera.swift:13 — view-facing capability
@MainActor
protocol Camera: AnyObject, SendableMetatype {
var status: CameraStatus { get }
var metadataManager: CinematicMetadataManager { get }
var preview: CALayer { get }
func toggleRecording() async
func tapPreview(at point: CGPoint) async
}| Type | Responsibility | Depends on |
|---|---|---|
Camera |
Define view-facing state and Cinematic focus commands | AVFoundation/Core Animation data types |
CameraModel |
Mediate views, capture, save, and observable state | CaptureService, MediaLibrary |
CaptureService |
Configure Cinematic capture, deliver preview/metadata, and control focus | AVFoundation, MovieCapture |
CinematicMetadataManager |
Publish normalized focus metadata to SwiftUI | Observation, CinematicFocusMetadata |
MovieCapture |
Adapt file-output recording and callback completion | AVCaptureMovieFileOutput |
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
| Model status, activity, switching state, thumbnails, errors, aperture limits | private(set) |
Views can observe but cannot assign | Keeps CameraModel as UI-state mutation boundary. |
| Model service/library references | private let |
Views interact only through model commands | Hides collaborator lifecycle and concrete types. |
| Capture session, movie service, inputs, metadata cache, and setup helpers | private |
Capture graph is inaccessible outside CaptureService |
Preserves actor-confined session invariants. |
CGPoint.normalized(for:) extension |
fileprivate |
Usable throughout FocusOverlayView.swift, nowhere else |
A same-file geometry helper supports the view without becoming a module utility. |
| App protocols/types | implicit internal |
Shared in the app module only | The target is not a public framework. |
Reference code
CinematicCaptureSample/Views/FocusOverlayView.swift:110 — same-file-only helper
fileprivate extension CGPoint {
func normalized(for proxy: GeometryProxy) -> CGPoint {
.init(x: x / proxy.size.width, y: y / proxy.size.height)
}
}No public or open declaration appears in the Swift source.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| UI state and user-intent translation | CameraModel |
Main-actor observable boundary. |
| Session/device/Cinematic configuration | CaptureService |
Actor-confined AVFoundation ownership. |
| Metadata conversion to normalized focus records | Capture-service metadata delegate extension | Runs where raw metadata output and coordinate conversion are available. |
| Focus overlay geometry and gestures | FocusOverlayView |
View-specific normalization/rendering stays in SwiftUI. |
| Recording lifecycle | MovieCapture |
Contains file-output delegate and timer state. |
| Photos save and thumbnail stream | MediaLibrary |
Separate PhotoKit actor boundary. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| MVVM-style mediation | CinematicCaptureSample/CameraModel.swift:13 and class comment |
Keeps SwiftUI free of session mutation. |
| Protocol-oriented substitution | CinematicCaptureSample/Model/Camera.swift:13 |
Allows PreviewCameraModel in previews/Simulator. |
| Actor-isolated service | CinematicCaptureSample/CaptureService.swift:18 and custom executor at line 70 |
Runs session work serially away from MainActor. |
| Observable store | CinematicCaptureSample/CaptureService.swift:12 and CinematicMetadataManager |
Publishes focus metadata to overlay views without exposing raw outputs. |
| Delegate-to-async adapter | CinematicCaptureSample/Capture/MovieCapture.swift:54 |
Converts recording completion into an async result. |
Main application flow
sequenceDiagram
participant Output as Metadata output
participant Service as CaptureService
participant Store as CinematicMetadataManager
participant Overlay as FocusOverlayView
actor User
Output->>Service: metadataOutput(objects)
Service->>Store: replace normalized focus metadata
Store-->>Overlay: observable update
User->>Overlay: tap or long press
Overlay->>Service: focus command through CameraModel
Service->>Service: configure device focus
Reference code
CinematicCaptureSample/CaptureService.swift:498 — metadata publication
nonisolated func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
assumeIsolated { isolatedSelf in
isolatedSelf.metadataObjectsCache = metadataObjects
// Convert objects to CinematicFocusMetadata values.
isolatedSelf.metadataManager.cinematicFocusMetadata = cinematicFocusMetadataObjects
}
}Naming conventions
- Types: responsibility and feature names (
CameraModel,CaptureService,CinematicMetadataManager,FocusOverlayView). - Protocols: capability nouns (
Camera,OutputService). - Methods: user commands (
toggleRecording,tapPreview,longPressPreview) and setup verbs (setupCinematicCapture,metadataVDOSetup). - State:
is...,min..., andmax...prefixes make Boolean/range semantics visible. - Files: primary type per file, folders by Capture/Model/Views/Support responsibility.
Architecture takeaways
- Keep presentation state on the main actor and capture mutation on a dedicated serial actor executor.
- Expose focus metadata as small normalized value objects instead of raw AVFoundation objects in views.
- Use a protocol-backed camera model to keep hardware-free SwiftUI previews possible.
- A same-file
fileprivateextension is a precise home for view-only geometry helpers. - The movie-output delegate remains inside
MovieCapture, whileCaptureServicecoordinates Cinematic-specific behavior.
Source map
| Source file | Relevant symbols |
|---|---|
CinematicCaptureSample/CinematicCaptureSampleApp.swift |
Composition root |
CinematicCaptureSample/CameraModel.swift |
Main-actor UI model |
CinematicCaptureSample/CaptureService.swift |
Capture actor, metadata store/delegates, Cinematic focus |
CinematicCaptureSample/Model/Camera.swift |
Camera, CinematicFocusMetadata |
CinematicCaptureSample/Capture/MovieCapture.swift |
Recording delegate/async adapter |
CinematicCaptureSample/Model/MediaLibrary.swift |
PhotoKit save actor |
CinematicCaptureSample/Views/FocusOverlayView.swift |
Focus UI and geometry helper |