web-dev-qa-db-fra.com

Obtenez AVAudioPlayer pour jouer plusieurs sons à la fois

J'essaie d'obtenir plusieurs fichiers sonores à lire sur une instance AVAudioPlayer, mais lorsqu'un son est lu, l'autre s'arrête. Je ne peux pas faire jouer plus d'un son à la fois. Voici mon 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 ()
    }
}

Quelqu'un peut-il m'aider en me disant comment faire jouer plus d'un fichier son à la fois? Toute aide est très appréciée.

Merci beaucoup, Kai

17
Kaimund600

La raison pour laquelle l'audio s'arrête est parce que vous n'avez qu'un seul AVAudioPlayer configuré, donc lorsque vous demandez à la classe de jouer un autre son, vous remplacez actuellement l'ancienne instance par une nouvelle instance d'AVAudioPlayer. Vous l'écrasez essentiellement.

Vous pouvez soit créer deux instances de la classe GSAudio, puis appeler playSound sur chacune d'elles, ou faire de la classe un gestionnaire audio générique qui utilise un dictionnaire de audioPlayers.

Je préfère de loin cette dernière option, car elle permet un code plus propre et est également plus efficace. Vous pouvez vérifier si vous avez déjà créé un lecteur pour le son auparavant, plutôt que d'en créer un nouveau par exemple.

Quoi qu'il en soit, j'ai refait votre classe pour vous afin qu'elle joue plusieurs sons à la fois. Il peut également jouer le même son sur lui-même (il ne remplace pas l'instance précédente du son) J'espère que cela aide!

La classe est un singleton, donc pour accéder à la classe, utilisez:

GSAudio.sharedInstance

par exemple, pour jouer un son que vous appelleriez:

GSAudio.sharedInstance.playSound("AudioFileName")

et pour jouer plusieurs sons à la fois:

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

ou vous pouvez charger les sons dans un tableau quelque part et appeler la fonction playSounds qui accepte un tableau:

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

J'ai également ajouté une fonction playSounds qui vous permet de retarder chaque son joué dans un format en cascade. Alors:

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

jouerait sound2 une seconde après sound1, puis sound3 jouerait une seconde après sound2 etc.

Voici la classe:

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
    }

}
22
Olivier Wilkinson

Voici une Swift 4 du code @Oliver Wilkinson avec des contrôles de sécurité et une mise en forme de code améliorée:

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)
        }
    }

}
7
Makalele

J'ai créé une bibliothèque d'aide qui simplifie la lecture des sons dans Swift. Il crée plusieurs instances d'AVAudioPlayer pour permettre la lecture simultanée du même son plusieurs fois. Vous pouvez le télécharger depuis Github ou l'importer avec Cocoapods.

Voici le lien: SwiftySound

L'utilisation est aussi simple que possible:

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

Toutes les réponses affichent des pages de code; ça n'a pas besoin d'être aussi compliqué.

// 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
                }
            }
        }
2
Bobjt