Rendering stereoscopic video with RealityKit
At a glance
| Item | Summary |
|---|---|
| Purpose | Render stereoscopic video in visionOS with RealityKit. |
| App architecture | RealityKitPlaybackApp owns PlayerModel; the model drives AVFoundation decode and sample-buffer rendering while ContentView binds the resulting stereoscopic frames to RealityKit presentation. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Async update consumer, Sample-buffer video bridge |
| Project style | Code-rich sample with 6 scanned Swift file(s) and 570 implementation line(s). |
Project structure
Source bundle/
└── RealityKit-Playback-AVSampleBufferVideoRenderer/
├── Models/
│ ├── PlayerModel.swift # PlayerModel, PlayerState
│ ├── LoopingVideoPlayer.swift # LoopingVideoPlayer
│ ├── SerialProcessor.swift # SerialProcessor
│ └── StereoMetadata.swift # StereoMetadata, FramePacking
├── RealityKitPlaybackApp.swift # RealityKitPlaybackApp
└── Views/
└── ContentView.swift # ContentView
Structure observations
- The runtime boundary is WindowGroup; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is Swift; role-named types and feature folders separate the major responsibilities.
Overall architecture
flowchart LR
RealityKitPlaybackApp_1["RealityKitPlaybackApp"]
WindowGroup_2["WindowGroup"]
ContentView_3["ContentView"]
PlayerModel_4["PlayerModel"]
RealityKit_entity_graph_5["RealityKit entity graph"]
AV_sample_buffers___RealityKit_video_6["AV sample buffers + RealityKit video"]
RealityKitPlaybackApp_1 --> WindowGroup_2
WindowGroup_2 --> ContentView_3
ContentView_3 --> PlayerModel_4
PlayerModel_4 --> RealityKit_entity_graph_5
RealityKit_entity_graph_5 --> AV_sample_buffers___RealityKit_video_6
Reference code
RealityKit-Playback-AVSampleBufferVideoRenderer/RealityKitPlaybackApp.swift:12 — executable or app-lifecycle anchor.
struct RealityKitPlaybackApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It separates lifecycle, presentation, stable state, framework/session work, and the RealityKit entity graph.
Ownership and state
classDiagram
PlayerModel --> LoopingVideoPlayer : player
RealityKitPlaybackApp *-- PlayerModel : player
PlayerModel --> PlayerState : state
PlayerModel --> Bundle : resourceURL
Ownership evidence
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:43 — representative stored state or nearest verified lifecycle anchor.
@MainActor
@Observable
final class PlayerModel {
// ...
private let player: LoopingVideoPlayer
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
PlayerModel |
LoopingVideoPlayer as player |
stores and coordinates | The declaring type controls writes |
RealityKitPlaybackApp |
PlayerModel as player |
creates and retains | The declaring type controls writes |
PlayerModel |
PlayerState as state |
owns value state | The declaring type controls writes |
PlayerModel |
Bundle as resourceURL |
stores and coordinates | The declaring type controls writes |
Ownership is intentionally narrow: environment, bindings, observed objects, weak links, and delegate callbacks do not prove lifetime ownership. Initialized stored services and wrapper-managed state do; entities added inside a RealityKit content closure belong to that entity graph.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
RealityKitPlaybackApp (RealityKit-Playback-AVSampleBufferVideoRenderer/RealityKitPlaybackApp.swift:12) |
Declares app lifecycle and top-level dependency lifetime. | App |
ContentView (RealityKit-Playback-AVSampleBufferVideoRenderer/Views/ContentView.swift:12) |
Presents UI and forwards gestures or lifecycle events. | View |
PlayerModel (RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:14) |
Owns feature state and domain transitions. | Concrete framework collaborators |
The reviewed source defines no local substitution protocol. Its App, View, delegate, renderer, ARKit, and RealityKit conformances are framework-facing contracts, so this document does not label the whole app protocol-oriented.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
private static let resourceURL = Bundle.main.bundleURL.appendingPathCom... (RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:16) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
No reviewed Swift access example uses fileprivate, private(set), public, open; declarations without a modifier are internal.
Reference code
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:16 — representative visibility boundary.
@MainActor
@Observable
final class PlayerModel {
// ...
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | RealityKitPlaybackApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | ContentView |
The view/controller forwards input and owns only presentation-local work. |
| Shared feature state and commands | PlayerModel |
A stable owner prevents view recomputation or callbacks from duplicating transitions. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | RealityKit-Playback-AVSampleBufferVideoRenderer/RealityKitPlaybackApp.swift:12 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | RealityKit-Playback-AVSampleBufferVideoRenderer/Views/ContentView.swift:26 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Observable state owner | RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:14 |
Keeps shared feature state in a stable owner rather than in transient view values. |
| Async update consumer | RealityKit-Playback-AVSampleBufferVideoRenderer/Models/SerialProcessor.swift:96 |
Consumes long-lived framework output in a cancellable asynchronous task. |
| Sample-buffer video bridge | RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:46 |
Keeps decode and sample-buffer timing in the player model and exposes the resulting video resource to RealityKit. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
RealityKitPlaybackApp; Model:PlayerModel; View:ContentView. - Protocols: no app-defined protocol in the reviewed source.
- Commands use verb-led methods:
load,play,stop,enqueueProcessor,loop,process,mediaDataReadyStream,transform. - Files and folders use primary-type or responsibility names such as
Views,Models,Rendering,Components,Systems,Packages, or feature names where those boundaries exist.
Architecture takeaways
- Treat
RealityKitPlaybackAppas the owner of executable or scene lifecycle, not automatically as the owner of every entity created later. - Keep view/controller input near presentation, but move sessions, reconstruction, capture, rendering, shared game state, or documents into stable owners when their lifetime exceeds one callback or render pass.
- Tie asynchronous session/output consumers to an explicit owner so cancellation and teardown follow the same lifecycle.
Source map
| Source file | Relevant symbols |
|---|---|
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/PlayerModel.swift:14 |
PlayerModel, PlayerState |
RealityKit-Playback-AVSampleBufferVideoRenderer/RealityKitPlaybackApp.swift:12 |
RealityKitPlaybackApp |
RealityKit-Playback-AVSampleBufferVideoRenderer/Views/ContentView.swift:12 |
ContentView |
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/LoopingVideoPlayer.swift:11 |
LoopingVideoPlayer |
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/SerialProcessor.swift:14 |
SerialProcessor |
RealityKit-Playback-AVSampleBufferVideoRenderer/Models/StereoMetadata.swift:12 |
StereoMetadata, FramePacking |