Get AVAudioPlayer to play multiple sounds at a time

前端 未结 4 392
没有蜡笔的小新
没有蜡笔的小新 2020-12-02 17:44

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 t

相关标签:
4条回答
  • 2020-12-02 18:11

    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

    The usage is as simple as it can be:

    Sound.play(file: "sound.mp3")
    
    0 讨论(0)
  • 2020-12-02 18:21

    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)
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-02 18:23

    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
        }
    
    }
    
    0 讨论(0)
  • 2020-12-02 18:31

    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
                    }
                }
            }
    
    0 讨论(0)
提交回复
热议问题