Show Loading Animation in immersive Space until Av Player plays video in Vision OS

190 Views Asked by At

show loading animation in immersive view until av player loads videos from a url also is there any way to pass dynamic url to this view since it is places inside app struct which gets initialised when app gets loads into memory

struct ImmersiveMeditationPlayerView: View {
    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
    @Environment(\.openWindow) private var openWindow
    let player = AVPlayer()
    
    var body: some View {
        VStack {
            RealityView { content, attachments in
                //Create Entity for the video
                let videoEntity = Entity()
                let url = URL(string: "https://somevideo/135-87.m3u8")!
                let asset = AVURLAsset(url: url)
                let playerItem = AVPlayerItem(asset: asset)
               
                 //create a videoMaterial
                let material = VideoMaterial(avPlayer: player)
                
                //Made a Sphere with the videoEntity and asign the videoMaterial to it
                videoEntity.components.set(ModelComponent(mesh: .generateSphere(radius: 1E3), materials: [material]))
                
                //adjust the properties of the videoEntity(Sphere) if needed
                videoEntity.scale = .init(x: 1, y: 1, z: -1)
                videoEntity.transform.translation += SIMD3<Float>(0.0, 10.0, 0.0)
                
                let angle = Angle.degrees(90)
                let rotation = simd_quatf(angle: Float(angle.radians), axis: .init(x: 0, y: 0, z: 0))
                
                videoEntity.transform.rotation = rotation
                
                //add VideoEntity to realityView
                content.add(videoEntity)
                
                //start the VideoPlayer
                player.replaceCurrentItem(with: playerItem)
                player.play()
            }
}

result displaying immersive view for Home Tab

1

There are 1 best solutions below

2
lorem ipsum On

You can do it via

RealityView<A>(make: (inout RealityViewContent, RealityViewAttachments) async -> Void, update: ((inout RealityViewContent, RealityViewAttachments) -> Void)?, attachments: () -> A)

and making use of the update for adding/removing the ProgressView and attachments to define the ProgressView.

struct ImmersiveMeditationPlayerView: View {
    //Get a dynamic URL
    @Binding var url: URL
    @State private var videoHelper: PlayerHelper = .init()
    @State private var isReady: Bool = false
    var body: some View {
        VStack {
            RealityView { content, attachments in
                content.add(videoHelper.videoEntity)
            } update: { content, attachments in
                switch isReady {
                case true:
                    if let progress = attachments.entity(for: ViewEntity.progress){
                        
                        videoHelper.videoEntity.removeChild(progress)
                    }
                case false:
                    if let progress = attachments.entity(for: ViewEntity.progress){
                        progress.position = [-0.5,0,0]
                        videoHelper.videoEntity.addChild(progress)
                    }
                }
                
            } attachments: {
                Attachment(id: ViewEntity.progress) {
                    ProgressView("Preparing Video")
                }
            }
            //Play the video and get status updates when url changes
            .task(id: url) {
                isReady = false
                try? await Task.sleep(for: .seconds(3))
                videoHelper.playURL(url: url)
                for await status in videoHelper.player.listenStatus() {
                    isReady = status
                }
            }
        }
    }
    enum ViewEntity: String {
        case progress
    }
}

I defined the "Helper" as follows.

@Observable
final class PlayerHelper {
    //Put the player somehwere that can be accessed by both the make and update
    let player: AVPlayer
    //Put the entity somehwere that can be accessed by both the make and update
    let videoEntity: Entity

    init() {
        //Set player
        let player = AVPlayer()
        self.player = player
        //SetEntity
        videoEntity = {
            //Create Entity for the video
            let videoEntity = Entity()
            //create a videoMaterial
            let material = VideoMaterial(avPlayer: player)
            
            //Made a Sphere with the videoEntity and asign the videoMaterial to it
            videoEntity.components.set(ModelComponent(mesh: .generateSphere(radius: 0.3), materials: [material]))
            
            //adjust the properties of the videoEntity(Sphere) if needed
            videoEntity.scale = .init(x: 0.5, y: 0.5, z: 0.5)
            //videoEntity.transform.translation += SIMD3<Float>(0.0, 10.0, 0.0)
            
            //let angle = Angle.degrees(90)
            //let rotation = simd_quatf(angle: Float(angle.radians), axis: .init(x: 0, y: 0, z: 0))
            
            //videoEntity.transform.rotation = rotation
            return videoEntity
        }()
        
        
    }
    //Update player
    func playURL(url: URL) {
        player.rate = 0
        let asset = AVURLAsset(url: url)
        let playerItem = AVPlayerItem(asset: asset)
        //start the VideoPlayer
        player.replaceCurrentItem(with: playerItem)
        player.play()
    }
    
}

And the rest of the code you'll need is just some convenience code.

#Preview {
    ImmersiveMeditationPlayerView(url: .constant(URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!))
}

extension AVPlayer {
    func listenStatus() -> AsyncStream<Bool> {
        AsyncStream  { continuation in
            continuation.yield(rate != 0)
            let observation = self.observe(\.rate) { player, newStatus in
                print(" kvo")
                continuation.yield(newStatus.newValue != 0)
            }
            continuation.onTermination = { _ in
                observation.invalidate()
                
            }
        }
    }
}