如何在SceneKit for iOS应用程序中创建循环视频素材?

mmvthczy  于 2022-12-05  发布在  iOS
关注(0)|答案(5)|浏览(148)

bounty将在2天后过期。回答此问题可获得+100声望奖励。Fattie希望吸引更多人关注此问题。

如何在SceneKit中创建播放循环视频的素材?

m4pnthwp

m4pnthwp1#

可以在SceneKit中使用SpriteKit场景作为几何体的material来实现这一点。
下面的示例将创建一个SpriteKit场景,使用视频播放器向其中添加一个视频节点,使视频播放器循环,创建一个SceneKit场景,添加一个SceneKit平面,最后将SpriteKit场景添加为平面的漫反射材质。

import UIKit
import SceneKit
import SpriteKit
import AVFoundation

class ViewController: UIViewController, SCNSceneRendererDelegate {

    @IBOutlet weak var sceneView: SCNView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // A SpriteKit scene to contain the SpriteKit video node
        let spriteKitScene = SKScene(size: CGSize(width: sceneView.frame.width, height: sceneView.frame.height))
        spriteKitScene.scaleMode = .aspectFit

        // Create a video player, which will be responsible for the playback of the video material
        let videoUrl = Bundle.main.url(forResource: "videos/video", withExtension: "mp4")!
        let videoPlayer = AVPlayer(url: videoUrl)

        // To make the video loop
        videoPlayer.actionAtItemEnd = .none
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(ViewController.playerItemDidReachEnd),
            name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
            object: videoPlayer.currentItem)

        // Create the SpriteKit video node, containing the video player
        let videoSpriteKitNode = SKVideoNode(avPlayer: videoPlayer)
        videoSpriteKitNode.position = CGPoint(x: spriteKitScene.size.width / 2.0, y: spriteKitScene.size.height / 2.0)
        videoSpriteKitNode.size = spriteKitScene.size
        videoSpriteKitNode.yScale = -1.0
        videoSpriteKitNode.play()
        spriteKitScene.addChild(videoSpriteKitNode)

        // Create the SceneKit scene
        let scene = SCNScene()
        sceneView.scene = scene
        sceneView.delegate = self
        sceneView.isPlaying = true

        // Create a SceneKit plane and add the SpriteKit scene as its material
        let background = SCNPlane(width: CGFloat(100), height: CGFloat(100))
        background.firstMaterial?.diffuse.contents = spriteKitScene
        let backgroundNode = SCNNode(geometry: background)
        scene.rootNode.addChildNode(backgroundNode)

        ...
    }

    // This callback will restart the video when it has reach its end
    func playerItemDidReachEnd(notification: NSNotification) {
        if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
            playerItem.seek(to: kCMTimeZero)
        }
    }

    ...
}
tzdcorbm

tzdcorbm2#

Year 2019 solution:

let mat = SCNMaterial()
let videoUrl = Bundle.main.url(forResource: "YourVideo", withExtension: "mp4")!
let player = AVPlayer(url: videoUrl)
mat.diffuse.contents = player
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self,
                                       selector: #selector(playerItemDidReachEnd(notification:)),
                                       name: .AVPlayerItemDidPlayToEndTime,
                                       object: player.currentItem)
player.play()

Code for method in selector:

@objc private func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: .zero, completionHandler: nil)
    }
}

Note: for ten years now you do NOT remove notifications that run a selector. (You only need to do so in the obscure case you're using a block.)

  • If you have time-travelled to before 2015: Don't forget to remove your notification observer when the object is deallocated! Something like NotificationCenter.default.removeObserver(self,name: .AVPlayerItemDidPlayToEndTime,object: player.currentItem)*
juzqafwq

juzqafwq3#

可以使用AVPlayer作为场景背景的内容。但是,直到我将.play(nil)发送到sceneView后,它才对我起作用。

override func viewDidLoad() {
    super.viewDidLoad()

    // Set the view's delegate
    sceneView.delegate = self

    // Show statistics such as fps and timing information
    sceneView.showsStatistics = true

    // Create a new scene
    let scene = SCNScene(named: "art.scnassets/ship.scn")!

    // create and add a camera to the scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scene.rootNode.addChildNode(cameraNode)

    // Set the scene to the view
    sceneView.scene = scene

    let movieFileURL = Bundle.main.url(forResource: "example", withExtension: "mov")!
    let player = AVPlayer(url:movieFileURL)
    scene.background.contents = player
    sceneView.play(nil) //without this line the movie was not playing

    player.play()
}
1tuwyuhd

1tuwyuhd4#

2022,现在在Scene Kit中做这个很简单。请参阅Apple文档。

请注意,Apple文档明确指出,现在可以只将视频放在SCNNode上。

// make some mesh. whatever size you want.
let mesh = SCNPlane()
mesh.width = 1.77
mesh.height = 1

// put the mesh on your node
yourNode.geometry = mesh

// add the video to the mesh
plr = AVPlayer(url: "https .. .m4v")
yourNode.geometry?.firstMaterial?.diffuse.contents = plr

注意,你可以在网格上放置任何你想要的东西。("几何"就是网格。)这很简单。例如,如果你只想要一个普通的颜色:

... firstMaterial?.diffuse.contents = UIColor.yellow

注意,这个问题问的是关于循环播放视频的问题。这是一个无关紧要的问题,与使用SceneKit无关。你可以看到上百万个关于循环播放视频的问题,很简单:

NotificationCenter.default.addObserver(self,
  selector: #selector(loopy),
  name: .AVPlayerItemDidPlayToEndTime,
  object: plr.currentItem)

然后再

@objc func loopy() { plr.seek(to: .zero) }
dsekswqp

dsekswqp5#

雨燕5.7

1. SceneKit + SpriteKit的循环视频素材

...工作正常-视频无缝循环...

我在macOS Ventura 13.0.1上的Xcode 14.1模拟器(iOS 16.1)上测试了这个应用程序。对于视频纹理,我使用了带有H.264编解码器的QuickTime 1600x900 .mov文件。

import SceneKit
import AVFoundation
import SpriteKit

class GameViewController: UIViewController {
    
    var sceneView: SCNView? = nil
    private var avPlayer: AVQueuePlayer? = nil
    private var looper: AVPlayerLooper? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.sceneView = self.view as? SCNView
        guard let scene = SCNScene(named: "tv.scn") else { return }
        sceneView?.scene = scene
        sceneView?.allowsCameraControl = true
        
        self.spriteKitScene(self.sceneKitNode())
    }

    private func spriteKitScene(_ node: SCNNode) { ... }           // A

    internal func sceneKitNode() -> SCNNode { ... }                // B
    
    fileprivate func loadVideoMaterial() -> AVPlayer? { ... }      // C
}

