I have an audio player that starts on my home screen - swift2

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)

Related

AR View freezes for couple seconds while video in AVPlayer loads

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()
}
}

How to get AVPlayer to redraw current AVItem videoComposition when paused

I'm building a simple video editor for macOS: A movie file is loaded as an AVAsset, transformed by a series of CIFilters in a AVVideoComposition, and played by an AVPlayer. I present UI controls for some of the parameters of the CIFilters.
When video is playing everything is working great, I slide sliders and effects change! But when the video is paused the AVPlayerView doesn't redraw after the controls in the UI are changed.
How can I encourage the AVPlayerView to redraw the contents of the videoComposition of it's current item when it's paused?
class ViewController: NSViewController {
#objc #IBAction func openDocument(_ file: Any) { ... }
#IBOutlet weak var moviePlayerView: AVPlayerView!
var ciContext:CIContext? = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!)
var sliderValue: Double = 0.0
#IBAction func sliderMoved(_ sender: NSSlider) {
self.sliderValue = sender.doubleValue
// need to update the view here when paused
self.moviePlayerView.setNeedsDisplay(self.moviePlayerView.bounds)
// setNeedsDisplay has no effect.
}
func loadMovie(file: URL) {
let avMovie = AVMovie(url: file)
let avPlayerItem = AVPlayerItem(asset: avMovie)
avPlayerItem.videoComposition = AVVideoComposition(asset: avMovie) { request in
let output = request.sourceImage.applyingGaussianBlur(sigma: self.sliderValue)
request.finish(with: output, context: self.ciContext)
}
self.moviePlayerView.player = AVPlayer(playerItem: avPlayerItem)
}
}
It turns out re-setting the AVPlayerItem's videoComposition property will trigger the item to redraw. This works even if you set the property to it's current value: item.videoComposition = item.videoComposition. The property setter appears to have undocumented side effects.
To fix the sample code above, do this:
#IBAction func sliderMoved(_ sender: NSSlider) {
self.sliderValue = sender.doubleValue
// update the view when paused
if self.moviePlayerView.player?.rate == 0.0 {
self.moviePlayerView.player?.currentItem?.videoComposition = self.moviePlayerView.player?.currentItem?.videoComposition
}
}
Hopefully someone finds this useful!

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")

How to play a sound just one time SWIFT

I'm having a little problem with my code who consists to play a sound when I start my app. But here's the problem everytime that I go back to the first screen the sound is playing again and I want it to play just one time. When the menu screen pop ups for the fist time.
Here's my code
var bubbleSound: SystemSoundID!
bubbleSound = createBubbleSound()
AudioServicesPlaySystemSound(bubbleSound)
(...)
the function
func createBubbleSound() -> SystemSoundID {
var soundID: SystemSoundID = 0
let soundURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), "bubble", "wav", nil)
AudioServicesCreateSystemSoundID(soundURL, &soundID)
return soundID
}
You can define a struct like this (source):
struct MyViewState {
static var hasPlayedSound = false
}
Then in your viewDidLoad:
if(!MyViewState.hasPlayedSound) {
var bubbleSound: SystemSoundID!
bubbleSound = createBubbleSound()
AudioServicesPlaySystemSound(bubbleSound)
MyViewState.hasPlayedSound = true
}
You can then modify MyViewState.hasPlayedSound and allow the UIViewController to play the sound again if desired.

Simple Swift Cocoa app with WebKit: Upload picture doesn't work

