I made an app that allows you to scan an image with an ios device and it plays a video using arKit tracking . However, once the video is finished, I can't seem to find a way to make it restart . Is there anyway I can recall all the code in the viewcontroller?
if let imageAnchor = anchor as? ARImageAnchor {
// Create a plane
let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
if imageAnchor.referenceImage.name == "skateboard" {
// Set AVPlayer as the plane's texture and play
plane.firstMaterial?.diffuse.contents = self.SkateboardVideoPlayer
self.SkateboardVideoPlayer.play()
}
}
Have you tried looping the video with a notification observer?
See the below code from here:
var SkateboardVideoPlayer: AVPlayer!
// In viewDidLoad or similar method
...
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.SkateboardVideoPlayer.currentItem, queue: .main) { [weak self] _ in
self?.SkateboardVideoPlayer?.seek(to: CMTime.zero)
self?.SkateboardVideoPlayer?.play()
}
Related
I am implementing a demo project using SceneKit and ARKit where i keep camera on some picture and play video from url. Everything works fine but i could not be able to stop previous video and it starts another video. Previous video plays on background. It happens sometimes when i switch image. My implementation is :
#IBOutlet var sceneView: ARSCNView!
var videosNames = [String]()
var player = AVPlayer()
var videoNode = SKVideoNode()
In viewDidload,
sceneView.delegate = self
// Create a session configuration
let configuration = ARImageTrackingConfiguration()
// first see if there is a folder called "ARImages" Resource Group in our Assets Folder
if let trackedImages = ARReferenceImage.referenceImages(inGroupNamed: "ARImages", bundle: Bundle.main) {
// if there is, set the images to track
configuration.trackingImages = trackedImages
// at any point in time, only 1 image will be tracked
configuration.maximumNumberOfTrackedImages = 1
}
// Run the view's session
sceneView.session.run(configuration)
The callback method looks like :
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// if the anchor is not of type ARImageAnchor (which means image is not detected), just return
if let _ : Bool = videosNames.contains(anchor.name!) {
guard let imageAnchor = anchor as? ARImageAnchor, var fileUrlString = Bundle.main.path(forResource: "baby1", ofType: "mp4") else {return}
print("didAdd node:\(anchor.name!)")
//find our video file
fileUrlString = "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" // i set the file URL path directly here
let videoItem = AVPlayerItem(url: URL(fileURLWithPath: fileUrlString))
self.player.seek(to: CMTime.zero)
self.player.pause() // Tried to pause the video, set seek time zero. Nothing works.
self.videoNode.pause()
player = AVPlayer(playerItem: videoItem)
//initialize video node with avplayer
videoNode = SKVideoNode(avPlayer: self.player)
//videoNode.removeAllChildren()
self.player.play()
//self.videoNode.play()
// add observer when our player.currentItem finishes player, then start playing from the beginning
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil) { (notification) in
self.player.seek(to: CMTime.zero)
self.player.play()
//self.videoNode.play()
print("Looping Video")
}
// set the size (just a rough one will do)
let videoScene = SKScene(size: CGSize(width: 480, height: 360))
// center our video to the size of our video scene
self.videoNode.position = CGPoint(x: videoScene.size.width / 2, y: videoScene.size.height / 2)
// invert our video so it does not look upside down
self.videoNode.yScale = -1.0
// add the video to our scene
videoScene.addChild(self.videoNode)
// create a plan that has the same real world height and width as our detected image
let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
// set the first materials content to be our video scene
plane.firstMaterial?.diffuse.contents = videoScene
// create a node out of the plane
let planeNode = SCNNode(geometry: plane)
// since the created node will be vertical, rotate it along the x axis to have it be horizontal or parallel to our detected image
planeNode.eulerAngles.x = -Float.pi / 2
// finally add the plane node (which contains the video node) to the added node
node.addChildNode(planeNode)
}
}
When any node gets update
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if node.isHidden == true {
print("Node is out of view:\(anchor.name!) :")
self.player.seek(to: CMTime.zero)
videoNode.pause()
} else {
print("Node is not out of view:\(anchor.name!)")
}
}
Here i get log when any new video start playing , but i could not stop previously started video which plays in background (only audio) with the new video. It totally messed up my process. If i put camera away from both image, but the sounds keeps playing. Could anyone please help me for the issue ?
Thanks in advance.
Node must be removed from ARSCNView's session. Following method does not removed the node from session.
node.removeFromParentNode()
Only sceneView.session.remove() works perfectly in didUpdate() method.
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if node.isHidden == true {
if let imageAnchor = anchor as? ARImageAnchor {
sceneView.session.remove(anchor: imageAnchor)
}
} else {
if !isNodeAdded {
isNodeAdded = true
}
}
}
sceneView.session.remove() works to stop the video in the didUpdate() method, but you also need to pause the player, otherwise the audio still will be playing. This works for me:
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if node.isHidden == true {
if let imageAnchor = anchor as? ARImageAnchor {
sceneView.session.remove(anchor: imageAnchor)
player.pause()
}
} else {
player.play()
}
}
So I have an AVPlayer which I would like to continuously loop. This is not an issue as this works fine.
However, the user has the ability to edit the rate of the video. If the rate is changed, it will loop fine for 2-3 times and then the AVPlayer get stuck on the first frame.
The completion block is not called after it gets stuck.
Here is my code for looping the AVPlayer
// Loop video notification
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
guard let self = self, let player = self.player else { return }
player.seek(to: .zero)
player.play()
player.rate = self.playerRate
}
In my project I want to switch between ARWorldTrackingConfiguration and ARFaceTrackingConfiguration.
I use two different types of view a ARSCNView to use the rear camera and a ARView to do the face tracking.
First I start the ARSCNView and after, if the user want, he can switch to face tracking
I start my view controller in this mode:
sceneView.delegate = self
sceneView.session.delegate = self
// Set up scene content.
setupCamera()
sceneView.scene.rootNode.addChildNode(focusSquare)
let configurationBack = ARWorldTrackingConfiguration(
configurationBack.isAutoFocusEnabled = true
configurationBack.planeDetection = [.horizontal, .vertical]
sceneView.session.run(configurationBack, options: [.resetTracking, .removeExistingAnchors])
And I load my Object (.scn)
When I want to switch to front camera I and pass to ARView I do this:
let configurationFront = ARFaceTrackingConfiguration()
// here I stop my ARSCNView session
self.sceneView.session.pause()
self.myArView = ARView.init(frame: self.sceneView.frame)
self.myArView!.session.run(configurationFront)
self.myArView!.session.delegate = self
self.view.insertSubview(self.myArView!, aboveSubview: self.sceneView)
And than I load my .rcproject
So my problem begin here, when I try to return to back camera and pass to ARWorldTracking again.
This is my method:
// remove my ARView with face tracking
self.myArView?.session.pause()
UIView.animate(withDuration: 0.2, animations: {
self.myArView?.alpha = 0
}) { (true) in
self.myArView?.removeFromSuperview()
self.myArView = nil
}
// here I restart the initial ARSCNView
let configurationBack = ARWorldTrackingConfiguration(
configurationBack.isAutoFocusEnabled = true
configurationBack.planeDetection = [.horizontal, .vertical]
session.run(configurationBack, options: [.resetTracking, .removeExistingAnchors])
When I switch to back camera, the sensor doesn't track the planes correctly.
How can I fix that, so how can I switch correctly between ARWorldTrackingConfiguration and ARFaceTrackingConfiguration?
Thanks in advance
Make sure to also remove all added nodes to the scene when you're pausing the session. Add below code after where you pause the session with self.sceneView.session.pause():
self.sceneView.scene.rootNode.enumerateChildNodes { (childNode, _) in
childNode.removeFromParentNode()
}
I have an app where I play a video in the background in a certain view.
This video is always supposed to play fullscreen. I recently asked a question about stretching this video to fill up an iPhone plus model (make AVPlayer fill the entire screen) However, I just tested on an iPhone X, and the following happens:
I made the background of the view containing the AVPlayerLayer purple, just to clarify. As you can see, the video plays in the top of the screen (appx. 80pt), then there's the complete video, and then there's nothingness. How is this possible if my view does actually cover the entire screen?
This is how the video is made:
#objc fileprivate func playVideo() {
log.debug("playVideo")
let file = self.videoName.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType:file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
self.player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.bounds
self.layer.addSublayer(playerLayer)
self.play()
if (self.loop) {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.restart()
}
}
self.backgroundColor = .purple
NotificationCenter.default.addObserver(
self,
selector: #selector(self.restart),
name: Notification.Name("playVideos"),
object: nil)
}
This method is called after layoutSubviews().
How do I make the video stretch fully (with some overflow on the sides to make it visible of course)? Thanks :-).
My audio starts and stops as I would expect. When I background the app the music keeps playing, if I activate Siri, the music interrupts but then resumes as I would expect.
The issue I have is that, if my sounds are playing in the background, and I start up Apple Music or Podcasts, the audio mixes together which I don't want however if I use Siri, my audio stops then resumes after.
I want my music to stop and the other to take control of the audio just like it does with Siri. I have tried removing .mixWithOthers but when I do that, it seems that once I background my app and I start Siri, afterwards my audio is no longer able to start again even though the code within the .ended case is called.
func commonInit() {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption), name: .AVAudioSessionInterruption, object: nil)
}
var shouldResume: Bool = false
#objc func handleInterruption(_ notification: Notification) {
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue)
else { return }
switch type {
case .began:
player?.pause()
case .ended:
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt
else { return }
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
player?.play()
}
}
}
Ideally I want my app to resume after any interruptions, but I also want my app to stop playing if there are any interruptions.
Thanks
Adding UIApplication.shared.beginReceivingRemoteControlEvents() after setting the category fixed this issue but I'm not sure why.