AR View freezes for couple seconds while video in AVPlayer loads - realitykit

I am creating a plane with VideoMaterial when AR Reference Image is detected. I play the video inside an AVPlayer and I want to load the video from URL, instead of local resources. It all works well, however when the image is detected and the place is created, the app freezes for couple of seconds while the video gets loaded.
I don't know how to get rid of the freezing, instead I would like to show SimpleMaterial plane until the video is loaded.
Can anybody help me with this, please?
Code that I use to play the video when reference image is detected:
class Coordinator: NSObject, ARSessionDelegate{
var parent: ARViewContainer
var videoPlayer: AVPlayer!
init(parent: ARViewContainer) {
self.parent = parent
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }
let playerItem = AVPlayerItem(url: URL(string: "https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Video-Player/iMacAdvertisement.mp4")!)
videoPlayer = AVPlayer(playerItem: playerItem)
let videoMaterial = VideoMaterial(avPlayer: videoPlayer)
let videoPlane = ModelEntity(mesh: .generatePlane(width: Float(imageAnchor.referenceImage.physicalSize.width), depth: Float(imageAnchor.referenceImage.physicalSize.height), cornerRadius: 10), materials: [videoMaterial])
if let imageName = imageAnchor.name, imageName == "referenceimg1" {
let anchor = AnchorEntity(anchor: imageAnchor)
anchor.addChild(videoPlane)
parent.arView.scene.addAnchor(anchor)
videoPlayer.play()
}
}

Related

Image on tabItem, UIKit UITabBarController

Hi im making an app with uikit and i want to set a profile image(received from url) on Tabbar item. I am expecting the updated profile image on TabBarItem. Also i want to add a border around the image when the tab is selected.
I found this solution, but the image does not appear in the tabitem.
Set user's profile picture on Tabbar item
extension UITabBarController {
func addSubviewToLastTabItem(_ image: UIImage) {
if let lastTabBarButton = self.tabBar.subviews.last, let tabItemImageView = lastTabBarButton.subviews.first {
if let accountTabBarItem = self.tabBar.items?.last {
accountTabBarItem.selectedImage = nil
accountTabBarItem.image = nil
}
let imgView = UIImageView()
imgView.frame = tabItemImageView.frame
imgView.layer.cornerRadius = tabItemImageView.frame.height/2
imgView.layer.masksToBounds = true
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
imgView.image = image
self.tabBar.subviews.last?.addSubview(imgView)
}
}
}
Could someone help me to do that?
Did you add the second part of code:
let yourImage = UIImage(named: "your-image")
override func viewDidLayoutSubviews() { // add to any vc
super.viewDidLayoutSubviews()
tabBarController?.addSubviewToLastTabItem(yourImage)
}
When you call addSubviewToLastTabItem("String") it is critical print correctly parameter without mistakes.
Could you post this part of code?
One more thing viewDidLayoutSubviews will work if you rotate iPhone, double check please that you are calling addSubviewToLastTabItem() in viewDidLoad()

Swift 2 Spritekit: Issue with background music not resuming when the game is unpaused

