Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 261 Vote(s) - 3.64 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Get AVAudioPlayer to play multiple sounds at a time

#1
I'm trying to get multiple sounds files to play on an AVAudioPlayer instance, however when one sound plays, the other stops. I can't get more than one sound to play at a time. Here is my code:

import AVFoundation

class GSAudio{

static var instance: GSAudio!

var soundFileNameURL: NSURL = NSURL()
var soundFileName = ""
var soundPlay = AVAudioPlayer()

func playSound (soundFile: String){

GSAudio.instance = self

soundFileName = soundFile
soundFileNameURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(soundFileName, ofType: "aif", inDirectory:"Sounds")!)
do{
try soundPlay = AVAudioPlayer(contentsOfURL: soundFileNameURL)
} catch {
print("Could not play sound file!")
}

soundPlay.prepareToPlay()
soundPlay.play ()
}
}

Can anyone please help me by telling me how to get more than one sound file to play at a time? Any help is much appreciated.

Many thanks,
Kai
Reply

#2
# Swift 5+ #
## Compiling some of the previous answers, improving code style and reusability ##

I usually avoid loose strings throughout my projects and use, instead, custom protocols for objects that will hold those string properties.

I prefer this to the `enum` approach simply because enumerations tend to couple your project together quite quickly. Everytime you add a new case you must edit the same file with the enumeration, breaking somewhat the Open-Closed principle from SOLID and increasing chances for error.

In this particular case, you could have a protocol that defines sounds:

```
protocol Sound {
func getFileName() -> String
func getFileExtension() -> String
func getVolume() -> Float
func isLoop() -> Bool
}

extension Sound {
func getVolume() -> Float { 1 }
func isLoop() -> Bool { false }
}
```

And when you need a new sound you can simply create a new structure or class that implements this protocol (It will even be suggested on autocomplete if your IDE, just like Xcode, supports it, giving you similar benefits to those of the enumeration... and it works way better in medium to large multi framework projects).

(Usually I leave volume and other configurations with default implementations as they are less frequently customized).

For instance, you could have a coin drop sound:
```swift
struct CoinDropSound: Sound {
func getFileName() -> String { "coin_drop" }
func getFileExtension() -> String { "wav" }
}
```

Then, you could use a singleton `SoundManager` that would take care of managing playing audio files

```swift
import AVFAudio

final class SoundManager: NSObject, AVAudioPlayerDelegate {
static let shared = SoundManager()

private var audioPlayers: [URL: AVAudioPlayer] = [:]
private var duplicateAudioPlayers: [AVAudioPlayer] = []

private override init() {}

func play(sound: Sound) {
let fileName = sound.getFileName()
let fileExtension = sound.getFileExtension()

guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension),
let player = getAudioPlayer(for: url) else { return }

player.volume = sound.getVolume()
player.numberOfLoops = numberOfLoops
player.prepareToPlay()
player.play()
}

private func getAudioPlayer(for url: URL) -> AVAudioPlayer? {
guard let player = audioPlayers[url] else {
let player = try? AVAudioPlayer(contentsOf: url)
audioPlayers[url] = player
return player
}
guard player.isPlaying else { return player }
guard let duplicatePlayer = try? AVAudioPlayer(contentsOf: url) else { return nil }
duplicatePlayer.delegate = self
duplicateAudioPlayers.append(duplicatePlayer)
return duplicatePlayer
}

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
duplicateAudioPlayers.removeAll { $0 == player }
}
}
```

Here I created a helper `getAudioPlayer` to be able to return early from code execution and make use of the `guard let`.

Using `guard let` more often and preferring less nested code can, most of the time, highly improve readability.

To use this `SoundManager` from anywhere in your project, simply access its shared instance and pass an object that conforms to `Sound`.

For example, given the previous `CoinDropSound`:
```swift
SoundManager.shared.play(sound: CoinDropSound())
```

You could maybe omit the `sound` parameter as it may improve readability
```
class SoundManager {
// ...
func play(_ sound: Sound) {
// ...
}
// ...
}
```
And then:
```
SoundManager.shared.play(CoinDropSound())
```
Reply

#3
Here's a Swift 4 version of @Oliver Wilkinson code with some safechecks and improved code formatting:

import Foundation
import AVFoundation

