I'm currently working on an app that plays an audio clip based on the state of an image/object.
Currently I am getting this error code.
Thread 1: EXC_BAD_ACCESS (code=1, address=0x48)
I'm guessing it could be an error with trying to play another audio clip on the same thread when another audio clip has already been played on the same thread.
But honestly, I'm not sure, I'm still learning about threads and multithreading, so I'm probably reaching here.
What am I missing here? How can I go about playing the second audio clip (turnSaberOffAudio) without getting this error message? Thanks!
This is what happens when I tap the button.
@objc func lightsaberTapped() {
print("lightsaber open!")
if lightsaberModel.isSaberOn == false {
UIView.animate(withDuration: 0.2, animations: {
self.saberImageHeightConstraint.constant = 530
self.saberImageWidthConstraint.constant = 210
self.mainView.layoutIfNeeded()
self.viewDidLayoutSubviews()
}, completion: nil)
lightsaberModel.turnSaberOnAudio.play()
lightsaberModel.isSaberOn = true
} else {
UIView.animate(withDuration: 0.2, animations: {
self.saberImageHeightConstraint.constant = 1
self.saberImageWidthConstraint.constant = 1
self.mainView.layoutIfNeeded()
self.viewDidLayoutSubviews()
}, completion: nil)
lightsaberModel.turnSaberOffAudio.play()
lightsaberModel.isSaberOn = false
}
}
This is the model with the necessary info.
class Model {
var turnSaberOnAudio: AVAudioPlayer = AVAudioPlayer()
var turnSaberOffAudio: AVAudioPlayer = AVAudioPlayer()
var whichLightsaber = String()
var isSaberOn = Bool()
func turnSaberOn() {
let turnSaberOnPath = Bundle.main.path(forResource: "SaberOn", ofType: "wav")
do {
turnSaberOnAudio = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: turnSaberOnPath!))
}
catch {
print(error)
}
}
func turnSaberOff() {
let turnSaberOffPath = Bundle.main.path(forResource: "SaberOff", ofType: "mp3")
do {
turnSaberOffAudio = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: turnSaberOffPath!))
}
catch {
print(error)
}
}
}
This is something you should never do:
var turnSaberOnAudio: AVAudioPlayer = AVAudioPlayer()
The empty initializer AVAudioPlayer()
creates something useless, which is not only useless but also harmful to cause crash.
And declaring the var as non-Optional, you cannot distinguish if it holds something useful or not.
And with your current code, turnSaberOn()
or turnSaberOff()
may not be called when you call play()
.
Try changing your Model
as:
class Model {
var turnSaberOnAudio: AVAudioPlayer? //<- Never use `AVAudioPlayer()`
var turnSaberOffAudio: AVAudioPlayer? //<- Never use `AVAudioPlayer()`
var whichLightsaber: String = ""
var isSaberOn: Bool = false
init() {
let turnSaberOnUrl = Bundle.main.url(forResource: "SaberOn", withExtension: "wav")!
do {
turnSaberOnAudio = try AVAudioPlayer(contentsOf: turnSaberOnUrl)
turnSaberOnAudio!.prepareToPlay()
print("\(turnSaberOnUrl) loaded")
} catch {
print(error)
}
let turnSaberOffUrl = Bundle.main.url(forResource: "SaberOff", withExtension: "mp3")!
do {
turnSaberOffAudio = try AVAudioPlayer(contentsOf: turnSaberOffUrl)
turnSaberOffAudio!.prepareToPlay()
print("\(turnSaberOffUrl) loaded")
} catch {
print(error)
}
//...
}
//...
}
And use it like this:
@IBAction func lightSaberTapped(_ sender: Any) {
print("lightsaber open!")
if !lightsaberModel.isSaberOn {
//...
lightsaberModel.turnSaberOnAudio?.play()
lightsaberModel.isSaberOn = true
} else {
//...
lightsaberModel.turnSaberOffAudio?.play()
lightsaberModel.isSaberOn = false
}
}