Playing immersive media with RealityKit
At a glance
| Item | Summary |
|---|---|
| Purpose | Create an immersive video playback experience with RealityKit. |
| App architecture | RealityKitImmersivePlaybackApp composes WindowGroup + ImmersiveSpace; AppModel keeps player and presentation state stable while ProgressivePlayerImmersiveSpace maps it into RealityKit immersive playback. |
| Main patterns | SwiftUI scene composition, SwiftUI–RealityKit bridge, Explicit immersive-space lifecycle, Observable state owner, Async update consumer |
| Project style | Code-rich sample with 19 scanned Swift file(s) and 1433 Swift line(s); resources and generated assets are excluded from those counts. |
Project structure
Source bundle/
└── RealityKitImmersivePlayback/
├── RealityKitImmersivePlaybackApp.swift # RealityKitImmersivePlaybackApp
├── Models/
│ ├── PlayerModel.swift # PlayerModel
│ ├── AppModel.swift # AppModel, ImmersiveSpaceState, WindowState
│ └── VideoModel.swift # VideoModel, ContentType, Mode
├── Scenes/
│ └── PlayerImmersiveSpace.swift # PlayerImmersiveSpace, ProgressivePlayerImmersiveSpace, SpatialPlayerImmersiveSpace
└── Views/
├── ContentView.swift # ContentView
├── ImmersiveControlsView.swift # ImmersiveControlsView
└── LoadingIndicator.swift # LoadingIndicator, LoadingIndicatorOverlay, LoadingIndicatorSpatialOverlay
Structure observations
- The runtime boundary is WindowGroup + ImmersiveSpace; the pruned tree lists only files that explain lifecycle, state, or framework integration.
- App code is split into role-named views, models, managers, providers, components, or systems.
Overall architecture
flowchart LR
RealityKitImmersivePlaybackApp_1["RealityKitImmersivePlaybackApp"]
WindowGroup___ImmersiveSpace_2["WindowGroup + ImmersiveSpace"]
ProgressivePlayerImmersiveSpace_3["ProgressivePlayerImmersiveSpace"]
AppModel_4["AppModel"]
RealityView_entity_graph_5["RealityView entity graph"]
AV_playback___system_presentation_6["AV playback + system presentation"]
RealityKitImmersivePlaybackApp_1 --> WindowGroup___ImmersiveSpace_2
WindowGroup___ImmersiveSpace_2 --> ProgressivePlayerImmersiveSpace_3
ProgressivePlayerImmersiveSpace_3 --> AppModel_4
AppModel_4 --> RealityView_entity_graph_5
RealityView_entity_graph_5 --> AV_playback___system_presentation_6
Reference code
RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:11 — the app or executable entry declares the outer scene lifecycle.
struct RealityKitImmersivePlaybackApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It keeps scene ownership, shared state, RealityKit content, and framework-provider work at separate levels.
Ownership and state
classDiagram
RealityKitImmersivePlaybackApp *-- AppModel : appModel
AppModel --> VideoModel : selectedVideo
AppModel --> PlayerModel : playerModel
AppModel --> WindowState : windowState
Ownership evidence
RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:17 — representative stored state or the nearest verified lifecycle anchor.
@main
struct RealityKitImmersivePlaybackApp: App {
// ...
@State private var appModel = AppModel()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
RealityKitImmersivePlaybackApp |
AppModel as appModel |
creates and retains | Only the declaring scope writes |
AppModel |
VideoModel as selectedVideo |
stores and coordinates | Only the declaring scope writes |
AppModel |
PlayerModel as playerModel |
stores and coordinates | The owning type coordinates writes |
AppModel |
WindowState as windowState |
stores and coordinates | The owning type coordinates writes |
Ownership here is deliberately narrow: @Environment and weak references are shared links, initialized @State or stored services are lifecycle ownership, and a RealityView content closure owns additions to its entity graph without making the SwiftUI view a reference-type owner.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
RealityKitImmersivePlaybackApp (RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:11) |
Declares app scenes and top-level dependency lifetime. | App |
ProgressivePlayerImmersiveSpace (RealityKitImmersivePlayback/Scenes/PlayerImmersiveSpace.swift:30) |
Provides a sample-specific value or framework adapter. | Scene |
AppModel (RealityKitImmersivePlayback/Models/AppModel.swift:15) |
Owns observable feature state and domain transitions. | Concrete framework collaborators |
PlayerModel (RealityKitImmersivePlayback/Models/PlayerModel.swift:13) |
Owns observable feature state and domain transitions. | Concrete framework collaborators |
VideoModel (RealityKitImmersivePlayback/Models/VideoModel.swift:11) |
Owns observable feature state and domain transitions. | Hashable, Identifiable |
ContentView (RealityKitImmersivePlayback/Views/ContentView.swift:10) |
Presents UI and forwards gestures or lifecycle events. | View |
ImmersiveControlsView (RealityKitImmersivePlayback/Views/ImmersiveControlsView.swift:11) |
Presents UI and forwards gestures or lifecycle events. | View |
The source defines no local substitution protocol in the reviewed boundary. Its protocol use is framework-facing (App, View, RealityKit/ARKit protocols, or platform adapters), so this document does not label the whole app protocol-oriented.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
private(set) var selectedVideo: VideoModel? (RealityKitImmersivePlayback/Models/AppModel.swift:23) |
private(set) |
The getter keeps its wider visibility, while mutation stays inside the declaring type and its permitted same-file extensions. | Inference: Protect a state invariant while allowing observation. |
private let isPlayerItemReadyToPlay: Bool (RealityKitImmersivePlayback/Models/PlaybackReadiness.swift:11) |
private |
Use is restricted to the declaration and same-file extensions permitted by Swift. | Inference: Hide implementation details and lifecycle-sensitive state. |
fileprivate var preferredPlaybackScene: AppModel.PlaybackScene { (RealityKitImmersivePlayback/Models/VideoModel.swift:84) |
fileprivate |
Use is restricted to declarations in this source file. | Inference: Share a helper across same-file extensions without making it module-wide. |
public var displayMessage: String? { (RealityKitImmersivePlayback/Extensions/VideoPlayerComponent+Extensions.swift:17) |
public |
The declaration is available to importing modules, subject to its containing type’s visibility. | Inference: Satisfy a cross-target or framework-facing surface without implying subclassability. |
No reviewed declaration uses open; unmodified Swift declarations are internal.
Reference code
RealityKitImmersivePlayback/Models/AppModel.swift:23 — representative visibility boundary.
private(set) var selectedVideo: VideoModel?
private(set) var videoModes: VideoModes?
init() {
// ...
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Scene declaration and dependency lifetime | RealityKitImmersivePlaybackApp |
The App/entry boundary determines window, volume, and immersive-space lifetime. |
| Presentation, attachments, and gestures | ProgressivePlayerImmersiveSpace |
SwiftUI view code forwards user intent and RealityView lifecycle events. |
| Shared feature state and commands | AppModel |
A role-named owner prevents sibling views from duplicating transitions. |
| Playback resource lifecycle | RealityKitImmersivePlayback/Models/PlayerModel.swift:14 |
The player/component owner survives view recomputation and drives system presentation. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:11 |
Keeps windows, volumes, and immersive-space lifecycle visible at the app boundary. |
| SwiftUI–RealityKit bridge | RealityKitImmersivePlayback/Views/LoadingIndicator.swift:49 |
Builds and updates a RealityKit entity graph from SwiftUI lifecycle closures. |
| Explicit immersive-space lifecycle | RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:14 |
Makes immersive presentation a scene transition rather than hidden global state. |
| Observable state owner | RealityKitImmersivePlayback/Models/AppModel.swift:15 |
Shares feature state across multiple views without moving framework resources into view values. |
| Async update consumer | RealityKitImmersivePlayback/Models/PlayerModel.swift:73 |
Consumes provider or event streams in cancellable structured tasks. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
RealityKitImmersivePlaybackApp; Model:AppModel,PlayerModel,VideoModel; View:ContentView,ImmersiveControlsView,TransportView,VideoPlayerView. - Protocols: no app-defined protocol in the reviewed source.
- Commands use verb-led methods:
closeImmersivePlayer,makeDefaultScenes,openImmersivePlayer,transitionStageFromBrowsingToPlayback,transitionStageFromPlaybackToBrowsing,transitionPlaybackStages,transitionStage,loadItem. - Files generally match their primary type;
Views,Models,Managers,Providers,Components,Systems, andPackagesfolders describe architectural roles where present.
Architecture takeaways
- Treat
RealityKitImmersivePlaybackAppas the owner of scene declarations, not as the owner of every RealityKit entity created later. - Keep view-local interaction in SwiftUI, but move provider sessions, playback resources, shared game state, or transport state into a stable owner when their lifetime exceeds one render pass.
- Model immersive-space open, transition, and close states explicitly so windows and immersive content cannot drift apart.
Source map
| Source file | Relevant symbols |
|---|---|
RealityKitImmersivePlayback/RealityKitImmersivePlaybackApp.swift:11 |
RealityKitImmersivePlaybackApp |
RealityKitImmersivePlayback/Models/PlayerModel.swift:13 |
PlayerModel |
RealityKitImmersivePlayback/Models/AppModel.swift:15 |
AppModel, ImmersiveSpaceState, WindowState, PlaybackScene, Stage |
RealityKitImmersivePlayback/Models/VideoModel.swift:11 |
VideoModel, ContentType, Mode |
RealityKitImmersivePlayback/Scenes/PlayerImmersiveSpace.swift:12 |
PlayerImmersiveSpace, ProgressivePlayerImmersiveSpace, SpatialPlayerImmersiveSpace |
RealityKitImmersivePlayback/Views/ContentView.swift:10 |
ContentView |
RealityKitImmersivePlayback/Views/ImmersiveControlsView.swift:11 |
ImmersiveControlsView |
RealityKitImmersivePlayback/Views/LoadingIndicator.swift:13 |
LoadingIndicator, LoadingIndicatorOverlay, LoadingIndicatorSpatialOverlay, LoadingIndicatorSceneOverlayModifier |