First of all I would like to thank anyone in advance for any help I get.
I have searched far and wide across the net and cannot find a solution to my issue. My issue is with an iOS game I am building using the SpriteKit framework. I have added a background song using an SKAudioNode and it works fine initially, but when I pause and play the game within a few seconds, the music does not begin playing again. I have tried lots of things like removing the SKAudioNode when the game is paused and adding it again when the game is resumed, but nothing has worked. I have posted a snippet of my code below keeping it as relevant as possible:
class GameScene: SKScene, SKPhysicsContactDelegate {
var backgroundMusic = SKAudioNode(fileNamed: "bg.mp3")
let pauseImage = SKTexture(imageNamed: "pause.png")
var pauseButton:SKSpriteNode!
let playImage = SKTexture(imageNamed: "play2.png")
var playButton:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.addChild(backgroundMusic)
// create pause button
pauseButton = SKSpriteNode(texture: pauseImage)
pauseButton.position = CGPoint(x: self.size.width - pauseButton.size.width, y: pauseButton.size.height)
pauseButton.zPosition = 1
pauseButton.name = "pauseButton"
self.addChild(pauseButton)
// create play button
playButton = SKSpriteNode(texture: playImage)
playButton.position = CGPoint(x: self.size.width - playButton.size.width, y: -playButton.size.height)
playButton.zPosition = 1
playButton.name = "playButton"
self.addChild(playButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
if pauseButton.containsPoint(touch.locationInNode(self)) {
let blockOne = SKAction.runBlock({
self.pauseButton.position.y = - self.pauseButton.size.height
self.playButton.position.y = self.playButton.size.height
})
let blockTwo = SKAction.runBlock({
self.view?.paused = true
})
self.runAction(SKAction.sequence([blockOne, blockTwo]))
}
else if(playButton.containsPoint(touch.locationInNode(self))) {
self.playButton.position.y = -self.playButton.size.height
self.pauseButton.position.y = self.pauseButton.size.height
self.view?.paused = false
}
}
}
}
You'll want to look into using AVAudioPlayer and NSNotificationCenter to pass data around the game.
Start the background audio player in your actual GameViewController class.
It's better to do it this way then use SKAudioNode... That's more for sounds that are like sound effects relating to something that happened in gameplay.
By using AVAudioPlayer, the advantage is when the music is paused it's still cued up to play in it's previous spot.
This is one of the few things that will be running regardless of what's going on. So we put it in the GameViewController.
So here's an example of GameViewController code we'd need to start
import AVFoundation
var bgMusicPlayer:AVAudioPlayer?
Then in the GameViewController we make these functions as such
override func viewDidLoad() {
super.viewDidLoad()
// PlayBackgroundSound , PauseBackgroundSound will be your code to send from other "scenes" to use the audio player //
// NSNotificationCenter to pass data throughout the game //
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.playBackgroundSound(_:)), name: "PlayBackgroundSound", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.pauseBackgroundSound), name: "PauseBackgroundSound", object: nil)
}
func playBackgroundSound(notification: NSNotification) {
let name = notification.userInfo!["fileToPlay"] as! String
if (bgSoundPlayer != nil){
bgSoundPlayer!.stop()
bgSoundPlayer = nil
}
if (name != ""){
let fileURL:NSURL = NSBundle.mainBundle().URLForResource(name, withExtension: "mp3")!
do {
bgSoundPlayer = try AVAudioPlayer(contentsOfURL: fileURL)
} catch _{
bgSoundPlayer = nil
}
bgSoundPlayer!.volume = 1
bgSoundPlayer!.numberOfLoops = -1
// -1 will loop it forever //
bgSoundPlayer!.prepareToPlay()
bgSoundPlayer!.play()
}
}
func pauseBackgroundSound() {
if (bgSoundPlayer != nil){
bgSoundPlayer!.pause()
}
}
Then when you want to use the audio player in your pause or resume button functions.
Remember you need to have the player used in each scene.
import AVFoundation.AVAudioSession
override func didMoveToView(view: SKView) {
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
}
Then if you want to pause or play something just use NSNotificationCenter.
// To Pause in a scene use this line of code in the function you need to pause the music
NSNotificationCenter.defaultCenter().postNotificationName("PauseBackgroundSound", object: self)
/// To Play initially or a new song .. use this line of code in the function you need to play the music
NSNotificationCenter.defaultCenter().postNotificationName("PlayBackgroundSound", object: self, userInfo: "FILE NAME OF BACKGROUND MUSIC")

AVPlayer doesn't play video from my parse

I'm trying to play video from my parse server
I'm getting empty video player
And when i print the url i get the correct video url
let thevideo:PFFile! = (self.selectedmsg["file"] as! PFFile)
let url:NSURL = NSURL(string: thevideo.url!)!
let player = AVPlayer(URL: url)
let playercontroller = AVPlayerViewController()
playercontroller.player = player
self.presentViewController(playercontroller, animated: true) {
player.play()
}
Any help?
I had the same issue, tried to search around for solution, but cannot, the only workable solution is save the file to device, and play it locally.

I have an audio player that starts on my home screen

