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

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

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

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

Наконец, поскольку он теперь поддерживается в iOS 11.0+ и рекомендован @mnuages ​​и другими, я попытался назначить AVPlayer напрямую для SCNMaterial.diffuse.contents, и пока он воспроизводится и не пропускает память, к сожалению, он имеет ряд плохие визуальные артефакты (даже на iOS 12.1.2). В частности, как отметили другие разработчики, 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()
            }
        })

}

}

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

Я заметил, что если бы непрозрачность узла была равна нулю, видео рендерер SceneKit ничего не делал, поэтому я изменил эту строку кода:

player.play()

добавить немного задержки:

planeNode.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
     player.play()
     self.planeNode.opacity = 1
}

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

1 ответ

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

Другие вопросы по тегам