Play music according to scene - ios

I am trying to play theme music in continuous loop according to the scene in Sprite Kit. So far I am doing this:
var backgroundMusicPlayer = AVAudioPlayer();
func playBackgroundMusic() -> () {
var error: NSError?
let backgroundMusicURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Home", ofType: "wav")!)
backgroundMusicPlayer = AVAudioPlayer(contentsOfURL: backgroundMusicURL, error: &error)
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
}
And it is working. But now I want to play a different theme music in another theme. According to this, I have to use AVQueuePlayer instead of AVAudioPlayer. But AVQueuePlayer doesn't have methods like stop() or number of loops.
Any idea how to change sounds when switching to different scenes?

Related

What is the best way to play a sound with the least amount of delay possible, in Swift/Xcode?

I am experiencing a slight delay between UIButton press and sound output (it seems to average about 0.05 seconds). I am currently implementing AVAudioPlayer. Is there a best way to play a sound instantaneously? I scoured the Internet but can't find a concrete answer.
For reference, my (simplified) code is as follows:
// Get sound url
let soundURL = URL(fileURLWithPath: Bundle.main.path(forResource: file, ofType: type)!)
// Audio Player
var audioPlayer: AVAudioPlayer!
do {
// Create audio player
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
}
catch let errMsg as NSError {
print("AVAudioPlayer error: \(errMsg.localizedDescription)")
}
// Prepare audio player to play
audioPlayer.prepareToPlay()
//...
#IBAction func btnPressed(_ sender: UIButton) {
audioPlayer.play()
}

AVAudioPlayer audio breaking and volume increases on its own

I am calling this function playSound() in viewWillAppear, the audio starts playing and then suddenly volume increases with a minor break in playback.
here is my code
func playSound() {
let url = Bundle.main.url(forResource: "music", withExtension: "mp3")!
do {
try self.player = AVAudioPlayer(contentsOf: url)
player?.prepareToPlay()
player?.numberOfLoops = -1
player?.play()
} catch let error as NSError {
KBLog.log(object: error.description)
}}
Edit 1 :
I am using this code for an iPad app, so ipad only have speaker, no earpiece, if this has something to do with the issue.
The problem was i was playing this audio and at the same time i was initialising my publisher view for starting the video call, thus an instance of AVPlayer was initialised to take audio input i.e. two instances of AVPlayer were getting initialised causing the break in playback.

Adding sound effects in Spritekit

I'm making a Flappy Bird-type game, and I want to add a sound effect for when a coin is collected. This is what I have:
import SpriteKit
import AVFoundation
class GameScene: SKScene {
var coinSound = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("Super Mario Bros.- Coin Sound Effect", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
Later in my didMoveToView:
audioPlayer = AVAudioPlayer(contentsOfURL: coinSound, error: nil)
audioPlayer.prepareToPlay()
For the second part (in my didMoveToView), the first line keeps showing an error:
Call can throw, but it is not marked with 'try' and the error is not handled
How do I fix this?
I know you already have marked an answer but if you re read this question or for future readers its easier to use SKActions to play a short 1 time sound.
AVFoundation is better used for background music.
e.g
class GameScene: SKScene {
// add as class property so sound is preloaded and always ready
let coinSound = SKAction.playSoundFileNamed("NAMEOFSOUNDFILE", waitForCompletion: false)
}
and than when you need to play the sound simply say this
run(coinSound)
Hope this helps
audioPlayer = AVAudioPlayer(contentsOfURL: coinSound, error: nil)
This is no longer the way that errors are handled in Swift. You must use the do-try-catch syntax.
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: coinSound)
audioPlayer.prepareToPlay()
}
catch let error {
// handle error
}
Alternatively, if you don't care to write any error handling code, you can make your audioPlayer variable optional and write a failable try?:
audioPlayer = try? AVAudioPlayer(contentsOfURL: coinSound)
audioPlayer?.prepareToPlay()
This is more closely equivalent to the code you're attempting where you pass nil for the error parameter.
Or if crashing is the appropriate behavior when an error occurs, you could take this approach:
guard let audioPlayer = try? AVAudioPlayer(contentsOfURL: coinSound) else {
fatalError("Failed to initialize the audio player with asset: \(coinSound)")
}
audioPlayer.prepareToPlay()
self.audioPlayer = audioPlayer
Swift 3.0+
For the above guard let example from #nhgrif, this Swift 3.0+ way of doing it:
var coinSound = NSURL(fileURLWithPath:Bundle.main.path(forResource: "Super Mario Bros.- Coin Sound Effect", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
func playCoinSound(){
guard let soundToPlay = try? AVAudioPlayer(contentsOf: coinSound as URL) else {
fatalError("Failed to initialize the audio player with asset: \(coinSound)")
}
soundToPlay.prepareToPlay()
// soundToPlay.play() // NO IDEA WHY THIS DOES NOT WORK!!!???
self.audioPlayer = soundToPlay
self.audioPlayer.play() // But this does work... please let me know why???
}
playCoinSound()

Background Music Restarting

I am programming a game which switches between SKScenes. When I start playing the background music from the start screen, switch to a new screen and back to the start screen the music starts playing again.
I can't find out how to run the music once and not restarting again and again. I could make a workaround with storing a bool in NSUserDefaults to check if the music is running but that seems like an awful workaround. There must be a more simple solution. I have attempted with static but that doesn't work.
Create a new empty swift file and add this code into it:
var backgroundMusicPlayer: AVAudioPlayer!
var musicOnPlayScene = Bool()
func playBackgroundMusic(filename: String) {
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
if (url == nil) {
print("Could not find file: \(filename)")
return
}
var error: NSError? = nil
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: url!)
} catch let error1 as NSError {
error = error1
backgroundMusicPlayer = nil
}
if backgroundMusicPlayer == nil {
print("Could not create audio player: \(error!)")
return
}
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
}
Now play your background music into start scene this way:
playBackgroundMusic("yourSong.mp3")
and when you switch a scene add this code into next scene's didMoveToView:
backgroundMusicPlayer.play()
This will resume your background music when you switch any scene.
Hope it will help.

