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.
Related
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.
I am working on a dice game that rolls 4 dice with random numbers assigned to each of them. When I press the roll button, the sound effect plays as intended and the images are replaced, but I am looking for a way to prevent the user from pressing roll until the sound effect is finished (maybe 2 seconds).
This is my function that updates dice images, where I have been testing this problem by adding DispatchTime.now() + 2 to the if statements and before arc4random_uniform, but to no avail:
func updateDiceImages() {
randomDiceIndex1 = Int(arc4random_uniform(6))
randomDiceIndex2 = Int(arc4random_uniform(6))
randomDiceIndex3 = Int(arc4random_uniform(6))
randomDiceIndex4 = Int(arc4random_uniform(6))
randomMultiplier = Int(arc4random_uniform(4))
// determine the operator dice at random
if randomMultiplier == 0 {
addDice()
}
if randomMultiplier == 1 {
subDice()
}
if randomMultiplier == 2 {
divDice()
}
if randomMultiplier == 3 {
multDice()
}
// image changes to random dice index
diceImageView1.image = UIImage(named: diceArray[randomDiceIndex1])
diceImageView2.image = UIImage(named: diceArray[randomDiceIndex2])
diceImageView3.image = UIImage(named: diceArray[randomDiceIndex3])
diceImageView4.image = UIImage(named: diceArray[randomDiceIndex4])
multImageView1.image = UIImage(named: multArray[randomMultiplier])
}
If necessary, here is also my function that plays the sound effect, where I also tried implementing DispatchTime.now() + 2:
func rollSound() {
// Set the sound file name & extension
let alertSound = URL(fileURLWithPath: Bundle.main.path(forResource: "diceRoll", ofType: "mp3")!)
do {
// Preparation
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
// Play the sound
do {
audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
} catch _{
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}
Here is the implementation that I feel is the closest, but I get many errors:
func rollSound() {
// Set the sound file name & extension
let when = DispatchTime.now() + 2 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
let alertSound = URL(fileURLWithPath: Bundle.main.path(forResource: "diceRoll", ofType: "mp3")!)
do {
// Preparation
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
// Play the sound
do {
audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
} catch _{
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
I have updated code for your error.
func rollSound() {
// Set the sound file name & extension
let when = DispatchTime.now() + 2 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
let alertSound = URL(fileURLWithPath: Bundle.main.path(forResource: "diceRoll", ofType: "mp3")!)
do {
// Preparation
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
// Play the sound
do {
self.audioPlayer = try AVAudioPlayer(contentsOf: alertSound)
} catch _{
}
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.play()
}
}
You need to add self to the property if you want to use class instance into DispatchQueue closer. and same for updateDiceImages()
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
So basically I have two audio players while using the AVFoundation. I want the second one to be played right after the first one finishes.
myFilePathString = NSBundle.mainBundle().pathForResource("\(workout[currentExercise])", ofType: "mp3")
#IBAction func startWorkout(sender: AnyObject) {
if let myFilePathString = myFilePathString {
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: myFilePathURL)
audioPlayer.play()
}catch {
print("Error")
}
}
This above one is to be the first
let path = NSBundle.mainBundle().pathForResource("Rest", ofType: "mp3")
let url = NSURL(fileURLWithPath: path!)
do {
let sound = try AVAudioPlayer(contentsOfURL: url)
secondAudioPlayer = sound
sound.play()
} catch {
print("error")
}
...while this one is to be played after. At the moment they play at the same time. Got any ideas for me?
Thank you in advance.
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()