web-dev-qa-db-fra.com

ARKit SKVideoNode jouant sur le rendu

Problème principal:

J'ajoute cette section APRÈS pour clarifier le problème. - Je peux suspendre ma vidéo (je ne veux pas qu'elle joue en boucle). Lorsque mon nœud apparaît, mon nœud lit ma vidéo, même si elle est en pause. Si ma vidéo a fini de jouer et qu'elle apparaît, elle redémarrera. Je veux SUPPRIMER ce comportement.

Dans mon application, j'ai un SKVideoNode créé à partir d'une AVPlayer(:URL) dans l'espace 3D à l'aide d'objets SCNNode et SCNGeometry objets. J'utilise ARKit.ImageTracking Pour déterminer quand une image spécifique est trouvée et lire une vidéo à partir de là. Tout est bon et dandy, sauf que le joueur décide de jouer sur son propre temps, chaque fois que l'AVPlayer entre en vue; cependant, cela pourrait se produire chaque fois que le ARImageAnchor auquel le SCNNode est attaché apparaît. Dans les deux cas, le AVPlayer joue à chaque fois que le nœud entre en vue de l'objectif de la caméra. j'utilise

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if(keyPath == "rate") {
        print((object as! AVPlayer).rate)
    }
}

pour imprimer le taux, et il est 1, cependant, il était 0.

J'ai imprimé une sorte de fonction d'impression print("Play") pour toutes mes fonctions en utilisant player.pause () ou player.play () et aucune d'entre elles n'est appelée chaque fois que le taux est modifié ci-dessus. Comment puis-je trouver la source de ce qui change le taux de mon lecteur?

J'ai vérifié le nœud racine d'origine, self.sceneview.scene.rootNode.childNodes Pour m'assurer que je ne crée pas de VideoNodes/SCNNodes/AVPlayers supplémentaires, etc., et il semble qu'il n'y en ait qu'un.

Avez-vous des idées sur la raison pour laquelle le SKVideoNode/AVPlayer joue alors que le SCNNode arrive à la vue de la caméra utilisant ARKit? Merci d'avance!

Edit1:

Solution de contournement, pour déterminer UNIQUEMENT lorsqu'un utilisateur a cliqué sur ce nœud

let tap = UITapGestureRecognizer(target: self, action: #selector(self!.tapGesture))
tap.delegate = self!
tap.name = "MyTap"
self!.sceneView.addGestureRecognizer(tap)

puis à l'intérieur de cette fonction suivante, je mets

@objc func tapGesture(_ gesture:UITapGestureRecognizer) {
    let tappedNodes = self.sceneView.hitTest(gesture.location(in: gesture.view), options: [SCNHitTestOption.searchMode: 1])

    if !tappedNodes.isEmpty {
        for nodes in tappedNodes {
            if nodes.node == videoPlayer3D {
                videoPlayer3D.tappedVideoPlayer = true
                videoPlayer3D.playOrPause()
                break
            }
        }
    }
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if(keyPath == "rate") {
        print((object as! AVPlayer).rate)
        if(!self.tappedVideoPlayer) {
            self.player.pause() //HERE
        }
    }
}

où videoPlayer3D est le SCNNode qui contient le SKVideoNode.

Cependant, j'obtiens l'erreur com.Apple.scenekit.scnview-renderer (17): EXC_BAD_ACCESS (code=2, address=0x16d8f7ad0) dans la section intitulée "ICI" ci-dessus. Il semble que le moteur de rendu de la vue tente de modifier mon nœud vidéo dans la fonction de rendu, bien que je n'utilise même pas la fonction renderer(updateAtTime:), j'utilise uniquement

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    guard let imageAnchor = anchor as? ARImageAnchor else { return }

    createVideoNode(imageAnchor)

}

pour déterminer quand je vois une image, c'est-à-dire imageTracking et je crée le nœud. Des conseils?

Pensée 1