SpriteKit场景能够播放.mov视频文件:

private func spriteKitScene(_ node: SCNNode) {
    
    let screenGeo: SCNPlane = node.geometry as! SCNPlane

    let videoNode = SKVideoNode(avPlayer: self.loadVideoMaterial()!)

    let skScene = SKScene(size: CGSize(width: screenGeo.width * 1600,
                                      height: screenGeo.height * 900))
    
    videoNode.position = CGPoint(x: skScene.size.width / 2,
                                 y: skScene.size.height / 2)
    
    videoNode.size = skScene.size
    skScene.addChild(videoNode)
    
    let screenMaterial = screenGeo.materials.first
    screenMaterial?.diffuse.contents = skScene
    videoNode.play()
    sceneView?.scene?.rootNode.addChildNode(node)
}

SceneKit的材质用作SpriteKit视频的媒介:

internal func sceneKitNode() -> SCNNode {
    
    if let screen = sceneView?.scene?.rootNode.childNode(withName: "screen",
                                                      recursively: false) {
        
        screen.geometry?.firstMaterial?.lightingModel = .constant
        screen.geometry?.firstMaterial?.diffuse.contents = UIColor.black
        return screen
    }
    return SCNNode()
}

最后,用于加载视频的方法包含一个AVPlayerLooper对象:

fileprivate func loadVideoMaterial() -> AVPlayer? {
    
    guard let path = Bundle.main.path(forResource: "video", ofType: "mov")
    else { return nil }
    
    let videoURL = URL(fileURLWithPath: path)
    let asset = AVAsset(url: videoURL)
    let item = AVPlayerItem(asset: asset)
    self.avPlayer = AVQueuePlayer(playerItem: item)

    if let avPlayer {
        avPlayer.isMuted = true
        self.looper = AVPlayerLooper(player: avPlayer, templateItem: item)
        return avPlayer
    }
    return AVPlayer()
}

2. Pure SceneKit的循环视频素材

...工作不正常-视频循环延迟...

您可以采取更短的路线来解决此任务,但问题是,该方法无法正常工作-视频循环播放的延迟等于整个视频的持续时间。

import SceneKit
import AVFoundation

class GameViewController: UIViewController {
    
    var sceneView: SCNView? = nil
    private var avPlayer: AVQueuePlayer? = nil
    private var looper: AVPlayerLooper? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.sceneView = self.view as? SCNView
        sceneView?.isPlaying = true               // if view is playing?
        guard let scene = SCNScene(named: "tv.scn") else { return }
        sceneView?.scene = scene
        sceneView?.allowsCameraControl = true
        
        self.loadModelWithVideoMaterial()
    }

    fileprivate func loadModelWithVideoMaterial() { ... }
}

下面我们将AVQueuePlayer对象分配给材质的内容:

fileprivate func loadModelWithVideoMaterial() {
    
    guard let path = Bundle.main.path(forResource: "video", ofType: "mov")
    else { return }
    
    let videoURL = URL(fileURLWithPath: path)
    let asset = AVAsset(url: videoURL)
    let item = AVPlayerItem(asset: asset)
    self.avPlayer = AVQueuePlayer(playerItem: item)

    if let avPlayer {
        avPlayer.isMuted = true

        guard let screen = sceneView?.scene?.rootNode.childNode(
                                                       withName: "screen",
                                                    recursively: true)
        else { return }
            
        screen.geometry?.firstMaterial?.lightingModel = .constant
        screen.geometry?.firstMaterial?.diffuse.contents = avPlayer
        sceneView?.scene?.rootNode.addChildNode(screen)
        
        self.looper = AVPlayerLooper(player: avPlayer, templateItem: item)
        avPlayer.playImmediately(atRate: 20)      // speed x20 for testing
    }
}

相关问题