<!--
{
  "availability" : [
    "visionOS: 26.0.0 -",
    "Xcode: 26.0.0 -"
  ],
  "documentType" : "article",
  "framework" : "visionOS",
  "identifier" : "/documentation/visionOS/playing-immersive-media-with-realitykit",
  "metadataVersion" : "0.1.0",
  "role" : "sampleCode",
  "title" : "Playing immersive media with RealityKit"
}
-->

# Playing immersive media with RealityKit

Create an immersive video playback experience with RealityKit.

## Overview

This sample shows how to build an immersive video playback experience for visionOS.
It demonstrates the use of RealityKit to present multiple content types, in both windows and immersive spaces.
It also presents some key factors to consider as you customize your app experience.

> Note: This sample code project is associated with WWDC25 session 296: [Support immersive video playback in visionOS apps](https://developer.apple.com/videos/play/wwdc2025/296).

## Choose a playback approach

When it comes to providing immersive video playback on visionOS, there are a few different approaches you can take:

- <doc://com.apple.documentation/documentation/AVKit> provides a superior video playback experience in visionOS.
  With AVKit, you can present an interface that’s consistent with other apps on the system, as it requires the least effort to adopt.
  For more information on using AVKit in visionOS, see <doc://com.apple.documentation/documentation/AVKit/adopting-the-system-player-interface-in-visionos>.
- <doc://com.apple.documentation/documentation/RealityKit> enables immersive video playback with <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent>.
  RealityKit manages changes in immersive viewing mode to preserve motion comfort, but it requires a little more effort to customize playback controls for your experience.

If you’re already using RealityKit in your app, <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent> may be suitable, particularly if your video content is relatively short.
In that case, it may not be necessary to offer controls for skipping, or to transition between full immersion and portal window viewing.
For design guidance, see Human Interface Guidelines > [Playing video](https://developer.apple.com/design/human-interface-guidelines/playing-video#visionOS).

## Configure video player to play immersive media

<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent> relies on three pairs of properties to play immersive media.
For each pair, one property is used for mutation, and another for introspection.

|Mutation                                                                                                 |Introspection                                                                                                    |
|:--------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------|
|<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/desiredImmersiveViewingMode>|<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/immersiveViewingMode-swift.property>|
|<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/desiredSpatialVideoMode>    |<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/spatialVideoMode-swift.property>    |
|<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/desiredViewingMode>         |<doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/viewingMode>                        |

The following code configures a video player to present spatial video within a portal window:

```swift
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)

videoPlayerComponent.desiredImmersiveViewingMode = .portal
videoPlayerComponent.desiredSpatialVideoMode = .spatial
videoPlayerComponent.desiredViewingMode = .stereo

entity.components[VideoPlayerComponent.self] = videoPlayerComponent
```

Here, the app uses `desiredImmersiveViewingMode` to render the content as a portal window.
It then uses `desiredSpatialVideoMode` to apply head-pose–based treatments, and specifies stereoscopic playback with `desiredViewingMode`.

## Size video for the shared space

When presented in an immersive space, <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent> automatically manages projection of the video content.
In a window, however, additional considerations are necessary to achieve best results.

Because portal-based presentations prefer a 16:9 aspect ratio,
the sample uses <doc://com.apple.documentation/documentation/SwiftUI/View/aspectRatio(_:contentMode:)>:

```swift
VideoPlayerView(videoModel: selection)
    .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fit)
```

The sample uses the <doc://com.apple.documentation/documentation/RealityKit/Entity> that contains the `VideoPlayerComponent` to scale the player to fit within the default scene size.
When correctly configured, the spatial video has feathered edges with rounded corners.

The sample uses a <doc://com.apple.documentation/documentation/SwiftUI/GeometryReader3D> to determine the scene size.
It then calls `scaleToFit(_:proxy:content:)` from both the `make` and `update` closures of the root <doc://com.apple.documentation/documentation/RealityKit/RealityView>, which ensures that the video properly resizes with the content window.

```swift
GeometryReader3D { geometry in
    RealityView { content in
        configureContent(content, playbackScene: appModel.playbackScene)
        scaleToFit(videoEntity, proxy: geometry, content: content)
        content.add(rootEntity)
    } update: { content in
        scaleToFit(videoEntity, proxy: geometry, content: content)
    }
```

The `scaleToFit(_:proxy:content:)` scales the <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerComponent/playerScreenSize> to fit the size of the containing scene.

```swift
func scaleToFit(_ entity: Entity, proxy: GeometryProxy3D, content: RealityViewContent) {
    guard let videoPlayer = videoEntity.videoPlayerComponent, videoPlayer.needsScaling else {
        return
    }

    let frame = proxy.frame(in: .local)
    let frameSize = abs(content.convert(frame.size, from: .local, to: .scene))
    entity.scaleToFit(videoPlayer.playerScreenSize, within: frameSize)
}
```

## Customize playback controls

The sample provides custom playback controls with three basic functions:

1. A toggle for controlling Play and Pause.
2. Immersion toggle.
3. Exit, when in an immersive space.

The app uses the same view, `TransportView`, regardless of whether playback occurs in a window or an immersive space.

For playback in the *Shared Space*, [ornaments](https://developer.apple.com/design/human-interface-guidelines/ornaments) are ideal: they attach to windows without obscuring the content within.
For more information, see [Present common controls in an ornament](https://developer.apple.com/tutorials/develop-in-swift/present-common-controls-in-an-ornament).

```swift
.ornament(attachmentAnchor: .scene(.bottom)) {
    TransportView()
}
```

The *Full Space* presentation uses the same `TransportView`, but it’s incorporated through composition instead.
It is added to an outer type, `ImmersiveControlsView`, which is then placed within a <doc://com.apple.documentation/documentation/RealityKit/ViewAttachmentComponent> for use in a <doc://com.apple.documentation/documentation/RealityKit/RealityView>:

```swift
private func updateImmersiveControls(with mitigation: VideoPlayerComponent.VideoComfortMitigation? = nil) {
    let controlsAttachment = ViewAttachmentComponent(rootView: ImmersiveControlsView(comfortMitigation: mitigation))
    immersiveControls.components.set(controlsAttachment)
}
```

> Note: For details regarding managing changes in immersive viewing mode, see [Playing immersive video](https://developer.apple.com/documentation/realitykit/videoplayercomponent#Playing-immersive-video).

## Preserve motion comfort

Because scenes with high motion can lead to motion discomfort, be mindful of motion comfort when presenting media immersively.
Two key considerations include: configuring the immersive space properly, and responding to video comfort mitigation events in a timely fashion.

When presenting your content in an <doc://com.apple.documentation/documentation/SwiftUI/ImmersiveSpace>, use a progressive <doc://com.apple.documentation/documentation/SwiftUI/ImmersionStyle> so that a person can turn the Digital Crown to adjust the amount of visible passthrough video:

```swift
PlayerImmersiveSpace(sceneIdentifier: Self.sceneID)
    .immersionStyle(
        selection: .constant(ProgressiveImmersionStyle(immersion: 0.01...1, initialAmount: 1)),
        in: .progressive
    )
```

The <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerEvents/VideoComfortMitigationDidOccur> event includes a single property, <doc://com.apple.documentation/documentation/RealityKit/VideoPlayerEvents/VideoComfortMitigationDidOccur/comfortMitigation>.
This event indicates that the system detected high motion and took steps to preserve motion comfort based on the person’s preference. The following shows how the sample app subscribes to this event:

```swift
_ = content.subscribe(
    to: VideoPlayerEvents.VideoComfortMitigationDidOccur.self,
    on: entity
) { event in
    areTransportControlsVisible = true
    updateImmersiveControls(with: event.comfortMitigation)
}
```

The sample uses this event to advise the person that system mitigation took place, and updates `ImmersiveControlsView` accordingly.

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)