L'erreur est présentée indiquant qu'une méthode est appelée à partir d'un objet SCNView pour le rendu de méthode (c'est ce que je comprends de l'erreur), mais je n'ai pas le nœud spécifiquement appelé. Je pense que peut-être une action par défaut, lorsque le nœud vient de s'afficher, est appelée, cependant, je ne suis pas sûr à 100% sur la façon d'y accéder ou de déterminer quelle méthode. Les objets que j'utilise ne sont pas des objets SCNView, et je ne crois pas qu'ils héritent des objets SCNView (regardez le 1er paragraphe pour voir les variables utilisées). Je cherche juste à supprimer "l'action" du nœud qui joue à chaque fois qu'il est en vue.

AJOUT:

Pour suivre la création de mon lecteur vidéo si vous êtes intéressé, le voici. Faites-moi savoir s'il y a autre chose que vous aimeriez voir (je ne sais pas quoi d'autre vous voudriez voir) et merci pour votre aide.

func createVideoNode(_ anchor:ARImageAnchor, initialPOV:SCNNode) -> My3DPlayer? {

    guard let currentFrame = self.sceneView.session.currentFrame else {
        return nil
    }

    let delegate = UIApplication.shared.delegate as! AppDelegate

    var videoPlayer:My3DPlayer!
    videoPlayer = delegate.testing ? My3DPlayer(data: nil, currentFrame: currentFrame, anchor: anchor) : My3DPlayer(data: self.urlData, currentFrame: currentFrame, anchor: anchor)

    //Create TapGesture
    let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture))
    tap.delegate = self
    tap.name = "MyTap"
    self.sceneView.addGestureRecognizer(tap)

    return videoPlayer
}

Classe My3dPlayer:

class My3DPlayer: SCNNode {

    init(geometry: SCNGeometry?) {
        super.init()
        self.geometry = geometry
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    convenience init(data:Data?, currentFrame:ARFrame, anchor:ARImageAnchor) {
        self.init(geometry: nil)
        self.createPlayer(currentFrame, data, anchor)
    }

    private func createPlayer(_ frame:ARFrame, _ data:Data?,_ anchor:ARImageAnchor) {

        let physicalSize = anchor.referenceImage.physicalSize

        print("Init Player W/ physicalSize: \(physicalSize)")

        //Create video
        if((UIApplication.shared.delegate! as! AppDelegate).testing) {
            let path = Bundle.main.path(forResource: "Bear", ofType: "mov")
            self.url = URL(fileURLWithPath: path!)
        }
        else {
            let url = data!.getAVAssetURL(location: "MyLocation")
            self.url = url
        }
        let asset = AVAsset(url: self.url)
        let track = asset.tracks(withMediaType: AVMediaType.video).first!
        let playerItem = AVPlayerItem(asset: asset)
        let player = AVPlayer(playerItem: playerItem)
        self.player = player
        var videoSize = track.naturalSize.applying(track.preferredTransform)

        videoSize = CGSize(width: abs(videoSize.width), height: abs(videoSize.height))
        print("Init Video W/ size: \(videoSize)")

        //Determine if landscape or portrait
        self.landscape = videoSize.width > videoSize.height
        print(self.landscape == true ? "Landscape" : "Portrait")

        //Do something when video ended
        NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(note:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)

        //Add observer to determine when Player is ready
        player.addObserver(self, forKeyPath: "status", options: [], context: nil)

        //Create video Node
        let videoNode = SKVideoNode(avPlayer: player)

        //Create 2d scene to put 2d player on - SKScene
        videoNode.position = CGPoint(x: videoSize.width/2, y: videoSize.height/2)
        videoNode.size = videoSize


        //Portrait -- //Landscape doesn't need adjustments??
        if(!self.landscape) {
            let width = videoNode.size.width
            videoNode.size.width = videoNode.size.height
            videoNode.size.height = width
            videoNode.position = CGPoint(x: videoNode.size.width/2, y: videoNode.size.height/2)
        }

        let scene = SKScene(size: videoNode.size)

        //Add videoNode to scene
        scene.addChild(videoNode)

        //Create Button-look even though we don't use the button. Just creates the illusion to pressing play and pause
        let image = UIImage(named: "PlayButton")!
        let texture = SKTexture(image: image)
        self.button = SKSpriteNode(texture: texture)
        self.button.position = videoNode.position

        //Makes the button look like a square
        let minimumSize = [videoSize.width, videoSize.height].min()!
        self.button.size = CGSize(width: minimumSize/4, height: minimumSize/4)
        scene.addChild(button)

        //Get ratio difference from physicalsize and video size
        let widthRatio = Float(physicalSize.width)/Float(videoSize.width)
        let heightRatio = Float(physicalSize.height)/Float(videoSize.height)

        let finalRatio = [widthRatio, heightRatio].min()!

        //Create a Plane (SCNPlane) to put the SKScene on
        let plane = SCNPlane(width: scene.size.width, height: scene.size.height)
        plane.firstMaterial?.diffuse.contents = scene
        plane.firstMaterial?.isDoubleSided = true

        //Set Self.geometry = plane
        self.geometry = plane

        //Size the node correctly
        //Find the real scaling variable
        let scale = CGFloat(finalRatio)
        let appearanceAction = SCNAction.scale(to: scale, duration: 0.4)
        appearanceAction.timingMode = .easeOut

        //Set initial scale to 0 then use action to scale up
        self.scale = SCNVector3Make(0, 0, 0)
        self.runAction(appearanceAction)
    }

    @objc func playerDidFinishPlaying(note: Notification) {
        self.player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
        self.player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
        self.setButtonAlpha(alpha: 1)
    }
}

Efforts1:

J'ai essayé d'arrêter le suivi via:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    guard let imageAnchor = anchor as? ARImageAnchor else { return }

