在 SceneKit 中播放视频的最佳方式是啥?

Posted

技术标签:

【中文标题】在 SceneKit 中播放视频的最佳方式是啥?【英文标题】:What's the best way to play video in SceneKit?在 SceneKit 中播放视频的最佳方式是什么? 【发布时间】:2019-05-19 11:46:25 【问题描述】:

我尝试了多种不同的技术在 SceneKit 中播放视频,但似乎都无法正常工作。

我尝试将 AVPlayerLayer 分配给 SCNMaterial.diffuse.contents 并且根本无法显示任何视频帧,尽管声音播放正常。

我还尝试将包含 SKVideoNode 子节点的 SpriteKite SKScene 分配给 SCNMaterial.diffuse.contents,并且效果很好,尽管 (as noted by others on ***) 每次播放视频时都会泄漏兆字节的内存,直到最后 ios杀死应用程序。

最后,由于它现在在 iOS 11.0+ 中得到支持并且得到了@mnuages 和其他人的推荐,我尝试将 AVPlayer 直接分配给 SCNMaterial.diffuse.contents 并且虽然它可以播放并且不会泄漏内存,但不幸的是它有一些糟糕的视觉伪像(即使在 iOS 12.1.2 上)。特别是as other developers have noted,每次将 AVPlayer 实例分配给材质并开始播放时,SceneKit 都会多次将以下内容记录到控制台:

[SceneKit] 错误:无法获取像素缓冲区 (CVPixelBufferRef)

日志消息可能是可存活的,除了每个错误,场景中的视频帧都显示为全白,这会导致每次播放新视频时都会闪烁几分之一秒。

这种技术的一个更糟糕的视觉伪影是我称之为“迷幻彩虹”效果的东西.它看起来很酷(请参阅随附的屏幕截图),但不是我们想要的应用程序。

有谁知道如何使这些技术之一可靠地工作?

这是我用来测试将 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

像这样添加延迟对我来说似乎总是一种 hack,但我还没有找到任何其他有效的方法。谁有更好的解决方法?

【问题讨论】:

【参考方案1】:

我使用了 SKVideoNode 和 SKScene,然后设置了 plane.firstMaterial?.diffuse.contents = skScene。我还为 AVPlayer 设置了一个观察者,当它播放到结束时间时,它会寻找开始并再次播放。

【讨论】:

以上是关于在 SceneKit 中播放视频的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

创建一个既能录制又能播放本地视频的 iPad 应用程序的最佳技术是啥?

在我的 android 程序中播放 swf 文件(flash)的最佳方式是啥?

在视频列表中静音/取消静音的最佳方法是啥

DMP是啥意思

使用 angularjs 播放 rtmp 流的最佳方法是啥?

电影播放器排行榜是啥样的?