Как лучше всего воспроизводить видео в SceneKit?

Я пробовал несколько различных методов воспроизведения видео в SceneKit, но ни один из них, похоже, не работает правильно.

Я попытался назначить AVPlayerLayer для SCNMaterial.diffuse.contents, и он просто не может отображать какие-либо видеокадры вообще, хотя звуки воспроизводятся нормально.

Я также пробовал назначить SpriteKite SKScene, содержащий дочерний узел SKVideoNode, для SCNMaterial.diffuse.contents, и это, тем не менее, работает хорошо (, как отметили другие в StackOverflow), при каждом воспроизведении видео происходит утечка мегабайтов памяти, пока, наконец, iOS не завершит работу приложения.

Наконец, поскольку теперь он поддерживается в iOS 11.0+ и рекомендован @mnuages ​​и другими, я попытался назначить AVPlayer непосредственно для SCNMaterial.diffuse.contents, и хотя он воспроизводится и не утекает память, к сожалению, у него есть несколько плохие визуальные артефакты (даже на iOS 12.1.2). В частности, как отметили другие разработчики < / strong>, SceneKit записывает на консоль следующее множество раз каждый раз, когда экземпляр AVPlayer назначается материалу и он начинает воспроизведение:

[SceneKit] Ошибка: не удалось получить буфер пикселей (CVPixelBufferRef)

Сообщения журнала могут быть сохранены, за исключением того, что при каждой ошибке видеокадр в сцене отображается полностью белым, что вызывает мигание в течение доли секунды при каждом воспроизведении нового видео.

Худшим визуальным артефактом этой техники является то, что я называю эффектом «психоделической радуги», который случайным образом добавляет сверкающий цветовой шумовой узор к соседним узлам, в то время как 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
}

Добавление таких задержек всегда кажется мне хитростью, но я просто не нашел ничего, что бы вообще работало. У кого-нибудь есть исправление получше?

Визуальные артефакты SceneKit


person LenK    schedule 19.12.2018    source источник


Ответы (1)


Я использовал SKVideoNode и SKScene, затем установил plane.firstMaterial? .Diffuse.contents = skScene. Я также установил наблюдателя для AVPlayer, когда он играет до конца времени, он будет искать начало и играть снова.

person Quang Dung Pham    schedule 20.02.2019