AVAudioSession/AVQueuePlayer/AVAudioPlayer in the Background Swift - ios

Problem: I am trying to get my audio to continue to play even when the app is in the background. The desired result would be that the audio continues to play even when: the home button is pressed, the phone sleeps/is locked, and to be able to mix with the other sounds (native music app, etc). I have looked at the other examples and have tried implementing them but without success. After starting the app action, my app plays a short sound and then waits a period of time, which it then plays another sound.
Things I have tried -
AVAudioPlayer not responding in the background
How to Play Audio in Background Swift?
Things I have learned so far is that I probably want to use the AVAudioSession with the Ambient category so that my sound mixes with the native music app. I have turned on the background capabilities in the tab and in the plist. I have tried putting the AVAudioSession in multiple places (delegate/VC file).
I am using the code from #2 to start my audioSession. Here is the code that plays the sound
func playComboSound(fileName:String){
if(fileName != "burpees" && fileName != "pushUps" && fileName != "sitUps")
{
let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: "mp3")!
do {
appDelegate.audioPlayer = try AVAudioPlayer(contentsOfURL: url)
//guard let appDelegate.audioPlayer = audioPlayer else { return }
//appDelegate.audioPlayer!.stop()
appDelegate.audioPlayer!.prepareToPlay()
appDelegate.audioPlayer!.play()
} catch let error as NSError {
print(error.description)
}
}
else
{
let numRepAudio: String
switch numReps{
case "THREE":
numRepAudio = "three"
case "FIVE":
numRepAudio = "five"
case "TEN":
numRepAudio = "ten"
default:
numRepAudio = "three"
}
let url1 = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(numRepAudio, ofType: "mp3")!)
let url2 = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fileName, ofType: "mp3")!)
print(numRepAudio)
print(fileName)
let item1 = AVPlayerItem(URL: url1)
let item2 = AVPlayerItem(URL: url2)
let items: [AVPlayerItem] = [item1, item2]
appDelegate.playerQueue = AVQueuePlayer(items: items)
appDelegate.playerQueue!.play()
}
}
Thank you

Related

Code after 'return' will never be executed

I'm in my first week of coding.
So far the youtube tutorial app I am making in swift is going perfectly apart from the above error message. Here is the code. I have rewritten this twice now exactly as they specify.
I am trying to set up sounds to play with the card game we are making, the sounds play when cards are shuffled, turned around, matched or incorrectly matched.
The error message is shown for "let soundURL" line of code, "Code after 'return' will never be executed".
Please help?
class SoundManager {
static var audioPlayer:AVAudioPlayer?
enum SoundEffect {
case flip
case shuffle
case match
case nomatch
}
static func playSound(_ effect:SoundEffect) {
var soundFilename = ""
// Determine which sound effect we want to play
//and set the appropriate file name
switch effect {
case .flip:
soundFilename = "cardflip"
case .shuffle:
soundFilename = "cardflip"
case .match:
soundFilename = "dingcorrect"
case.nomatch:
soundFilename = "dingwrong"
}
// Get the path to the sound file inside the bundle
let bundlePath = Bundle.main.path(forResource: soundFilename, ofType: "wav")
guard bundlePath != nil else {
print("Couldn't find sound file \(soundFilename) in the bundle")
return
// Create a URL object from this string path
let soundURL = URL(fileURLWithPath: bundlePath!)
do {
// Create audio player object
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
// Play the sound
audioPlayer?.play()
}
catch {
// Could'nt create audio player object, log the error
print("Could'nt create the audio player object for sound file \(soundFilename)")
}
}
}
}
I believe you want your code to look something like this:
// Get the path to the sound file inside the bundle
let bundlePath = Bundle.main.path(forResource: soundFilename, ofType: "wav")
guard bundlePath != nil else {
print("Couldn't find sound file \(soundFilename) in the bundle")
return
}
// Create a URL object from this string path
let soundURL = URL(fileURLWithPath: bundlePath!)
do {
// Create audio player object
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
// Play the sound
audioPlayer?.play()
}
catch {
// Could'nt create audio player object, log the error
print("Could'nt create the audio player object for sound file \(soundFilename)")
}
If there is a guard statement with return inside its brackets, you do not want to put any code beneath the return and within the same brackets. None of the code below the return will execute. In my example, the following code is outside of the brackets, meaning it will execute so long as there is a value for bundlePath.
Code after 'return' will never be executed
var soundFilename = ""
// Determine which sound effect we want to play
//and set the appropriate file name
switch effect {
case .flip:
soundFilename = "cardflip"
case .shuffle:
soundFilename = "cardflip"
case .match:
soundFilename = "dingcorrect"
case.nomatch:
soundFilename = "dingwrong"
}
// Get the path to the sound file inside the bundle
let bundlePath = Bundle.main.path(forResource: soundFilename, ofType: "wav")
guard bundlePath != nil else {
print("Couldn't find sound file \(soundFilename) in the bundle")
return
}
// Create a URL object from this string path
let soundURL = URL(fileURLWithPath: bundlePath!)
do {
// Create audio player object
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
// Play the sound
audioPlayer?.play()
}
catch {
// Could'nt create audio player object, log the error
print("Could'nt create the audio player object for sound file \(soundFilename)")
}
// }
}
I believe that you have misunderstood the guard syntax as it will execute the code when the condition is correct and will fall in else part when the condition is false. So therefore it will use return in else with no other code , So that the method will be terminated gracefully without giving any crash.
The return keyword is used to return something with the method execution or to exit from the method.
So you need to close the else part with “}” and remove one below catch block. Hope this helps.