    createVideoNode(imageAnchor)
    self.resetConfiguration(turnOnConfig: true, turnOnImageTracking: false)   
}

func resetConfiguration(turnOnConfig: Bool = true, turnOnImageTracking:Bool = false) {
    let configuration = ARWorldTrackingConfiguration()
    if(turnOnImageTracking) {
        guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
            fatalError("Missing expected asset catalog resources.")
        }
        configuration.planeDetection = .horizontal
        configuration.detectionImages = referenceImages
    }
    else {
        configuration.planeDetection = []
    }
    if(turnOnConfig) {
        sceneView.session.run(configuration, options: [.resetTracking])
    }
}

Ci-dessus, j'ai essayé de réinitialiser la configuration. Cela ne fait que réinitialiser les plans, semble-t-il, car la vidéo est toujours en cours de rendu. Qu'il soit en pause ou terminé, il se réinitialisera et recommencera ou continuera à jouer là où il s'était arrêté.

Efforts2:

J'ai essayé

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    guard let imageAnchor = anchor as? ARImageAnchor else { return }

    createVideoNode(imageAnchor)
    self.pauseTracking()  
}

func pauseTracking() {
    self.sceneView.session.pause()
}

Cela arrête tout, donc la caméra se fige même si rien n'est suivi. C'est totalement inutile ici.

32
impression7vx

D'accord. Voici donc une solution. voir renderer(_:updateAtTime:).

var player: AVPlayer!
var play = true

@objc func tap(_ recognizer: UITapGestureRecognizer){
    if play{
        play = false
        player.pause()
    }else{
        play = true
        player.play()
    }
}

func setVideo() -> SKScene{
    let size = CGSize(width: 500, height: 500)
    let skScene = SKScene(size: size)

    let videoURL = Bundle.main.url(forResource: "video.mp4", withExtension: nil)!
    player = AVPlayer(url: videoURL)

    skScene.scaleMode = .aspectFit

    videoSpriteNode = SKVideoNode(avPlayer: player)
    videoSpriteNode.position = CGPoint(x: size.width/2, y: size.height/2)
    videoSpriteNode.size = size
    videoSpriteNode.yScale = -1
    skScene.addChild(videoSpriteNode)

    player.play()

    return skScene
}

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    if let image = anchor as? ARImageAnchor{
        print("found")

        let planeGeometry = SCNPlane(width: image.referenceImage.physicalSize.width, height: image.referenceImage.physicalSize.height)
        let plane = SCNNode(geometry: planeGeometry)
        planeGeometry.materials.first?.diffuse.contents = setVideo()

        plane.transform = SCNMatrix4MakeRotation(-.pi/2, 1, 0, 0)

        node.addChildNode(plane)
    }
}

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    if !play{
        player.pause()
    }
}

Utilisez cette idée dans votre code.

2
Alok Subedi