[英]What's the best way to play video in SceneKit?
我已经尝试了许多不同的技术来在SceneKit中播放视频,但它们似乎都没有正常工作。
我已经尝试将AVPlayerLayer分配给SCNMaterial.diffuse.contents并且根本无法显示任何视频帧,尽管声音播放正常。
我还尝试将包含SKVideoNode子节点的SpriteKite SKScene分配给SCNMaterial.diffuse.contents,虽然( 如StackOverflow上的其他人所述 )它每次播放视频时都会泄漏兆字节的内存,直到最后iOS杀死了应用程序。
最后,由于它现在支持iOS 11.0+并由@mnuages和其他人推荐,我已经尝试将AVPlayer直接分配给SCNMaterial.diffuse.contents,虽然它播放并且不会泄漏内存,但不幸的是它有一些糟糕的视觉文物(即使在iOS 12.1.2上)。 特别是, 正如其他开发人员所指出的 ,每次将AVPlayer实例分配给一个素材并开始播放时,SceneKit会将以下多次记录到控制台:
[SceneKit]错误:无法获取像素缓冲区(CVPixelBufferRef)
日志消息可能是适合居住的,除了每个错误之外,场景中的视频帧都显示为全白色,这会在每次播放新视频时闪烁几分之一秒。
这种技术的更糟糕的视觉效果就是我称之为“迷幻彩虹”的效果,它将AVPlayer随机添加到相邻节点,同时将AVPlayer分配给素材(无论是播放还是暂停)。 它看起来很酷(参见随附的屏幕截图),但不太适合我们的应用程序。
有谁知道如何使这些技术之一可靠地工作?
以下是我用于测试直接将AVPlayer分配给素材的一些代码:
class GameViewController: UIViewController {
var plane: SCNPlane!
var planeNode: SCNNode!
var playerObserver: NSKeyValueObservation?
var playerCompletionObserver: NSObjectProtocol?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let scnView = self.view as? SCNView else {return}
guard let scene = SCNScene(named: "art.scnassets/scene.scn") else {return}
scnView.scene = scene
plane = SCNPlane(width: 10, height: 10)
planeNode = SCNNode(geometry: plane)
planeNode.position.y = 10.0
scene.rootNode.addChildNode(planeNode)
scnView.isPlaying = true
scnView.loops = true
addPlayer()
}
func addPlayer() {
guard let url = Bundle.main.url(forResource: "Clover", withExtension: "mov") else {return}
let asset = AVURLAsset(url: url)
let playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: [#keyPath(AVAsset.tracks), #keyPath(AVAsset.duration)])
let player = AVPlayer(playerItem: playerItem)
print("\(url.lastPathComponent) \(player.error?.localizedDescription ?? "loaded")")
playerObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: {[weak self] (playerItem, change) in
guard let strongSelf = self else {return}
if playerItem.status == .readyToPlay {
strongSelf.playerObserver?.invalidate()
strongSelf.playerObserver = nil
if let material = strongSelf.plane.firstMaterial {
material.diffuse.contents = player
let videoSize = playerItem.asset.tracks(withMediaType: .video).first?.naturalSize ?? CGSize(width: 640, height: 480)
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(Float(videoSize.height / videoSize.width), 1, 1), 0.25, 0, 0)
material.isDoubleSided = true
}
strongSelf.playerCompletionObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem, queue: nil) {[weak self](Notification) in
guard let strongSelf = self else {return}
NotificationCenter.default.removeObserver(strongSelf.playerCompletionObserver!)
strongSelf.addPlayer()
}
player.play()
}
})
}
}
更新 :我现在尝试了数十种automaticLoadedAssetKeys和.preroll的组合,但到目前为止,他们都没有采取任何措施来最小化白色闪烁问题。 然而,我做了一些几乎消除了问题的东西。
我注意到如果节点的不透明度为零,则SceneKit视频渲染器将不会执行任何操作,因此我更改了以下代码行:
player.play()
添加一点延迟:
planeNode.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
player.play()
self.planeNode.opacity = 1
}
像这样添加延迟对我来说似乎总是一个黑客,但我还没有找到任何其他可行的东西。 Doe有人有更好的解决方案吗?
我使用了SKVideoNode和SKScene然后设置了plane.firstMaterial?.diffuse.contents = skScene。 我还为AVPlayer设置了一个观察者,当它播放到结束时间时,它将寻找开始并再次播放。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.