swift - ARKit SKVideoNode Reproduciendo en Render
avplayer scnnode (1)
De acuerdo. Así que aquí hay una solución. ver 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()
}
}
Usa esta idea en tu código.
Problema principal:
Estoy agregando esta sección DESPUÉS para aclarar el problema. - Puedo PAUSAR mi video (no quiero que se reproduzca en un bucle). Cuando mi nodo aparece, mi nodo reproduce mi video, incluso si está en pausa. Si mi video ha terminado de reproducirse y aparece a la vista, se reiniciará. Quiero eliminar este comportamiento.
En mi aplicación, tengo un SKVideoNode
creado a partir de un AVPlayer(:URL)
dentro de 3D Space usando objetos SCNGeometry
y objetos SCNGeometry
. Uso ARKit
.ImageTracking
para determinar cuándo se encuentra una imagen específica y reproduzco un video desde allí. Todo es bueno y elegante, excepto que el jugador decide jugar en su propio tiempo, cada vez que el AVPlayer aparece a la vista; sin embargo, podría ser cuando ARImageAnchor
el SCNNode
esté conectado a la vista. De cualquier manera, el AVPlayer
está jugando cada vez que el nodo ve la lente de la cámara. yo suelo
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "rate") {
print((object as! AVPlayer).rate)
}
}
para imprimir la tasa, y es 1, sin embargo, fue 0.
Imprimí algún tipo de impresión de función de print("Play")
para todas mis funciones utilizando player.pause () o player.play () y no se llama a ninguna de ellas cuando la tasa se cambia arriba. ¿Cómo puedo encontrar la fuente de lo que está cambiando la velocidad de mi jugador?
Revisé el rootnode original, self.sceneview.scene.rootNode.childNodes
para asegurarme de que no estoy creando VideoNodes / SCNNodes / AVPlayers adicionales, etc., y parece que solo hay 1.
¿Alguna idea sobre por qué SKVideoNode / AVPlayer se está reproduciendo cuando SCNNode ve la cámara con ARKit? ¡Gracias por adelantado!
Edit1:
Hizo una solución para determinar SÓLO cuando un usuario hizo clic en este nodo
let tap = UITapGestureRecognizer(target: self, action: #selector(self!.tapGesture))
tap.delegate = self!
tap.name = "MyTap"
self!.sceneView.addGestureRecognizer(tap)
Y luego dentro de esta siguiente función, puse
@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
}
}
}
donde videoPlayer3D es el SCNNode
que contiene el SKVideoNode.
Sin embargo, recibo el error com.apple.scenekit.scnview-renderer (17): EXC_BAD_ACCESS (code=2, address=0x16d8f7ad0)
en la sección "AQUÍ" arriba. Parece que el renderizador de la vista en pantalla está intentando alterar mi nodo de video en la función de renderizado, aunque, ni siquiera uso el renderer(updateAtTime:)
, solo uso
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
createVideoNode(imageAnchor)
}
para determinar cuándo veo una imagen, es decir, imageTracking
y creo el nodo. ¿Algun consejo?
Pensamiento 1
Se presenta el error que indica que se está llamando a algún método desde un objeto SCNView para el renderizador del método (eso es lo que entiendo del error), pero no tengo el nodo llamado específicamente. Creo que tal vez se está llamando una acción predeterminada, ya que el nodo se está viendo, sin embargo, no estoy 100% seguro de cómo acceder a él o determinar qué método. Los objetos que estoy usando no son objetos SCNView, y no creo que hereden de los objetos SCNView (mire el primer párrafo para ver las variables utilizadas). Solo busco eliminar la "acción" del nodo que se está reproduciendo cada vez que está a la vista.
ADICIÓN:
Por el bien de seguir la creación de mi reproductor de video si está interesado, aquí está. Déjame saber si hay algo más que te gustaría ver (no estoy seguro de qué más quieres ver) y gracias por tu ayuda.
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
}
Clase 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)
}
}
Esfuerzos1:
He intentado detener el seguimiento a través de:
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])
}
}
Arriba, he intentado restablecer la configuración. Esto solo hace que reinicie los planos que parece, ya que el video aún se está reproduciendo en render. Ya sea que esté en pausa o finalizado, se reiniciará y volverá a comenzar o continuará jugando donde lo dejó.
Esfuerzos2:
Yo he tratado
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()
}
Esto detiene todo, por lo que la cámara incluso se congela ya que no se está rastreando. Es completamente inútil aquí.