2025 Audio & VideoSpatial Computing
WWDC25 · 26 min · Audio & Video / Spatial Computing
Support immersive video playback in visionOS apps
Discover how to play immersive videos in visionOS apps. We’ll cover various immersive rendering modes, review the frameworks that support them, and walk through how to render immersive video in your app. To get the most out of this video, we recommend first watching “Explore video experiences for visionOS” from WWDC25.
Watch at developer.apple.com ↗Chapters
- 0:00 — Introduction
- 1:24 — Video profiles supported in visionOS 26
- 3:09 — Immersive video playback in Quick Look
- 4:25 — Immersive video playback with AVKit
- 9:11 — Comfort mitigation detection
- 9:51 — Custom playback in RealityKit
- 11:48 — Progressive immersion mode in RealityKit
- 16:32 — Spatial video rendering with RealityKit
- 21:16 — Comfort mitigation detection in RealityKit
- 22:57 — RealityKit content integration with SwiftUI
Code shown on screen · 14 snippets
AVExperienceController - AutomaticTransitionToImmersive
struct ExpandedConfiguration {
enum AutomaticTransitionToImmersive {
case `default`
case none
}
} Disable Automatic Transitions to immersive
import AVKit
let controller = AVPlayerViewController()
let experienceController = controller.experienceController
experienceController.allowedExperiences = .recommended(including: [.expanded, .immersive])
experienceController.configuration.expanded.automaticTransitionToImmersive = .none
await experienceController.transition(to: .expanded) AVExperienceController - Immersive
enum Experience {
case immersive
}
struct Configuration {
struct Placement {
static var unspecified: Placement
static func over(scene: UIScene) -> Placement
}
} Transition to immersive
import AVKit
let controller = AVPlayerViewController()
let experienceController = controller.experienceController
experienceController.allowedExperiences = .recommended(including: [.immersive])
let myScene = getMyPreferredWindowUIScene()
experienceController.configuration.placement = .over(scene: myScene)
await experienceController.transition(to: .immersive) AVExperienceController.Delegate
func experienceController(_ controller: AVExperienceController, didChangeAvailableExperiences availableExperiences: AVExperienceController.Experiences)
func experienceController(_ controller: AVExperienceController, prepareForTransitionUsing context: AVExperienceController.TransitionContext) async
func experienceController(_ controller: AVExperienceController, didChangeTransitionContext context: AVExperienceController.TransitionContext) PortalVideoView
@main
struct ImmersiveVideoApp: App {
var body: some Scene {
WindowGroup {
PortalVideoView()
}
}
} Portal Rendering
import AVFoundation
import RealityKit
import SwiftUI
struct PortalVideoView: View {
var body: some View {
RealityView { content in
guard let url = URL(string: "https://cdn.example.com/My180.m3u8") else { return }
let player = AVPlayer(playerItem: AVPlayerItem(url: url))
let videoEntity = Entity()
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.desiredImmersiveViewingMode = .portal
videoEntity.components.set(videoPlayerComponent)
videoEntity.scale *= 0.4
content.add(videoEntity)
}
}
} Progressive Immersion Rendering
import AVFoundation
import RealityKit
import SwiftUI
struct ProgressiveVideoView: View {
var body: some View {
RealityView { content in
guard let url = URL(string: "https://cdn.example.com/My180.m3u8") else { return }
let player = AVPlayer(playerItem: AVPlayerItem(url: url))
let videoEntity = Entity()
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.desiredImmersiveViewingMode = .progressive
videoEntity.components.set(videoPlayerComponent)
content.add(videoEntity)
}
}
} ProgressiveVideoView
import AVFoundation
import RealityKit
import SwiftUI
@main
struct ImmersiveVideoApp: App {
var body: some Scene {
ImmersiveSpace {
ProgressiveVideoView()
}
.immersionStyle(selection: .constant(.progressive(0.1...1, initialAmount: 1.0)), in: .progressive)
}
} SpatialVideoMode
if let vpc = components.get[VideoPlayerComponent.self] {
vpc.desiredSpatialVideoMode = .spatial
} Spatial Video Portal Rendering
import AVFoundation
import RealityKit
import SwiftUI
struct PortalSpatialVideoView: View {
var body: some View {
RealityView { content in
let url = Bundle.main.url(forResource: "MySpatialVideo", withExtension: "mov")!
let player = AVPlayer(url: url)
let videoEntity = Entity()
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.desiredViewingMode = .stereo
videoPlayerComponent.desiredSpatialVideoMode = .spatial
videoPlayerComponent.desiredImmersiveViewingMode = .portal
videoEntity.components.set(videoPlayerComponent)
videoEntity.scale *= 0.4
content.add(videoEntity)
}
}
} Spatial Video Immersive Rendering
import AVFoundation
import RealityKit
import SwiftUI
struct PortalSpatialVideoView: View {
var body: some View {
RealityView { content in
let url = Bundle.main.url(forResource: "MySpatialVideo", withExtension: "mov")!
let player = AVPlayer(url: url)
let videoEntity = Entity()
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.desiredViewingMode = .stereo
videoPlayerComponent.desiredSpatialVideoMode = .spatial
videoPlayerComponent.desiredImmersiveViewingMode = .full
videoEntity.position = [0, 1.5, -1]
videoEntity.components.set(videoPlayerComponent)
content.add(videoEntity)
}
}
} ImmersiveSpatialVideoView
import AVFoundation
import RealityKit
import SwiftUI
@main
struct SpatialVideoApp: App {
var body: some Scene {
ImmersiveSpace {
ContentSimpleView()
}
.immersionStyle(selection: .constant(.mixed), in: .mixed)
.immersiveEnvironmentBehavior(.coexist)
}
} Comfort Mitigation Event
switch event.comfortMitigation {
case .reduceImmersion:
// Default behavior
break
case .play:
// No action
break
case .pause:
// Show custom pause dialog
break
} Resources
Related sessions
-
26 min -
29 min -
21 min -
32 min -
20 min -
15 min