1
votes

I have two scenes and I want that the background music play in both. The problem is that the music plays only the first time the app opens. If I press the button to go to the second scene the music plays while the scenes transition, but stop when the transition end. No music for the second scene and if I go back to the first scene there is no music there too. The music always play during the transition between scenes (this is why I'm using a long transition time, just to confirm (I don't know why).

class GameScene: SKScene {

var backgroundMusic: SKAudioNode!

// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #



override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    backgroundColor = SKColor.redColor()

    global.centerPoint = CGPoint(x:(CGRectGetMidX(self.frame)), y:(CGRectGetMidY(self.frame)))



    let myLabel = SKLabelNode(fontNamed:"Helvetica Bold")
    myLabel.text = "GameScene"
    myLabel.fontSize = 45
    myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + 100)

    self.addChild(myLabel)

    createBtn()


    // # # # # # # # # # # # # # # # # # # # # # # # # # # #

    // MUSICA
    if let musicURL = NSBundle.mainBundle().URLForResource("music", withExtension: "wav") {

        backgroundMusic = SKAudioNode(URL: musicURL)

        addChild(backgroundMusic)

        print("backgroundMusic OK")

    } else {

        print("backgroundMusic file not found!")
    }



} // END override func didMoveToView



// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    /* Called when a touch begins */

    let touch = touches //as!  Set<UITouch>
    let location = touch.first!.locationInNode(self)
    let node = self.nodeAtPoint(location)


    if (node.name == "btnOne") {

        let reveal = SKTransition.pushWithDirection(.Left, duration: 2.5)
        let scene = SceneTwo(size: size)
        self.view?.presentScene(scene, transition:reveal)


    }


} // END override func touchesBegan

For the second file/scene I'm using the same code:

class SceneTwo: SKScene {

var backgroundMusicTwo: SKAudioNode!

// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # #



override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    backgroundColor = SKColor.blueColor()

    global.centerPoint = CGPoint(x:(CGRectGetMidX(self.frame)), y:(CGRectGetMidY(self.frame)))



    let myLabel = SKLabelNode(fontNamed:"Helvetica Bold")
    myLabel.text = "SceneTwo"
    myLabel.fontSize = 45
    myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + 100)

    self.addChild(myLabel)

    createBtn()

    // # # # # # # # # # # # # # # # # # # # # # # # # # # #

    // MUSICA
    if let musicURL = NSBundle.mainBundle().URLForResource("music", withExtension: "wav") {

        backgroundMusicTwo = SKAudioNode(URL: musicURL)

        addChild(backgroundMusicTwo)

        print("backgroundMusicTwo OK")

    } else {

        print("backgroundMusicTwo file not found!")
    }



} // END override func didMoveToView
2
You said it plays only when the app first opens, but then you said it only plays during transitions. Or do you mean it plays the first time the app opens, and it continues to while it's transitioning? Or also during transition when going back to scene 1 and scene 2 like you said later? - Jose Ramirez
Also, you seem to have the same audio code in both scenes. You might want to build a single class to handle audio so you wouldn't have to keep copy and pasting code. For example, I built a class called JKAudioPlayer that handles all my audio for me and whenever I need it, I just call this in the specific scene: audio.playMusic("Music"). - Jose Ramirez
Thank you very much for your help. Your solution is perfect! - FernandoRosa

2 Answers

2
votes

I think the reason it won't play is because the audio node would be deallocated once the scene changes. I'm not sure why it's only playing in between transitions. I've also never used an audio node, but I did create my own custom class to handle all the audio.

Also, I would not copy and paste code. You have the same code in both scenes when you can just have a single class to handle audio. Here's a solution using a custom class I made called JKAudioPlayer. Just create a new Swift class and paste the code in it. Because the audio object declared later is not part of a scene, it will continue/allow music to play when you transition.

import Foundation
import SpriteKit
import AVFoundation
import AVFoundation.AVAudioSession

/**Manages a shared instance of JKAudioPlayer.*/
private let JKAudioInstance = JKAudioPlayer()

