AudioServicesCreateSystemSoundID only once in application and play sound from anywhere - ios

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.

Related

How to dynamically change players in AudioKit output

I'm implementing an app utilizing AudioKit that allows you to play a large number of audio files and switch between them at any time. Additionally we need one audio file to play on loop for ambient noise.
What is the correct way to dynamically change players in AudioKit's output? Or what's the proper way to implement this behavior?
AudioKit requires you set its output which can be an AKPlayer that plays one audio file or an AKMixer given an array of AKPlayers for example. You cannot change the output once AudioKit has been started as I saw previously. So currently my approach is to use AKMixer to play two AKPlayers - one for the current file and one for the ambient noise. When the user taps the 'Next' button I stop() AudioKit, recreate an AKMixer with a new player for the next song's audio file and the ambient noise player, assign that to output, start() AudioKit, and play both players. This results in undesirable behavior because playback of the ambient noise is stopped when switching songs resulting in a brief pause.
If this is the correct approach, how can we update the output without stopping AudioKit? I wondered if you can initialize the mixer with an array of players that's a strongly held property and simply add/remove players in the array. But that doesn't work - starting a new player throws an error player started when in a disconnected state because this node is not attached to the output.
I've created a sample project to demonstrate this behavior. When launched the app plays drums as ambient noise and waves for the current track. When you tap Next it'll switch to the next track (which in this demo code is just the same audio file) and you can hear the drums stop and then resume which is undesirable. I've included the project's code below:
final class Maestro: NSObject {
static let shared = Maestro()
private var audioPlayer: AKPlayer?
private var ambientPlayer: AKPlayer = {
let player = AKPlayer(url: Bundle.main.url(forResource: "drums", withExtension: "wav")!)!
player.isLooping = true
return player
}()
private var mixer: AKMixer?
private let audioFileURL = Bundle.main.url(forResource: "waves", withExtension: "mp3")!
func play() {
playNewPlayer(fileURL: audioFileURL)
}
func next() {
//In the real app we'd play the next audio file in the playlist but for the demo we'll just play the same file
playNewPlayer(fileURL: audioFileURL)
}
private func playNewPlayer(fileURL: URL) {
audioPlayer?.stop()
audioPlayer = nil
do {
try AudioKit.stop()
} catch {
print("Maestro AudioKit.stop error: \(error)")
}
audioPlayer = AKPlayer(url: fileURL)!
mixer = AKMixer([audioPlayer!, ambientPlayer])
AudioKit.output = mixer
do {
try AudioKit.start()
} catch {
print("Maestro AudioKit.start error: \(error)")
}
if ambientPlayer.isPlaying {
//need to resume playback from current position
let pos = ambientPlayer.currentTime
ambientPlayer.stop()
ambientPlayer.play(from: pos)
} else {
ambientPlayer.play()
}
audioPlayer?.play()
}
}

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

AVAudioPlayer crackles and pops

I have a Simon-like memory game where a sequence of tones is played and the user attempts to repeat the tones. The problem is that I get a horrible crackly, popping sound from time to time. It could be after just a few notes or as many as twenty. The sounds are all wav files that are two seconds in length. I cycle through ten players so that none of the sounds are clipped. (I've tried up to 50 but that didn't help anything.) I have also tried implementing audioPlayerDidFinishPlaying so that I could stop the player once the sound is complete but that didn't help either. And finally, I added prepareToPlay() - sadly, no difference.
One interesting issue that may or may not be related is that after a sequence of about 15 notes, the audio stops working all together. The app continues like normal, but without any sound.
Here is the audio portion of the code:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "beep1"
case 1:
soundName = "beep2"
case 2:
soundName = "beep3"
case 3:
soundName = "beep4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
do {
buttonSound.insert(try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue), at: playerIndex)
buttonSound[playerIndex].prepareToPlay()
buttonSound[playerIndex].delegate = self
if playerIndex < 10 {
buttonSound[playerIndex].play()
playerIndex += 1
} else {
buttonSound[playerIndex].play()
playerIndex = 0
}
} catch {
// error
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag {
player.stop()
}
}
/******** UPDATE *********/
I moved the AVAudioPlayer creation to its own class based on an outdated example I ran across...somewhere. It solved the audio completely cutting out after 15 tones problem, however, the cracks and pops are still there! I also tried rerecording the sounds with no luck.
Here is the class for creating AVAudioPlayers as needed:
import UIKit
import AVFoundation
private var players: [AVAudioPlayer] = []
class AVAudioPlayerPool: NSObject {
class func playerWithURL(url: URL) -> AVAudioPlayer? {
let availablePlayers = players.filter { (player) -> Bool in
return player.isPlaying == false && player.url == url
}
if let playerToUse = availablePlayers.first {
playerToUse.prepareToPlay()
return playerToUse
}
do {
let newPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)
players.append(newPlayer)
newPlayer.prepareToPlay()
return newPlayer
} catch {
return nil
}
}
}
Playing a sound is simple:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "tt1"
case 1:
soundName = "tt2"
case 2:
soundName = "tt3"
case 3:
soundName = "tt4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
let player = AVAudioPlayerPool.playerWithURL(url: url)
player?.play()
}
If anyone has any thoughts - even if it's just the next step you might try...
In my opinion, your main problem is that the sound loading is done inside the same function supposed to play the sound itself. Conceptually is wrong, in order to be able to play the sound as fast as you can, you should load once and for all your sounds outside the "play" function. That function should just play, and that's it.
We've just faced the problem in our team. In our case distorted sound bug can be reproduced only if the app is running on a device with debugger attached. Once we detach the app from debugger crackles disappear.
It also looks like the problem first appeared in new Xcode 9.2 (9C40b).
After many hours of experimentation, I'm going to say that the problem was caused by the sound I had chosen - a big, hollow, square wave synth sound with reverb. I first tried reducing the level thinking that might help. Next I tried all sorts of different sounds, but everything crackled. I think that had more to do with the way I was assigning players. Once I fixed the player problem, I didn't think to go back and try different sounds again. Until, I did...and it works!

