英文:
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
继承的类的标准方法,请确保您的类符合这一要求。还要记住在不再需要时删除观察者。
此外,为了观察变化,您还需要保持对 AVPlayerItem
和 AVPlayer
的强引用。这可能需要进行一些架构更改(向您的类添加属性)。
这个解决方案应该为您提供一个大致的方向,但您可能需要根据您的特定项目设置和要求进行调整。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论