0

I'm playing around with ARKit and have created an app which overlays video content onto recognized images. Code is as follows:

import UIKit
import SceneKit
import ARKit
import SpriteKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!

    override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.delegate = self

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let configuration = ARImageTrackingConfiguration()

        if let imageToTrack = ARReferenceImage.referenceImages(inGroupNamed: "ALLimages", bundle: Bundle.main) {

            configuration.trackingImages = imageToTrack

            configuration.maximumNumberOfTrackedImages = 3

            print("Images successfully added")

        }

        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        sceneView.session.pause()
    }

    // MARK: - ARSCNViewDelegate

    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()

        var videoNode = SKVideoNode()

        var videoScene = SKScene()

        if let imageAnchor = anchor as? ARImageAnchor {


            videoNode = SKVideoNode(fileNamed: "video1.mp4")

            videoNode.play()

            videoScene = SKScene(size: CGSize(width: 640, height: 360))

            videoNode.position = CGPoint(x: videoScene.size.width / 2, y: videoScene.size.height / 2)

            videoNode.yScale = -1.0

            videoScene.addChild(videoNode)

            let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)

             plane.firstMaterial?.diffuse.contents = videoScene

            let planeNode = SCNNode(geometry: plane)

            planeNode.eulerAngles.x = -.pi/2

            node.addChildNode(planeNode)

        }
        return node
    }
}

This works perfectly... once! But the problem is that once the video has finished playing, because of the limited controls available via SKVideoNode I cannot figure out how to restart the video automatically. Ideally these should play on a loop.

I've done some research and it seems that the best way is to initialize my video node using an AVPlayer object.

So, I attempted to do this but cannot get it working.

I added var player = AVPlayer() in my class and then tried to initialize my videoNode as follows:

var videoNode: SKVideoNode? = {
    guard let urlString = Bundle.main.path(forResource: "video1", ofType: "mov") else {
        return nil
    }

    let url = URL(fileURLWithPath: urlString)
    let item = AVPlayerItem(url: url)
    let player = AVPlayer(playerItem: item)

    return SKVideoNode(avPlayer: player)
}()

I then attempted to user player.play() but the video never plays. Instead my plane just appears as a blank rectangle over my images.

Once I successfully initialize this I think I'm able to add an observer to check when the video has finished player and restart it, but I'm struggling to get to that point.

DevB1
  • 1,235
  • 3
  • 17
  • 43

1 Answers1

1

First, you don't need SKVideoNode in a SKScene in a SCNNode. You can directly use AVPlayer as diffuse content of you SCNNode : plane.firstMaterial?.diffuse.contents = player

For looping you have to subscribe to a notification on the player and reset time to zero at end :

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { _ in
    player.seek(to: .zero)
    player.play()
}
ryancrunchi
  • 465
  • 2
  • 16
  • Excellent thank you. One problem I'm running into is that I'm using the AVPlayer object each time an image is recognised. When I add the observer, it only runs the player.seek(to: .zero) on the first image recognised. It's weird because I added a print statement within that closure : print("Video finished") and this does print each time any one of the three videos finishes, but it won't run the seek to zero on more than one... – DevB1 Dec 04 '19 at 20:17
  • Just to add a little more, I added another print statement after print("Video finished") as follows: print("VIDEO TITLE: \(self.player.currentItem?.asset)") and indeed it is always the same item being printed, so self.player here seems always to be referring to the first video played. But I'm confused then why in the same closure it is successfully printing 'Video finished' when the other videos finish playing – DevB1 Dec 04 '19 at 21:12
  • So I figured I obviously need to have 3 separate AVPlayers() in order to control these individually - but now I seem to be in a position whereby all three videos get reset to zero and play again once the first video finishes... seems strange as I have 3 scenarios now wrapped in 3 separate if statements relating to the name of the image recognised. – DevB1 Dec 04 '19 at 22:24