class GSAudio: NSObject, AVAudioPlayerDelegate {

static let sharedInstance = GSAudio()

private override init() { }

var players: [URL: AVAudioPlayer] = [:]
var duplicatePlayers: [AVAudioPlayer] = []

func playSound(soundFileName: String) {

guard let bundle = Bundle.main.path(forResource: soundFileName, ofType: "aac") else { return }
let soundFileNameURL = URL(fileURLWithPath: bundle)

if let player = players[soundFileNameURL] { //player for sound has been found

if !player.isPlaying { //player is not in use, so use that one
player.prepareToPlay()
player.play()
} else { // player is in use, create a new, duplicate, player and use that instead

do {
let duplicatePlayer = try AVAudioPlayer(contentsOf: soundFileNameURL)

duplicatePlayer.delegate = self
//assign delegate for duplicatePlayer so delegate can remove the duplicate once it's stopped playing

duplicatePlayers.append(duplicatePlayer)
//add duplicate to array so it doesn't get removed from memory before finishing

duplicatePlayer.prepareToPlay()
duplicatePlayer.play()
} catch let error {
print(error.localizedDescription)
}

}
} else { //player has not been found, create a new player with the URL if possible
do {
let player = try AVAudioPlayer(contentsOf: soundFileNameURL)
players[soundFileNameURL] = player
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
}
}


func playSounds(soundFileNames: [String]) {
for soundFileName in soundFileNames {
playSound(soundFileName: soundFileName)
}
}

func playSounds(soundFileNames: String...) {
for soundFileName in soundFileNames {
playSound(soundFileName: soundFileName)
}
}

func playSounds(soundFileNames: [String], withDelay: Double) { //withDelay is in seconds
for (index, soundFileName) in soundFileNames.enumerated() {
let delay = withDelay * Double(index)
let _ = Timer.scheduledTimer(timeInterval: delay, target: self, selector: #selector(playSoundNotification(_:)), userInfo: ["fileName": soundFileName], repeats: false)
}
}

@objc func playSoundNotification(_ notification: NSNotification) {
if let soundFileName = notification.userInfo?["fileName"] as? String {
playSound(soundFileName: soundFileName)
}
}

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if let index = duplicatePlayers.index(of: player) {
duplicatePlayers.remove(at: index)
}
}

}

Reply

#4
All answers are posting pages of code; it doesn't need to be that complicated.

// Create a new player for the sound; it doesn't matter which sound file this is
let soundPlayer = try AVAudioPlayer( contentsOf: url )
soundPlayer.numberOfLoops = 0
soundPlayer.volume = 1
soundPlayer.play()
soundPlayers.append( soundPlayer )

// In an timer based loop or other callback such as display link, prune out players that are done, thus deallocating them
checkSfx: for player in soundPlayers {
if player.isPlaying { continue } else {
if let index = soundPlayers.index(of: player) {
soundPlayers.remove(at: index)
break checkSfx
}
}
}

Reply

#5
I have created a helper library that simplifies playing sounds in Swift. It creates multiple instances of AVAudioPlayer to allow playing the same sound multiple times concurrently. You can download it from Github or import with Cocoapods.

Here is the link: [SwiftySound][1]

The usage is as simple as it can be:

Sound.play(file: "sound.mp3")


[1]:

[To see links please register here]

Reply

#6
The reason the audio stops is because you only have one AVAudioPlayer set up, so when you ask the class to play another sound you are currently replacing the old instance with a new instance of AVAudioPlayer. You are overwriting it basically.

You can either create two instances of the GSAudio class, and then call playSound on each of them, or make the class a generic audio manager that uses a dictionary of audioPlayers.

I much prefer the latter option, as it allows for cleaner code and is also more efficient. You can check to see if you have already made a player for the sound before, rather than making a new player for example.

Anyways, I re-made your class for you so that it will play multiple sounds at once. It can also play the same sound over itself (it doesn't replace the previous instance of the sound) Hope it helps!

The class is a singleton, so to access the class use:

GSAudio.sharedInstance

for example, to play a sound you would call:

GSAudio.sharedInstance.playSound("AudioFileName")

and to play a number of sounds at once:

GSAudio.sharedInstance.playSounds("AudioFileName1", "AudioFileName2")

or you could load up the sounds in an array somewhere and call the playSounds function that accepts an array:

let sounds = ["AudioFileName1", "AudioFileName2"]
GSAudio.sharedInstance.playSounds(sounds)

I also added a playSounds function that allows you to delay each sound being played in a cascade kind of format. So:

let soundFileNames = ["SoundFileName1", "SoundFileName2", "SoundFileName3"]
GSAudio.sharedInstance.playSounds(soundFileNames, withDelay: 1.0)

would play sound2 a second after sound1, then sound3 would play a second after sound2 etc.

Here is the class:

class GSAudio: NSObject, AVAudioPlayerDelegate {

static let sharedInstance = GSAudio()

private override init() {}

var players = [NSURL:AVAudioPlayer]()
var duplicatePlayers = [AVAudioPlayer]()

func playSound (soundFileName: String){

let soundFileNameURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(soundFileName, ofType: "aif", inDirectory:"Sounds")!)

if let player = players[soundFileNameURL] { //player for sound has been found

if player.playing == false { //player is not in use, so use that one
player.prepareToPlay()
player.play()

} else { // player is in use, create a new, duplicate, player and use that instead

let duplicatePlayer = try! AVAudioPlayer(contentsOfURL: soundFileNameURL)
//use 'try!' because we know the URL worked before.

duplicatePlayer.delegate = self
//assign delegate for duplicatePlayer so delegate can remove the duplicate once it's stopped playing

duplicatePlayers.append(duplicatePlayer)
//add duplicate to array so it doesn't get removed from memory before finishing

duplicatePlayer.prepareToPlay()
duplicatePlayer.play()

}
} else { //player has not been found, create a new player with the URL if possible
do{
let player = try AVAudioPlayer(contentsOfURL: soundFileNameURL)
players[soundFileNameURL] = player
player.prepareToPlay()
player.play()
} catch {
print("Could not play sound file!")
}
}
}


func playSounds(soundFileNames: [String]){

for soundFileName in soundFileNames {
playSound(soundFileName)
}
}

func playSounds(soundFileNames: String...){
for soundFileName in soundFileNames {
playSound(soundFileName)
}
}

func playSounds(soundFileNames: [String], withDelay: Double) { //withDelay is in seconds
for (index, soundFileName) in soundFileNames.enumerate() {
let delay = withDelay*Double(index)
let _ = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(playSoundNotification(_:)), userInfo: ["fileName":soundFileName], repeats: false)
}
}

func playSoundNotification(notification: NSNotification) {
if let soundFileName = notification.userInfo?["fileName"] as? String {
playSound(soundFileName)
}
}

func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
duplicatePlayers.removeAtIndex(duplicatePlayers.indexOf(player)!)
//Remove the duplicate player once it is done
}

}
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through