Reputation: 435
Playing a very short sound (~0.5s) produces a hiccup (like a lag) in my SpriteKit iOS game programmed in Swift. In other questions, I read that I should prepareToPlay()
the sound, which I did.
I even used a variable (soundReady
) to check if the sound is prepared before playing it. I also re-prepare the sound whenever it is finished playing (audioPlayerDidFinishPlaying()
). Here are the relevant parts of the code:
class GameScene: SKScene, AVAudioPlayerDelegate {
var splashSound = NSURL()
var audioPlayer = AVAudioPlayer()
var soundReady = false
override func didMoveToView(view: SKView) {
let path = NSBundle.mainBundle().pathForResource("plopSound", ofType: "m4a")
splashSound = NSURL(fileURLWithPath: path)
audioPlayer = AVAudioPlayer(contentsOfURL: splashSound, error: nil)
audioPlayer.delegate = self
soundReady = audioPlayer.prepareToPlay()
}
func playSound(){
if(soundReady){
audioPlayer.play()
soundReady = false
}
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool){
//Prepare to play after Sound finished playing
soundReady = audioPlayer.prepareToPlay()
}
}
I have no idea where I've gone wrong on this one. I feel like I have tried everything (including, but not limited to: only preparing once, preparing right after playing, not using a variable, but just prepareToPlay()).
Additional information:
Upvotes: 31
Views: 9369
Reputation: 2253
When I called play multiple times it would cause bad access. I believe the player is being deallocated since this is not thread safe. I created a serial queue to alleviate this problem.
class SoundManager {
static let shared = SoundManager()
private init() {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try? AVAudioSession.sharedInstance().setActive(true)
}
private let serialQueue = DispatchQueue(label: "SoundQueue", qos: .userInitiated)
private var player: AVAudioPlayer?
static func play(_ sound: Sound) {
shared.play(sound)
}
func play(_ sound: Sound) {
guard let url = Bundle.main.url(forResource: sound.fileName, withExtension: "mp3")
else { return }
do {
try serialQueue.sync {
self.player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
DispatchQueue.main.async {
self.player?.play()
}
}
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
}
Upvotes: 2
Reputation: 171
Just adding a Swift 3 version of the solution from @brilliantairic.
DispatchQueue.global().async {
audioPlayer.play()
}
Upvotes: 15
Reputation: 836
I ran into this same problem and played the sound in the backgroundQueue.
This is a good example: https://stackoverflow.com/a/25070476/586204.
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
audioPlayer.play()
})
Upvotes: 24