AVAudioPlayer will not play audio for one mp3 but will for others?

All of my other audio files play perfectly through the audio player. Most of the files are less than 5 seconds long, and the longest file that still plays is 23 seconds long. For some reason my 1:15 long file "sortSong" is completely silent when it is called.
Here is the code for my audio player:
import Foundation
import AVFoundation
class AudioPlayer {
static let sharedInstance = AudioPlayer()
enum Sound: String {
case sortSongPlay = "sortSong"
case centerButtonRelease = "centerButtonRelease"
case buttonTap = "tapSound"
static var all: [Sound] {
return [.centerButtonRelease, .buttonTap, sortSongPlay]
}
var url: URL {
return URL.init(fileURLWithPath: Bundle.main.path(forResource: self.rawValue, ofType: "mp3")!)
}
}
private var soundMap = [String: SystemSoundID]()
init() {
for sound in Sound.all {
let soundUrl = sound.url
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId);
soundMap[sound.rawValue] = soundId
}
}
func play(sound: Sound) {
AudioServicesPlaySystemSound(soundMap[sound.rawValue]!)
}
}
The sound is played when this function is called in my view controller.
func successfulSort() {
AudioPlayer.sharedInstance.play(sound: .sortSongPlay)
rotateSortImageOne()
rotateSortImageTwo()
}
This is the action that calls the successfulSort func
if inputText == "hello" && seedArray == [1, 4, 1] {
successfulSort()
}
If I simply change the case sortSongPlay to = "shortSortSong" (the 23 second version) it plays just fine.
All of my sound files have their target memberships checked for this project file, and all of the files have the correct path. The audio will play in the interface builder if I press the play button. There are no compiler errors or warnings, the app never crashes, the audio for sortSong simply isn't playing when it is called in the app.
This is a link containing examples I have tried on my project. The first two sounds play silently in the app while the shorter sounds all play perfectly. https://soundcloud.com/spiffystache
What is causing this to be silent?
You should put a correct title on your question.
Your code does not use AVAudioPlayer, but uses System Sound Services.
The documentation clearly states its limitation:
You can use System Sound Services to play short (30 seconds or
shorter) sounds.
I have never checked if its limitation is exactly 30 seconds, but it matches the description in your question.
Consider using AVAudioPlayer (as in the title) to play longer sound.

My AVAudioPlayer stops when transitioning scenes Swift

Okay so originally I was using SKAudioNode in Spritekit to try and continue my music through scenes however I got redirected to use AVFoundation by another person on this site. I'm not all that familiar with the AVFoundation framework. All I want to know is how to continue the music when I transition from scene1 to scene2? The music is all set up and works with the code shown below.
var Music = AVAudioPlayer()
let myFilePathString = NSBundle.mainBundle().pathForResource("Verm - Explode", ofType: "mp3")
if let myFilePathString = myFilePathString {
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do {
try Music = AVAudioPlayer(contentsOfURL: myFilePathURL)
Music.play()
}catch
{
print("Error")
}
}
So how can I continue my song when transitioning scenes? Thanks in advance. Sam. :p
You basically put your code into a helper class (Singleton or with static methods ) so that you can access/play it from anywhere in your project.
This way you have a centralised spot for music playback, which makes your life easier, and it will play regardless of the scene your are in/transitioning too.
e.g Singleton way (class has 1 instance for lifetime of app)
Create a new swift file and add a class similar to this.
class Music {
let sharedInstance = Music()
var music = AVAudioPlayer()
private init() { } // to make it a proper singleton
func prepare() {
// code to prepare your AVAudioPlayers
}
// other methods to play, stop etc
}
Now you can also set up all your audio players at app launch instead when you play them which should also help with performance.
In your gameViewController or AppDelegate you call the prepare method
Music.sharedInstance.prepare()
to set up all your players.
Than you can play them in your SKScene at the correct spot like so
Music.sharedInstance.music.play()
or use some methods you created in the Music helper class e.g
Music.sharedInstance.playGame()
I have a helper on github for a more detailed example.
https://github.com/crashoverride777/Swift-MusicManager
Hope this helps

Resources