I decided to make my own FB chat app that simply shows https://messenger.com on a WebView after trying other 'freemium' apps.
My ViewController.swift has just a few lines of code that loads URL on the web view
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var webView: WebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://messenger.com")
let request = NSURLRequest(URL: url!);
webView.mainFrame.loadRequest(request);
}
override var representedObject: AnyObject? {
didSet {
// do nothing
}
}
}
Besides adding NSAppTransportSecurity key to info.plist to unblock HTTP traffic via HTTPS connection, I have not done any other settings.
Question
Please take a look at this image first.
Everything looks fine & working except two things.
Uploading image does not work - I labeled as 1 in the picture.
normally (as in other released apps or from web browsers) if you click that icon, it shows an explorer to upload a picture like below.
My app completely ignores user's click on that icon so I cannot upload any pictures to the chat. Interestingly, if I drag and drops the picture to the webview, it uploads fine.
Shared picture does not show up - I labeled as 2 in the picture.
again, from other browsers or released apps, it shows the pictures that I shared with participants like below. (of course I censored the pictures)
my app tries to load the pics, but does not display them. I can see it trying to load because I see circular progress indicator while loading.
Why?
I suspect that there might be a way to listen to the JavaScript that's triggered within the WebView and link to a file explorer or something like that?
This I have no idea. I'm logged into Messenger (basically Facebook), so I think session is not a problem here. Maybe some jQuery loading issue??
What should I do to solve these issues?
There is indeed a delegate method to open a new panel called runOpenPanelForFileButtonWithResultListener, documentation here.
In the delegate method, just create a new NSOpenPanel like this:
func webView(sender: WebView!, runOpenPanelForFileButtonWithResultListener resultListener: WebOpenPanelResultListener!, allowMultipleFiles: Bool) {
let openDialog = NSOpenPanel()
if (openDialog.runModal() == NSOKButton) {
let fileName: String = (openDialog.URL?.path)!
resultListener.chooseFilename(fileName) // Use chooseFilenames for multiple files
}
}
I just tried to create a WebView from Messagers App and images are loading well.
You should try to enable WebView options like "Autoload Images" or "Enable Animated Images" from interface builder (or by code).
This code works for me and what is nice here is that you make the download of image asynchronous. You can find more about this concept here: http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1 and: http://www.raywenderlich.com/79150/grand-central-dispatch-tutorial-swift-part-2
Edited
I. You have to create new Class or a new iOS Swift File named ImageLoader with this content:
class ImageLoader {
var cache = NSCache()
class var sharedLoader : ImageLoader {
struct Static {
static let instance : ImageLoader = ImageLoader()
}
return Static.instance
}
func imageForUrl(urlString: String, completionHandler:(image: UIImage?, url: String) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {()in
var data: NSData? = self.cache.objectForKey(urlString) as? NSData
if let goodData = data {
let image = UIImage(data: goodData)
dispatch_async(dispatch_get_main_queue(), {() in
completionHandler(image: image, url: urlString)
})
return
}
var downloadTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: urlString)!, completionHandler: {(data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if (error != nil) {
completionHandler(image: nil, url: urlString)
return
}
if data != nil {
let image = UIImage(data: data)
self.cache.setObject(data, forKey: urlString)
dispatch_async(dispatch_get_main_queue(), {() in
completionHandler(image: image, url: urlString)
})
return
}
})
downloadTask.resume()
})
}
}
II. In your actual viewController you call the method 'imageForUrl' from ImageLoaded following this lines of code:
ImageLoader.sharedLoader.imageForUrl("http://upload.wikimedia.org/wikipedia/en/4/43/Apple_Swift_Logo.png", completionHandler:{(image: UIImage?, url: String) in
self.myImage.image = image!
})
I took the code from this link: https://teamtreehouse.com/community/does-anyone-know-how-to-show-an-image-from-url-with-swift
Edited for image loaded on webview
Here is the code. It works perfect for me:
override func viewDidLoad() {
super.viewDidLoad()
let myWebView:UIWebView = UIWebView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
myWebView.loadRequest(NSURLRequest(URL: NSURL(string: "https://scontent-vie1-1.xx.fbcdn.net/hphotos-xap1/v/t1.0-9/12144725_10204647881668565_4367944825116750386_n.jpg?oh=5ecdae91f5258ffe0e0355e176f8eb8a&oe=56B007CA")!))
self.view.addSubview(myWebView)
}

Resources