You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VisionOS沉浸式空间加载动画实现及AVPlayer动态URL传递方案

VisionOS沉浸式视图:视频加载动画实现与动态URL传递

一、传递动态URL到沉浸式视图

因为沉浸式视图由App结构体初始化,可通过以下两种方式传递动态URL:

方式1:利用ImmersiveSpace的session参数传递

在App结构体中定义沉浸式空间时,通过session.extra接收外部传入的URL:

@main
struct MeditationApp: App {
    var body: some Scene {
        WindowGroup {
            HomeView() // 首页视图,触发打开沉浸式空间
        }

        ImmersiveSpace(id: "MeditationPlayer") { session in
            // 从session参数中提取动态URL
            if let videoURL = session.extra?["videoURL"] as? URL {
                ImmersiveMeditationPlayerView(videoURL: videoURL)
            }
        }
    }
}

在触发打开沉浸式空间的视图(如首页)中,传入目标URL:

struct HomeView: View {
    @Environment(\.openImmersiveSpace) private var openImmersiveSpace

    var body: some View {
        Button("启动沉浸式冥想") {
            // 替换为实际动态获取的URL
            guard let dynamicURL = URL(string: "https://your-video-url.m3u8") else { return }
            Task {
                try? await openImmersiveSpace(id: "MeditationPlayer", extra: ["videoURL": dynamicURL])
            }
        }
    }
}

方式2:App全局状态管理

通过App内的@State变量存储动态URL,沉浸式视图监听该变量:

@main
struct MeditationApp: App {
    @State private var targetVideoURL: URL?
    @State private var immersiveSpaceID: ImmersiveSpace.ID?

    var body: some Scene {
        WindowGroup {
            HomeView()
                .environment(\.setVideoURL) { url in
                    targetVideoURL = url
                    Task {
                        immersiveSpaceID = try? await openImmersiveSpace(id: "MeditationPlayer")
                    }
                }
        }

        ImmersiveSpace(id: "MeditationPlayer") {
            if let url = targetVideoURL {
                ImmersiveMeditationPlayerView(videoURL: url)
            }
        }
    }
}

// 自定义环境键传递URL设置方法
private struct SetVideoURLKey: EnvironmentKey {
    static let defaultValue: (URL) -> Void = { _ in }
}

extension EnvironmentValues {
    var setVideoURL: (URL) -> Void {
        get { self[SetVideoURLKey.self] }
        set { self[SetVideoURLKey.self] = newValue }
    }
}

二、实现视频加载完成前的加载动画

通过监听AVPlayerItem的加载状态,控制沉浸式视图中加载动画的显示/隐藏:

1. 定义视图模型管理播放状态

import AVFoundation
import Observation

@Observable
class VideoPlayerViewModel {
    var isLoading = true
    let player = AVPlayer()
    private var playerItem: AVPlayerItem?
    private var observation: NSKeyValueObservation?

    func setupPlayer(with url: URL) {
        let asset = AVURLAsset(url: url)
        playerItem = AVPlayerItem(asset: asset)
        
        // 监听播放项状态变化
        observation = playerItem?.observe(\.status, options: [.initial, .new]) { [weak self] item, change in
            guard let self = self else { return }
            DispatchQueue.main.async {
                switch item.status {
                case .readyToPlay:
                    self.isLoading = false
                case .failed:
                    self.isLoading = false
                    print("视频加载失败: \(item.error?.localizedDescription ?? "未知错误")")
                default: break
                }
            }
        }
        
        player.replaceCurrentItem(with: playerItem)
        player.play()
    }
}

2. 修改沉浸式视图添加加载动画

import SwiftUI
import RealityKit
import AVFoundation

struct ImmersiveMeditationPlayerView: View {
    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
    let videoURL: URL
    @State private var viewModel = VideoPlayerViewModel()

    var body: some View {
        VStack {
            RealityView { content, attachments in
                // 1. 创建视频球实体
                let videoEntity = Entity()
                let videoMaterial = VideoMaterial(avPlayer: viewModel.player)
                videoEntity.components.set(ModelComponent(
                    mesh: .generateSphere(radius: 1000),
                    materials: [videoMaterial]
                ))
                // 调整视频球属性(修正原代码旋转轴错误)
                videoEntity.scale = .init(x: 1, y: 1, z: -1)
                videoEntity.transform.translation = SIMD3<Float>(0, 10, 0)
                let rotation = simd_quatf(angle: Float(Angle.degrees(90).radians), axis: .init(x: 1, 0, 0))
                videoEntity.transform.rotation = rotation
                content.add(videoEntity)

                // 2. 创建加载动画实体(旋转立方体)
                let loadingEntity = Entity()
                let loadingMesh = MeshResource.generateBox(size: 0.5)
                let loadingMaterial = SimpleMaterial(color: .white, isMetallic: true)
                loadingEntity.components.set(ModelComponent(mesh: loadingMesh, materials: [loadingMaterial]))
                loadingEntity.transform.translation = SIMD3<Float>(0, 10, 0)
                content.add(loadingEntity)
                attachments.set(loadingEntity, for: "loading")

                // 3. 绑定加载状态控制动画
                viewModel.$isLoading
                    .receive(on: DispatchQueue.main)
                    .sink { isLoading in
                        guard let loadingEntity = attachments.entity(for: "loading") else { return }
                        if isLoading {
                            // 启动无限旋转动画
                            loadingEntity.move(
                                to: Transform(rotation: simd_quatf(angle: .pi * 2, axis: .y)),
                                relativeTo: loadingEntity.parent,
                                duration: 2,
                                timingFunction: .linear
                            )
                            loadingEntity.repeatAnimation(.infinity)
                        } else {
                            // 加载完成后淡出移除动画
                            withAnimation(.easeOut(duration: 0.5)) {
                                loadingEntity.scale = .zero
                            } completion: {
                                loadingEntity.removeFromParent()
                            }
                        }
                    }
                    .store(in: &attachments.cancellables)
            }
            .onAppear {
                viewModel.setupPlayer(with: videoURL)
            }
        }
    }
}

关键说明

  • 使用@Observable视图模型管理播放状态,避免直接在视图中处理KVO逻辑
  • 加载动画采用RealityKit实体实现,保持沉浸式体验一致性
  • 监听AVPlayerItem.status状态,准确判断视频是否准备就绪

内容的提问来源于stack exchange,提问作者Ahsan Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:34:55