Whenever I go back to my home screen it replays the music over the already playing music. I tried making an if statements that 'obviously' doesn't work (because I barely know any swift!). Here is my home screen code:
var myAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
let myFilePathString =
NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
if let myFilePathString = myFilePathString
{
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do{
try myAudioPlayer = AVAudioPlayer(contentsOfURL: myFilePathURL)
myAudioPlayer.play()
}catch
{
print("error")
}
}
}
How can I stop it from playing on top of itself?
Here is a picture of my storyboard
enter image description here
The music player is on StartScreen, but when I click the back button, it starts another music player over the current one.
I need code which says
is myAudioPlayer playing?
if yes do not play song again
else play "16 March of the resistance"
enter image description here
You need to check if you are already playing. You can do this by the playing property of the AVAudioPlayer class.
Guard your viewDidLoad with say:
if !myAudioPlayer.playing
Note that I don't know any Swift.
I'm not sure what you mean by "my home screen", is it the device or your main VC? Why are you overriding viewDidLoad()? A previous player could never be playing in the viewDidLoad() call, unless for a leak which is what's needing fixing.
Otherwise, you just need to declare your player as an optional, rather than creating it in the declaration. As for the replaying or overlapping, just check if you player exists, although you wouldnt do this in your viewDidLoad(). ViewDidLoad() is called when the view of the VC is created and the view loaded, which doesn't relate to your lifecycle of the audio.
Depending on your requirements, you might want to create the player somewhere else that is plays throughout the app, rather than restarting when visiting that VC. Otherwise the code below will avoid the duplication. The problem was this var myAudioPlayer = AVAudioPlayer() which would duplicate players and one wasn't being released.
var myAudioPlayer: AVAudioPlayer?
func viewDidLoad() {
// this should never be needed with correct player setup
if let player = myAudioPlayer where player.playing {
return
}
if let pathString = NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
{
let url = NSURL(fileURLWithPath: pathString)
do{
try myAudioPlayer = AVAudioPlayer(contentsOfURL: url)
myAudioPlayer?.play()
} catch {
print("error")
}
}
}
Update
So there is no need to make the VC do this. Create a Class of e.g. AudioController, which could even be a singleton. Initialise it in your AppDelegate and trigger this player setup code after initialisation. Your view controllers dont need to know about the audio controller. IF e.g. you do want to modify the volume from a setting page, you just access the singleton object and set its volume.
This way you keep all your audio controller code nicely separated. Dont just think of functionality, think of overall software architecture, with objects having clear responsibilities. Keep your ViewControllers light, they shouldnt be doing much processing.
Take a look at this Singletons , its really easy to setup a singleton in Swift nowadays.
class AudioController {
static let sharedInstance = AudioController()
var audioPlayer: AVAudioPlayer?
let kVolumeKey = "VolumeKey"
let kHasSavedInitialVolumeKey = "HasSavedInitialVolumeKey"
var volume: Float = 0.5
func setup() {
self.loadVolume()
self.setupPlayer()
}
func updatePlayerVolume(volume: Float) {
self.audioPlayer?.volume = volume
self.volume = volume
self.saveVolume()
}
func saveVolume() {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: kHasSavedInitialVolumeKey)
NSUserDefaults.standardUserDefaults().setFloat(self.volume, forKey: kVolumeKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadVolume() {
if NSUserDefaults.standardUserDefaults().boolForKey(kHasSavedInitialVolumeKey) {
self.volume = NSUserDefaults.standardUserDefaults().floatForKey(kVolumeKey)
}
}
func setupPlayer() {
if let pathString = NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
{
let url = NSURL(fileURLWithPath: pathString)
do{
try audioPlayer = AVAudioPlayer(contentsOfURL: url)
audioPlayer?.volume = self.volume
audioPlayer?.play()
} catch {
print("error")
}
}
}
}
So, you can just setup the player from your AppDelegate's didFinishLaunching
AudioController.sharedInstance.setupPlayer()
And then you can access the AudioController using AudioController.sharedInstance.whatever when you need to get or set. Also, you will need to implement the players delegate methods for handling the end of the song, possibly triggering a new song to play from a playlist(create a new object for this) or whatever. Your player delegate code is also nicely separated now.
Looking at your storyboard it seems that when you click the Back button on the second ViewController you allocate another start screen and you push it on the screen.
It seems that you're using a UINavigationController if so when the back button is tapped you should call:
self.navigationController?.popViewControllerAnimated(true)
If your showing it as a modal you should dismiss is like this:
self.dismissViewControllerAnimated(true, completion: nil)

Cannot invoke initializer for type 'AVPlayer'

I'm having an issue getting my video to play via AVPlayer. For some reason I'm getting the error 'Cannot invoke initializer for type 'AVPlayer' with an argument list of type '(URL:NSURL)'
The weird thing is, this exact code snippet works in another one of my projects. I do have the AVKit and AVFoundation frameworks imported.
Well, I realized my dumb mistake. I had a local variable (player), so it died instantly before it could even play. I moved it up a level to be an instance variable so it persists as long as the view controller persists.
var MoviePlaying:Bool = false
let path = NSBundle.mainBundle().pathForResource("big_buck_bunny_720p_50mb", ofType:"mp4")
var player: AVPlayer = AVPlayer()
let playerController = AVPlayerViewController()
#IBAction func PlayTapped(sender: UIButton) {
let moviePlayerController = AVPlayerViewController()
let path = NSBundle.mainBundle().pathForResource("big_buck_bunny_720p_50mb", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
let playerVC = AVPlayerViewController()
var player: AVPlayer = AVPlayer()
playerVC.player = AVPlayer(URL: url)
self.presentViewController(playerVC, animated: true, completion: nil)

Resources