Play a sound file on repeat in Swift?

I have had success playing sounds by pressing a button, by using the following code. However, I'd like to press a button and have that sound play in a loop/infinitely. How is this acheived? I'd also like to acheive pause/play functionality as well. Thanks in advance.
#IBAction func keyPressed(_ sender: UIB`utton) {
playSound(soundName: sender.currentTitle!)
}
func playSound(soundName: String) { //
let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
} // End of Play Sound
By default AVAudioPlayer plays its audio from start to finish then stops, but we can control how many times to make it loop by setting its numberOfLoops property. For example, to make your audio play three times in total, you’d write this:
player.numberOfLoops = 3
if you want the infinite loop then use
player.numberOfLoops = -1
for e.g
func playSound(soundName: String) { //
let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.numberOfLoops = -1 // set your count here
player.play()
} // End of Play Sound
var audioPlayer: AVAudioPlayer?
func startBackgroundMusic() {
if let bundle = Bundle.main.path(forResource: "Guru_Nanak_Sahib_Ji", ofType: "mp3") {
let backgroundMusic = NSURL(fileURLWithPath: bundle)
do {
audioPlayer = try AVAudioPlayer(contentsOf:backgroundMusic as URL)
guard let audioPlayer = audioPlayer else { return }
audioPlayer.numberOfLoops = -1 // for infinite times
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print(error)
}
}
}
You can retain the player. Then in the player's completion delegate callback, start playing again. Or stop the player at any time, since you've retained a reference to it.

Play audio and vibration simultaneously on ios using swift

Is there any way to play audio file and vibrate iphone when audio is started and stop vibrating when audio stops playing?
currently this code is working but it plays audio and when audio stops then it vibrates the phone, I want both (audio and vibration) play together.
var alertSoundEffect: AVAudioPlayer?
let path = Bundle.main.path(forResource: "alert.mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
alertSoundEffect = try AVAudioPlayer(contentsOf: url)
alertSoundEffect?.prepareToPlay()
alertSoundEffect?.play()
} catch {
return
}
let vib = SystemSoundID(1519)
AudioServicesPlayAlertSound(SystemSoundID(vib))
AudioServicesRemoveSystemSoundCompletion(vib)
You can try
DispatchQueue.global().async
{
var alertSoundEffect: AVAudioPlayer?
let path = Bundle.main.path(forResource: "alert.mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
alertSoundEffect = try AVAudioPlayer(contentsOf: url)
alertSoundEffect?.prepareToPlay()
alertSoundEffect?.play()
} catch {
return
}
}

Auto play audio files in Swift