Swift AVAudioPlayer wont play audio recorded with AVAudioRecorder

I have built an app that records an audio clip, and saves it to a file called xxx.m4a.
Once the recording is made, I can find this xxx.m4a file on my system and play it back - and it plays back fine. The problem isn't recording this audio track.
My problem is playing this audio track back from within the app.
// to demonstrate how I am building the recordedFileURL
let currentFileName = "xxx.m4a"
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsDir: AnyObject = dirPaths[0]
let recordedFilePath = docsDir.stringByAppendingPathComponent(currentFileName)
var recordedFileURL = NSURL(fileURLWithPath: recordedFilePath)
// quick check for my own sanity
var checkIfExists = NSFileManager.defaultManager()
if checkIfExists.fileExistsAtPath(recordedFilePath){
println("audio file exists!")
}else{
println("audio file does not exist")
}
// play the recorded audio
var error: NSError?
let player = AVAudioPlayer(contentOfURL: recordedFileURL!, error: &error)
if player == nil {
println("error creating avaudioplayer")
if let e = error {
println(e.localizedDescription)
}
}
println("about to play")
player.delegate = self
player.prepareToPlay()
player.volume = 1.0
player.play()
println("told to play")
// -------
// further on in this class I have the following delegate methods:
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool){
println("finished playing (successfully? \(flag))")
}
func audioPlayerDecodeErrorDidOccur(player: AVAudioPlayer!, error: NSError!){
println("audioPlayerDecodeErrorDidOccur: \(error.localizedDescription)")
}
My console output when I run this code is like this:
audio file exists!
about to play
told to play
I dont get anything logged into the console from either audioPlayerDidFinishPlaying or audioPlayerDecodeErrorDidOccur.
Can someone explain to me why I my audio clip isnt playing?
Many thanks.
You playing code is fine; your only problem is with the scope of your AVAudioPlayer object. Basically, you need to keep a strong reference to the player around all the time it's playing, otherwise it'll be destroyed by ARC, as it'll think you don't need it any more.
Normally you'd make the AVAudioPlayer object a property of whatever class your code is in, rather than making it a local variable in a method.
In the place where you want to use the Playback using speakers before you initialize your AVAudioPlayer, add the following code:
let recordingSession = AVAudioSession.sharedInstance()
do{
try recordingSession.setCategory(AVAudioSessionCategoryPlayback)
}catch{
}
and when you are just recording using AVAudioRecorder use this before initializing that:
do {
recordingSession = AVAudioSession.sharedInstance()
try recordingSession.setCategory(AVAudioSessionCategoryRecord)
}catch {}

Resources