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