I'm making a very simple app in which the user get to select some audio files he wants to play.
Then, he pushes the Play button and, it would play all the files selected one after another.
To do so, I made an array that stores the name of the files the users wants to play. Then, a function triggered by the Play button would make a iteration of the array, look for the names and play the files.
It's something like this :
//declaring the audioplayer
var audioPlayer = AVAudioPlayer()
//declaring the array
var selectedMusic = [String]()
The interface got 6 buttons, one of each is linked to a specific music. The user can trigger the music instantly by tapping the button, or make a selection for a sequential play by a long press. Let's assume that the user got a selection and the array would look something like this :
selectedMusic = ["Song 1", "Song 5", "Song 3", "Song 2"]
My function to play the songs is :
for selection in selectedMusic {
if selection == "Song 1" {
guard let alertSound = Bundle.main.url(forResource: "Song 1", withExtension: "mp3") else {return}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch let error {
print(error.localizedDescription)
}
} else if selection == "Song 2" {
guard let alertSound = Bundle.main.url(forResource: "Song 2", withExtension: "mp3") else {return}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch let error {
print(error.localizedDescription)
}
Etc, until Song 5.
What I want is to play the selected songs in a specific order (1 to 5) and not the order the user chose (it's important).
Yet, this function is not giving the expected results, it's just playing the last item of the selection.
So, how should I make this work ? Is the array not the best option here ?
As i mentioned, the fact that the songs are played in chronological order is mandatory.
Any help would be greatly appreciated :)
Thanks !
Well first, you're using a for and repeating the code inside it.. in theory you could just do
audioUrls.forEach { (url) in
guard let alertSound = Bundle.main.url(forResource: url, withExtension: "mp3") else {return}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch let error {
print(error.localizedDescription)
}
}
second, you're not tracking the audio so your program does not know that the track finished playing and you're overwriting the player with a new object.
For instance, in the following code, I am keeping track of my audio player and updating a label with the current time.
func keepTrackOfAudio() {
audioPlayer?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
//track progress
let interval = CMTime(value: 1, timescale: 2)
audioPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main , using: { (progressTime) in
let seconds = CMTimeGetSeconds(progressTime)
let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60)))
let minutesString = String(format: "%02d", Int(seconds / 60))
self.audioCurrentTimeLabel.text = "\(minutesString):\(secondsString)"
//lets move the slider thumb
if let duration = self.audioPlayer?.currentItem?.duration {
let durationSeconds = CMTimeGetSeconds(duration)
self.audioSlider.value = Float(seconds / durationSeconds)
}
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let duration = audioPlayer?.currentItem?.duration {
let seconds = CMTimeGetSeconds(duration)
let secondsText = String(format: "%02d", Int(seconds) % 60)
let minutesText = String(format: "%02d", Int(seconds) / 60)
audioLengthLabel.text = "\(minutesText):\(secondsText)"
}
}
}
your homework is to calculate when the audio has reached the end and only then, play the next track.

Play audio file in a sequence in swift

I have two functions. First one loops list of names for audio to be played, and the second one is the actual player function. The problem is that it plays only the last one. It does not play one by one waiting till the next is finished. How it possible to perform in a sequence? I tried to add: dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), { }) ... still does not work. Any possible solution. Thank you in advance!
func startGame(){
audioPlay("NowListen")
for i in 0...self.soundArray.count - 1 {
print(self.soundArray[i])
self.audioPlay(self.soundArray[i])
}
}
func audioPlay(name: String){
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("\(name)", ofType: "mp3")!)
print(alertSound)
// try to play sound
do{
self.audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
self.audioPlayer.prepareToPlay()
self.audioPlayer.play()
} catch {
}
}
Use AVQueuePlayer ( init it with Items at the start and it will play in queue, one after another ) class instead of AVAudioPlayer.
In your Start Game, you can do following:
var audioItems: [AVPlayerItem] = []
for audioName in soundsArray {
let url = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(audioName, ofType: "mp3")!)
let item = AVPlayerItem(URL: url)
audioItems.append(item)
}
let player = AVQueuePlayer(items: audioItems)
player.play()

Resources