Search code examples
iosswiftscenekit

How do I create a looping video material in SceneKit for iOS app?


How do I create a material in SceneKit that plays a looping video?


Solution

  • It's possible to achieve this in SceneKit using a SpriteKit scene as the geometry's material.

    The following example will create a SpriteKit scene, add a video node to it with a video player, make the video player loop, create a SceneKit scene, add a SceneKit plane, and finally add the SpriteKit scene as the plane's diffuse material.

    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)
            }
        }
    
        ...
    }