Swift: Unmanaged AVAudioPlayer - ios

I have AVAudioPlayer instance:
var audioPlayer: AVAudioPlayer!
self!.audioPlayer = AVAudioPlayer(data: fileData, error: &error)
self!.audioPlayer?.numberOfLoops = -1
self!.audioPlayer?.delegate = self
if (self?.audioPlayer?.prepareToPlay() != false) {
println("Successfully prepared for playing")
} else {
println("Failed to prepare for playing")
}
I need to disable ARC for this AVAudioPlayer. Unmanaged is not well-documentated, so it is pretty hard to do that. Here's what I've tried:
var audioPlayer: Unmanaged<AVAudioPlayer>!
//Stuck after creating nil instance, what to do now?
self!.audioPlayer = AVAudioPlayer(data: fileData, error: &error)
self!.audioPlayer?.numberOfLoops = -1
self!.audioPlayer?.delegate = self
if (self?.audioPlayer?.prepareToPlay() != false) {
println("Successfully prepared for playing")
} else {
println("Failed to prepare for playing")
}

You should write:
// Properties in your class
var unmanagedAudioPlayer: Unmanaged<AVAudioPlayer>
var audioPlayer : AVAudioPlayer!
// code
self.audioPlayer = AVAudioPlayer(...)
self.unmanagedAudioPlayer = Unmanaged.passRetained(self.audioPlayer)
Now you can use self.audioPlayer as usual as an AVAudioPlayer (or AVAudioPlayer! if you prefer, but I don't see why).
self.unmanagedAudioPlayer keeps a retained reference of self.audioPlayer such that it cannot be deallocated by ARC.
When you are done with this AVAudioPlayer object, you can call self.unmanagedAudioPlayer.release() (or autorelease) to release it as if you are doing the same in Objective-C.
In fact you don't need to store self.audioPlayer separately, because you can always get it via unmanagedAudioPlayer.takeUnratainedValue() but it is a nice alias to make your code more readable.

This is an un-question. The suggestion to "turn off ARC" is wrong, and you should not be trying to do it. If you have a memory issue you should be tackling it directly. For example, if there's a problem with an AVAudioPlayer in the background, set it to nil as you enter the background, to release it from memory. But even more important, you should be asking yourself why you have this problem to start with. I've been using AVAudioPlayer for years and I've never had a situation where "it leaks from memory" (whatever you mean by that).

Related

Cannot play audio file without app crashing on device running ios 13

I have an app being used by people to receive orders with it needing to make a continuous sound until staff attend to it. It was working for two months then just started crashing a lot. For whatever reason, it runs fine on an iPad but not on iPhones running a recent operating system.
When this bit of code gets called it crashes:
guard let path = Bundle.main.path(forResource: "alert.mp3", ofType: nil) else { return }
let url = URL(fileURLWithPath: path)
do {
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
} catch let err {
print("err: \(err)")
}
DispatchQueue.main.async {
self.alertSoundEffect.numberOfLoops = -1
self.alertSoundEffect.prepareToPlay()
self.alertSoundEffect.play()
}
The fix online to declare the alertSoundEffect variable like this:
private var alertSoundEffect : AVAudioPlayer!
has not worked at all.
I tried moving everything but the line:
self.alertSoundEffect.play()
to viewDidLoad as I thought maybe that code couldn't get called more than once, but it didn't help.
Specifically, the compiler highlights this line when it crashes:
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
I tried using try AVAudioPlayer where it takes a Data object as a parameter or with including the type of audio file to be played, but that did not change anything.
When I try the AVAudioPlayer's delegate and declare it like this:
self.alertSoundEffect.delegate = self
right before the first lines of code I shared above Xcode highlights this line instead when it reliably crashes.
What else should I try?
I suppose your path is wrong.
Try this:
guard let path = Bundle.main.path(forResource: "alert", ofType: "mp3") else { return }
Also, if your audio file is short, like less than 30s, then try not to call self.alertSoundEffect.prepareToPlay(). Just call self.alertSoundEffect.play() right away.
Since iOS 13, this was causing a bug in my app, since I have notification sounds which are 3-10 seconds long.
If you initialise your AVAudioPlayer like var wrongMusicPlayer: AVAudioPlayer = AVAudioPlayer() OR wrongMusicPlayer = AVAudioPlayer() in any method then please remove it and just Declare like var wrongMusicPlayer: AVAudioPlayer!.
iOS 13.1 Crash in AVAudio Player

AudioServicesCreateSystemSoundID only once in application and play sound from anywhere

I am doing following stuff to play a sound on button tap. But here, I guess it loads sound file every time I tap on button.
if let soundURL = NSBundle.mainBundle().URLForResource("notification", withExtension: "mp3") {
var mySound: SystemSoundID = 0;
AudioServicesCreateSystemSoundID(soundURL, &mySound);
AudioServicesPlaySystemSound(mySound);
}
What I am trying to do is to load above code once in AppDelegate and call below code from any other VC :
let systemSoundID: SystemSoundID = 0;
AudioServicesPlaySystemSound(systemSoundID);
each time I want a sound.
But it causes an error in console showing
Failure to setup sound, err = -50.
Any Solution?
Rather than add to AppDelegate, it may be neater to add a separate class. This works for me:
import AudioToolbox
class PlaySound {
static private var mySound:SystemSoundID = {
// Do it like this so mySound is initialised only when it is first used
var aSound:SystemSoundID = 1000 // a default sound in case we forget the sound file
if let soundURL = NSBundle.mainBundle().URLForResource("Detection", withExtension: "wav") {
AudioServicesCreateSystemSoundID(soundURL, &aSound)
print("Initialised aSound:\(aSound)")
} else {
print("You have forgotten to add your sound file to the app.")
}
return aSound // this value is put into mySound when mySound is first used.
}()
static func play() { AudioServicesPlaySystemSound(mySound) } // play the sound
static func prepare() -> SystemSoundID { return mySound } // call this to preload sound to avoid any delay on first use, if you want
}
Then whenever I need the sound, I write
PlaySound.play()
and that only sets up the sound once, the first time it is used. The print above shows just when, and how many times, it is initialized. If you want to avoid any possibility of delay in setting up the sound when it is first used, you can call
PlaySound.prepare()
in AppDelegate when the app is launched.

Swift/iOS: AVAudioPlayer does not play sound in NSData format

I am trying to create an AVAudioPlayer that plays NSData downloaded from Parse.
I am pretty certain the sound (.wav format) has been uploaded to Parse. I am also certain that the sound can be downloaded from Parse in the NSData format. So I am creating an AVAudioPlayer object using the downloaded data from Parse:
if audioData != nil {
print("successful downloading audio!") //this prints out
let audioPlayer = try! AVAudioPlayer(data: audioData!, fileTypeHint: AVFileTypeWAVE)
audioPlayer.prepareToPlay()
audioPlayer.volume = 0.5
audioPlayer.play()
}
As you can see above, the audioPlayer is created, but it does not play the sound. Where might be wrong?
Is this code within a function? If so, your making the audioPlayer variable locally. This means that the audioPlayer is created, starts playing and then is deallocated (at the end of the function call), resulting in no audio. Your audioPlayer object needs to be a class property or within global space (such as a singleton for example), so the object life persists after the function ends.

AVAudioPlayer sound not playing

In iOS 8/Xcode 6 I had a function that included a sound effect. It no longer works in iOS 9 after changing the code multiple times. This is what I've tried:
Original:
let bangSoundEffect = SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false)
runAction(bangSoundEffect)
Other attempt:
self.runAction(SKAction.playSoundFileNamed("Bang.mp3", waitForCompletion: false))
Also:
func playRocketExplosionSound(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() }
playRocketExplosionSound("Bang.mp3")
I'm pulling my hair out. I'm using the same code in a different scene for another sound effect and it works fine!! What's going wrong?
I've noticed that the sound effect begins to play sometimes in the simulator, however it doesn't complete and throws this error:
2015-09-24 19:12:14.554 APPNAME[4982:270835] 19:12:14.553 ERROR: 177: timed out after 0.012s (735 736); mMajorChangePending=0
It doesn't work at all on actual devices.
What is the problem? :'(
Possible problem with MP3 file
The problem is most likely connected with the MP3 file you're using. The code works for other sounds, this suggests that the MP3 file might be corrupted and AVAudioPlayer fails with decoding it. You can try re-encode this file and see if the problem persists. Or, even better, converting it to WAV.
Using WAVs
General rule of the thumb when creating short sound effects for games, is to use WAV unless you really feel you need the trim the fat.
Top-notch games are going for top-of-the-line production quality, so they record and produce assets uncompressed 24bit/48kHz. Titles with slightly lesser ambitions might record and produce in 16/44.1, which is the official standard for CD quality audio.
This has at least two benefits. One is that the sound has a better quality. Second one, the CPU does not have to decode the file to play it.
Corrupt data file | AVAudioPlayer out of scope
1. Corrupt data file
This will ensure you have found the file:
var backgroundMusicPlayer: AVAudioPlayer? = nil
if let url = Bundle.main.url(
forResource: "Bang", withExtension: "mp3") {
do {
try backgroundMusicPlayer = AVAudioPlayer(contentsOf: url)
backgroundMusicPlayer!.play()
} catch {}
}
return nil
2. AVAudioPlayer out of scope
The variable retaining backgroundMusicPlayer must not go out of scope before play() has completed and returns. This is generally achieved by using a class variable:
var backgroundMusicPlayer: AVAudioPlayer? = nil
Don't do this: the following sound will play for, at best, outOfScopeDelay due to the local scope of var audioPlayer.
let outOfScopeDelay = 0.5
do {
var audioPlayer:AVAudioPlayer! // Incorrectly scoped variable
try audioPlayer = AVAudioPlayer(contentsOf: audioRecorder.url)
audioPlayer.play()
Thread.sleep(forTimeInterval: outOfScopeDelay)
} catch {}
► Find this solution on GitHub and additional details on Swift Recipes.
try this:
dispatch_async(dispatch_get_main_queue(), {
(self.playRocketExplosionSound("Bang.mp3")
})
it's no longer safe to play audio in child thread under iOS 9.

how do I set my Audio Session Default Behavior to Playback in swift?

My app has a "Click" sound functionality. I used the
import AVFoundation
then the following function to run the "Click" sound:
var audioPlayer = AVAudioPlayer()
func playSound() {
var soundPath = NSBundle.mainBundle().pathForResource("tick", ofType: "wav")
var soundURL = NSURL.fileURLWithPath(soundPath!)
self.audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
self.audioPlayer.play()
}
Now if the user is running a music player, my app causes the music player to stop. I read about the Audio Session Default Behavior in the documentation, but I don't know how to apply it.
Can you please help?
Thank you!
If you are wondering the syntax for swift 2, here it is:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: .DuckOthers)
} catch {
print("AVAudioSession cannot be set: \(error)")
}
Depending on what you want the app to behave, i.e, how your app's sound effect or music should interact with other app's background audio session, you might need to tweak both the audio session category and categoryOption.
If you just want to play the sound effect, like "tick" sound, then, AVAudioSessionCategoryAmbient and DuckOthers should be used respectively, for example:
let audioSession = AVAudioSession.sharedInstance()
var error: NSErrorPointer = nil
audioSession.setCategory(AVAudioSessionCategoryAmbient, withOptions: .DuckOthers, error: error)
However, I suppose you are actually trying to play a sound effect, in this case, the AudioServices API is a more suitable choice. You can check func AudioServicesPlaySystemSound(inSystemSoundID: SystemSoundID) in AudioToolbox framework for more details.
Another common scenario. If you want to have your app to play audio exclusively, even if there're other app's playing the music in the background, you need to set the category to AVAudioSessionCategorySoloAmbient, for example:
let audioSession = AVAudioSession.sharedInstance()
var error: NSErrorPointer = nil
audioSession.setCategory(AVAudioSessionCategorySoloAmbient, error: error)
I hope you've got what you're looking for.

Resources