/**Provides an easy way to play sounds and music. Use sharedInstance method to access a single object for the entire game to manage the sound and music.*/
public class JKAudioPlayer {

    /**Used to access music.*/
    var musicPlayer: AVAudioPlayer!

    /** Allows the audio to be shared with other music (such as music being played from your music app). If this setting is false, music you play from your music player will stop when this app's music starts. Default set by Apple is false. */
    static var canShareAudio = false {
        didSet {
            canShareAudio ? try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient) : try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
        }
    }

    /**Creates an instance of the JAAudio class so the user doesn't have to make their own instance and allows use of the functions. */
    public class func sharedInstance() -> JKAudioPlayer {
        return JKAudioInstance
    }

/**Plays music. You can ignore the "type" property if you include the full name with extension in the "filename" property. Set "canShareAudio" to true if you want other music to be able to play at the same time (default by Apple is false).*/
    public func playMusic(fileName: String, withExtension type: String = "") {
        if let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: type) {
            musicPlayer = try? AVAudioPlayer(contentsOfURL: url)
            musicPlayer.numberOfLoops = -1
            musicPlayer.prepareToPlay()
            musicPlayer.play()
        }
    }

    /**Stops the music. Use the "resumeMusic" method to turn it back on. */
    public func stopMusic() {
        if musicPlayer != nil && musicPlayer!.playing {
            musicPlayer.currentTime = 0
            musicPlayer.stop()
        }
    }

    /**Pauses the music. Use the "resumeMusic" method to turn it back on. */
    public func pauseMusic() {
        if musicPlayer != nil && musicPlayer!.playing {
            musicPlayer.pause()
        }
    }

    /**Resumes the music after being stopped or paused. */
    public func resumeMusic() {
        if musicPlayer != nil && !musicPlayer!.playing {
            musicPlayer.play()
        }
    }

    /**Plays a sound only once. Must be used inside a runAction(...) method.*/
    public func playSound(fileName: String) -> SKAction? {
        return SKAction.playSoundFileNamed(fileName, waitForCompletion: false)
    }
}

To use it, create a global variable that has access to the audio.

let audio = JKAudioPlayer.sharedInstance()

Then when you want to play music, you can easily call this in the didMoveToView function of the scene class.

audio.playMusic("MusicName")

And you also have a way to control it simply by calling the pause/stop/resume music functions like this:

audio.stopMusic()

If you want the user to be able to play their own music in the background, for example from their iTunes library without your app pausing their music, then you can just set canShareAudio to true in the GameViewController's viewDidLoad function.

audio.canShareAudio = true
0
votes

here is the code for Swift 3 tested and working you need to put the extension of the file at the end or it will not work with locating the sound file

in a new Swift file copy and paste

import Foundation
import SpriteKit
import AVFoundation

    public let audio = JKAudioPlayer.sharedInstance()


    private let JKAudioInstance = JKAudioPlayer()


    public class JKAudioPlayer {


    var musicPlayer: AVAudioPlayer!


    static var canShareAudio = false {
        didSet {
        canShareAudio ? try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient) : try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
        }
    }

    public class func sharedInstance() -> JKAudioPlayer {
        return JKAudioInstance
    }
    public func playMusic(fileName: String, withExtension type: String = "") {
        if let url = Bundle.main.url(forResource: fileName, withExtension: type) {
            musicPlayer = try? AVAudioPlayer(contentsOf: url)
            musicPlayer.numberOfLoops = -1
            musicPlayer.prepareToPlay()
            musicPlayer.play()
        }
    }


    public func stopMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.currentTime = 0
            musicPlayer.stop()
        }
    }


    public func pauseMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.pause()
        }
    }

    public func resumeMusic() {
        if musicPlayer != nil && !musicPlayer!.isPlaying {
            musicPlayer.play()
        }
    }

    public func playSound(fileName: String) -> SKAction? {
        return SKAction.playSoundFileNamed(fileName, waitForCompletion: false)
    }
}

to play any file just simply put in

audio.playMusic(fileName: "NameOfFile")

and to stop the music just put in where you need to stop it

audio.stopMusic()