Editing and playing HDR video
At a glance
| Item |
Summary |
| Purpose |
Demonstrate HDR-safe playback/export with a built-in compositor, Core Image handler, or custom video compositor. |
| App architecture |
Shared asset/filter/export code plus target-selected AVVideoCompositionBuilder implementations and platform UI adapters. |
| Main patterns |
Builder, compile-time strategy by target membership, framework protocol implementation, and platform-view adapter. |
| Project style |
Multiple app targets reuse CommonSource/CommonUI; compositor folders provide mutually exclusive same-named builders. |
Project structure
├── CommonSource/
│ ├── Composition.swift # AssetLoader, AssetExporter, HDR filter
│ └── HDRIndicator.ci.metal
├── CommonUI-macOS/ # SwiftUI + AVPlayerView bridge
├── CIFilter-iOS/ # iOS SwiftUI + AVPlayerViewController bridge
├── BuiltInCompositor/
│ └── AVVideoCompositionBuilder.swift
├── CIFilter/
│ └── AVVideoCompositionBuilder.swift
└── CustomCompositor/
└── AVVideoCompositionBuilder.swift # Builder + AVVideoCompositing type
Structure observations
- Every scheme compiles one same-named
AVVideoCompositionBuilder; AssetLoader calls a stable static method without runtime dependency injection.
- Shared files hold asset assembly, export, and the Core Image filter; technique-specific code stays in target folders.
- UI differs by platform, while the composition call chain remains shared.
Overall architecture
flowchart LR
UI["Platform UI"] --> Loader["AssetLoader"]
Loader --> Builder["Target-specific AVVideoCompositionBuilder"]
Builder --> BuiltIn["Built-in instructions"]
Builder --> CIFilter["Core Image handler"]
Builder --> Custom["SampleCustomCompositor"]
Custom --> Filter["HDRIndicatorFilter"]
Loader --> Player["AVPlayerItem"]
UI --> Exporter["AssetExporter"]
Exporter --> Builder
Reference code
CommonSource/Composition.swift:13 — shared call into the target-selected builder
class AssetLoader {
static func loadAsCompositions() -> (AVComposition, AVVideoComposition) {
// ... create AVMutableComposition ...
let videoComposition = AVVideoCompositionBuilder
.buildVideoComposition(asset: asset, avComposition: avComposition)
return (avComposition, videoComposition)
}
}
Interpretation
This is a compile-time variation point: schemes select which builder file belongs to the target. It resembles Strategy, but there is no runtime strategy protocol or injected instance. Playback and export therefore share one composition-building API in every variant.
Ownership and state
classDiagram
PlatformUI ..> AssetLoader : calls
AssetLoader ..> AVMutableComposition : creates and returns
AssetExporter *-- ProgressChecker : returns handle
ProgressChecker o-- AVAssetExportSession : privately retains
SampleCustomCompositor *-- HDRIndicatorFilter : creates
SampleCustomCompositor *-- CIContext : creates
PlayerView *-- AVPlayer : creates
Ownership evidence
CommonSource/Composition.swift:35 and CustomCompositor/AVVideoCompositionBuilder.swift:29 — operation handle and custom compositor resources
struct ProgressChecker {
private var exportSession: AVAssetExportSession? = nil
var progress: Float { exportSession?.progress ?? 0 }
}
class SampleCustomCompositor: NSObject, AVVideoCompositing {
private let filter = HDRIndicatorFilter()
private let coreImageContext = CIContext(/* ... */)
}
| Owner |
Object or state |
Relationship |
Mutation authority |
AssetLoader call |
Asset, mutable composition, video composition |
Creates and returns |
Static function during construction |
PlayerView / AVKit adapter |
Player item and player |
Creates for view lifecycle |
Player view methods |
AssetExporter operation |
Export session |
Creates; ProgressChecker retains a private reference |
Export session; checker only reads status |
SampleCustomCompositor |
Filter and Core Image context |
Creates and retains |
Compositor callbacks |
SwiftUI ContentView |
Alerts, progress handle, presentation state |
Owns as @State |
View actions and export callback |
Class and protocol design
CustomCompositor/AVVideoCompositionBuilder.swift:29 — framework-defined compositor capability
class SampleCustomCompositor: NSObject, AVVideoCompositing {
var supportsWideColorSourceFrames = true
var supportsHDRSourceFrames = true
func startRequest(_ request: AVAsynchronousVideoCompositionRequest) {
// render HDR-preserving output
}
}
| Type |
Responsibility |
Depends on |
AssetLoader |
Assemble the source track and request a target-specific video composition |
AVFoundation, builder name |
AVVideoCompositionBuilder variants |
Configure one editing technique |
AVFoundation and optionally Core Image |
SampleCustomCompositor |
Fulfill asynchronous composition requests |
AVVideoCompositing, Core Image |
HDRIndicatorFilter |
Apply the shared Metal-backed HDR visualization |
CIFilter, CIColorKernel |
AssetExporter.ProgressChecker |
Expose read-only progress/status from a hidden export session |
AVAssetExportSession |
The significant protocol boundary is Apple’s AVVideoCompositing; the sample does not define its own builder protocol.
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
ProgressChecker.exportSession |
private |
Callers can inspect derived status only. |
Prevents external reconfiguration/cancellation through the handle. |
Custom compositor filter and CIContext |
private |
Only compositor callbacks can mutate/render through them. |
Keeps request processing state internal. |
| SwiftUI alert, progress, and timer state |
private |
State cannot be changed by other files. |
Expresses view ownership. |
PlayerViewRepresentable.coordinator |
static private |
Shared bridge storage is limited to that representable type. |
Hides AppKit bridging mechanics. |
Notification.Name.exportAsset |
public static |
Export notification name is visible outside the app module. |
Wider than required here; likely convenience for menu/action wiring. |
| Builders and shared utility types |
internal by default |
Each is visible only inside its selected app target. |
Target composition, not a framework API, chooses the implementation. |
Reference code
CommonUI-macOS/ContentView.swift:73 — private presentation state
@State private var showingAlert = false
@State private var alertReason: AlertReason = .none
@State private var showingExportProgress = false
@State private var exportProgressChecker: AssetExporter.ProgressChecker? = nil
private let exportAssetMenuSelectPublisher =
NotificationCenter.default.publisher(for: .exportAsset)
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| Shared asset/composition assembly |
AssetLoader |
Identical across compositor variants |
| Built-in transform ramp |
BuiltInCompositor/AVVideoCompositionBuilder.swift |
Technique-specific target code |
| Per-frame Core Image handler |
CIFilter/AVVideoCompositionBuilder.swift |
Technique-specific target code |
| Custom request rendering |
SampleCustomCompositor |
Required by the framework protocol |
| Metal-backed HDR filter |
HDRIndicatorFilter and .ci.metal |
Shared image transformation |
| Playback/export presentation |
Platform UI files |
Platform lifecycle and user interaction |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Builder |
CommonSource/Composition.swift:29 |
Gives shared code one construction call for a complex AV object. |
| Compile-time strategy |
BuiltInCompositor/AVVideoCompositionBuilder.swift:14, CIFilter/AVVideoCompositionBuilder.swift:14, CustomCompositor/AVVideoCompositionBuilder.swift:85 |
Schemes swap implementation without runtime branching; not dynamically replaceable. |
| Framework strategy/protocol |
CustomCompositor/AVVideoCompositionBuilder.swift:29 |
AVFoundation invokes the selected compositor implementation. |
| Adapter |
CommonUI-macOS/ContentView.swift:40, CIFilter-iOS/ContentView.swift:13 |
Embeds AppKit/AVKit player views in SwiftUI. |
| Facade-style utilities |
CommonSource/Composition.swift:13, CommonSource/Composition.swift:35 |
UI uses small static load/export operations instead of assembling AV objects. |
Naming conventions
- Variant folders name the editing mechanism:
BuiltInCompositor, CIFilter, and CustomCompositor.
- The same
AVVideoCompositionBuilder name is intentionally reused across exclusive targets.
- Shared types use explicit roles:
AssetLoader, AssetExporter, ProgressChecker, and HDRIndicatorFilter.
- Method names state construction or operation:
buildVideoComposition, loadAsCompositions, exportAsynchronously.
Architecture takeaways
- Target membership can be a clean compile-time variation point for a sample suite.
- Keep a stable builder API so playback and export use identical composition configuration.
- Encapsulate export session mutation and expose a read-only progress handle.
- Use the framework’s compositor protocol when runtime AVFoundation callbacks are the true substitution boundary.
Source map
| Source file |
Relevant symbols |
CommonSource/Composition.swift |
AssetLoader, AssetExporter, HDRIndicatorFilter |
BuiltInCompositor/AVVideoCompositionBuilder.swift |
built-in builder variant |
CIFilter/AVVideoCompositionBuilder.swift |
Core Image handler variant |
CustomCompositor/AVVideoCompositionBuilder.swift |
SampleCustomCompositor, custom builder variant |
CommonUI-macOS/ContentView.swift |
player bridge, ContentView, ProgressPopup |
CIFilter-iOS/ContentView.swift |
iOS AVKit bridge |