在Swift和RealityKit中,后台加载AVAsset视频并在可播放时替换播放占位视频。

huangapple go评论62阅读模式
英文:

Load AVAsset video in the background and replace playing placeholder video once it's playable in Swift and RealityKit

问题

我使用以下代码创建一个视频播放器,用于在AR会话中检测到的参考图像上播放视频。目前我显示一个占位视频,然后在1秒后切换到我想播放的实际视频。然而,我想在实际视频准备好播放之前显示占位视频。

我尝试使用AVAsset进行实验,并根据此链接观察基于可播放状态:https://stackoverflow.com/questions/5401437/knowing-when-avplayer-object-is-ready-to-play - 但我没有任何成功。

func createVideoNode(_ target: ARReferenceImage) -> ModelEntity {
    var videoPlane = ModelEntity()
    var targetName: String = ""
    
    if let name = target.name,
       let validURL = URL(string: "https://testdomain.com/\(name).mp4") {
        targetName = name

        // 使用预加载的占位符资源创建AVPlayer
        if let placeholderAsset = parent.placeholderAsset {
            let placeholderPlayer = AVPlayer(playerItem: AVPlayerItem(asset: placeholderAsset))
            let videoMaterial = VideoMaterial(avPlayer: placeholderPlayer)
            videoPlane = ModelEntity(mesh: .generatePlane(width: Float(target.physicalSize.width), depth: Float(target.physicalSize.height)), materials: [videoMaterial])
            placeholderPlayer.play()

            DispatchQueue.global(qos: .background).async {
                let videoPlayer = AVPlayer(url: validURL)
                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem, queue: .main) { [weak videoPlayer] _ in
                    videoPlayer?.seek(to: CMTime.zero)
                    videoPlayer?.play()
                }
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    let videoMaterial = VideoMaterial(avPlayer: videoPlayer)
                    videoPlane.model?.materials = [videoMaterial]
                    videoPlayer.play()
                    self.parent.videoPlayers[targetName] = videoPlayer
                    print (target.name as Any)
                }
            }
        } else {
            fatalError("Failed to load placeholder video asset.")
        }
    }

    return videoPlane
}
英文:

I'm using the following code to create a video player for detected reference images in AR session. Currently I display a placeholder video and after 1 second switch to real video that I want played. However, I would like to show the placeholder video until the real video is ready to be played.

I tried experimenting with AVAsset and observing the playable status based on this: https://stackoverflow.com/questions/5401437/knowing-when-avplayer-object-is-ready-to-play - however I didn't have any success.

        func createVideoNode(_ target: ARReferenceImage) -> ModelEntity {
        var videoPlane = ModelEntity()
        var targetName: String = ""
        
        if let name = target.name,
           let validURL = URL(string: "https://testdomain.com/\(name).mp4") {
            targetName = name

            // Use the preloaded placeholder asset to create an AVPlayer
            if let placeholderAsset = parent.placeholderAsset {
                let placeholderPlayer = AVPlayer(playerItem: AVPlayerItem(asset: placeholderAsset))
                let videoMaterial = VideoMaterial(avPlayer: placeholderPlayer)
                videoPlane = ModelEntity(mesh: .generatePlane(width: Float(target.physicalSize.width), depth: Float(target.physicalSize.height)), materials: [videoMaterial])
                placeholderPlayer.play()

                DispatchQueue.global(qos: .background).async {
                    let videoPlayer = AVPlayer(url: validURL)
                    NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem, queue: .main) { [weak videoPlayer] _ in
                        videoPlayer?.seek(to: CMTime.zero)
                        videoPlayer?.play()
                    }
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                        let videoMaterial = VideoMaterial(avPlayer: videoPlayer)
                        videoPlane.model?.materials = [videoMaterial]
                        videoPlayer.play()
                        self.parent.videoPlayers[targetName] = videoPlayer
                        print (target.name as Any)
                    }
                }
            } else {
                fatalError("Failed to load placeholder video asset.")
            }
        }

        return videoPlane
    }

答案1

得分: 2

解决此问题的关键是确保 AVPlayer 的项目在切换视频之前确实准备好播放。您可以使用AVPlayerItem的 status 属性上的键-值观察(KVO)来在它准备好播放时收到通知。

这是更新后的 createVideoNode(_:) 函数:

func createVideoNode(_ target: ARReferenceImage) -> ModelEntity {
    var videoPlane = ModelEntity()
    var targetName: String = ""
    
    if let name = target.name,
       let validURL = URL(string: "https://testdomain.com/\(name).mp4") {
        targetName = name

        // 使用预加载的占位符资源来创建AVPlayer
        if let placeholderAsset = parent.placeholderAsset {
            let placeholderPlayer = AVPlayer(playerItem: AVPlayerItem(asset: placeholderAsset))
            let videoMaterial = VideoMaterial(avPlayer: placeholderPlayer)
            videoPlane = ModelEntity(mesh: .generatePlane(width: Float(target.physicalSize.width), depth: Float(target.physicalSize.height)), materials: [videoMaterial])
            placeholderPlayer.play()

            DispatchQueue.global(qos: .background).async {
                let asset = AVAsset(url: validURL)
                let playerItem = AVPlayerItem(asset: asset)
                let videoPlayer = AVPlayer(playerItem: playerItem)

                // 观察playerItem的状态。
                playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)

                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem, queue: .main) { [weak videoPlayer] _ in
                    videoPlayer?.seek(to: CMTime.zero)
                    videoPlayer?.play()
                }

                self.parent.videoPlayers[targetName] = videoPlayer
            }
        } else {
            fatalError("加载占位符视频资源失败。")
        }
    }

    return videoPlane
}

// 添加此方法以处理观察到的值的变化
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "status" {
        if let playerItem = object as? AVPlayerItem, playerItem.status == .readyToPlay {
            DispatchQueue.main.async { [weak self] in
                if let videoPlane = self?.videoPlane {
                    let videoMaterial = VideoMaterial(avPlayer: playerItem.player)
                    videoPlane.model?.materials = [videoMaterial]
                    playerItem.player?.play()
                }
            }
        }
    }
}

此函数的这个版本现在使用AVAsset创建了一个AVPlayerItem。然后它将ViewController添加为playerItem的状态属性的观察者。当状态为 .readyToPlay 时,observeValue(forKeyPath:of:change:context:) 方法将在主队列上调用,然后切换视频。

请注意,observeValue 方法是从 NSObject 继承的类的标准方法,请确保您的类符合这一要求。还要记住在不再需要时删除观察者。

此外,为了观察变化,您还需要保持对 AVPlayerItemAVPlayer 的强引用。这可能需要进行一些架构更改(向您的类添加属性)。

这个解决方案应该为您提供一个大致的方向,但您可能需要根据您的特定项目设置和要求进行调整。

英文:

The key to resolving this issue is making sure the AVPlayer's item is actually ready to play before switching the video. You can use the Key-Value Observing (KVO) on the AVPlayerItem's status property to get notified when it's ready to play.

Here is the updated createVideoNode(_:) function:

func createVideoNode(_ target: ARReferenceImage) -> ModelEntity {
    var videoPlane = ModelEntity()
    var targetName: String = ""
    
    if let name = target.name,
       let validURL = URL(string: "https://testdomain.com/\(name).mp4") {
        targetName = name

        // Use the preloaded placeholder asset to create an AVPlayer
        if let placeholderAsset = parent.placeholderAsset {
            let placeholderPlayer = AVPlayer(playerItem: AVPlayerItem(asset: placeholderAsset))
            let videoMaterial = VideoMaterial(avPlayer: placeholderPlayer)
            videoPlane = ModelEntity(mesh: .generatePlane(width: Float(target.physicalSize.width), depth: Float(target.physicalSize.height)), materials: [videoMaterial])
            placeholderPlayer.play()

            DispatchQueue.global(qos: .background).async {
                let asset = AVAsset(url: validURL)
                let playerItem = AVPlayerItem(asset: asset)
                let videoPlayer = AVPlayer(playerItem: playerItem)

                // Observe the status of playerItem.
                playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)

                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem, queue: .main) { [weak videoPlayer] _ in
                    videoPlayer?.seek(to: CMTime.zero)
                    videoPlayer?.play()
                }

                self.parent.videoPlayers[targetName] = videoPlayer
            }
        } else {
            fatalError("Failed to load placeholder video asset.")
        }
    }

    return videoPlane
}

// Add this method to handle observed value change
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "status" {
        if let playerItem = object as? AVPlayerItem, playerItem.status == .readyToPlay {
            DispatchQueue.main.async { [weak self] in
                if let videoPlane = self?.videoPlane {
                    let videoMaterial = VideoMaterial(avPlayer: playerItem.player)
                    videoPlane.model?.materials = [videoMaterial]
                    playerItem.player?.play()
                }
            }
        }
    }
}

This version of the function now creates an AVPlayerItem using the AVAsset. It then adds the ViewController as an observer of the playerItem's status property. The observeValue(forKeyPath:of:change:context:) method gets called when the status changes. When the status is .readyToPlay, it switches the video on the main queue.

Please note that the observeValue method is a standard method for classes that inherit from NSObject, make sure your class does that. Also remember to remove the observer when it's no longer needed.

You will also have to hold a strong reference to your AVPlayerItem and AVPlayer in order to observe changes. This might necessitate some architectural changes (adding properties to your class).

This solution should give you a general direction, but you might need to adjust it to fit your specific project setup and requirements.

huangapple
  • 本文由 发表于 2023年6月2日 02:59:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76384930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定