Вам AVAudioPlayer играть несколько звуков одновременно

Я пытаюсь получить несколько звуковых файлов для воспроизведения на экземпляре AVAudioPlayer, однако, когда один звук воспроизводится, другой останавливается. Я не могу воспроизвести больше одного звука за раз. Вот мой код:

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

может ли кто-нибудь помочь мне, рассказав мне, как получить более одного звукового файла для воспроизведения за раз? Любая помощь очень ценится.

большое спасибо, Кай!--2-->

4 ответов


причина остановки звука заключается в том, что у вас настроен только один AVAudioPlayer, поэтому, когда вы просите класс воспроизвести другой звук, вы в настоящее время заменяете старый экземпляр новым экземпляром AVAudioPlayer. Вы переписываете его в основном.

вы можете создать два экземпляра класса GSAudio, а затем вызвать playSound по каждому из них, или сделать класс универсальный аудио менеджер, который использует словарь аудиоплееров.

Я предпочитаю последнее вариант, поскольку он позволяет более чистый код, а также более эффективен. Вы можете проверить, если вы уже сделали плеер для звука раньше, а не сделать новый плеер, например.

в любом случае, я переделал ваш класс для вас, чтобы он воспроизводил несколько звуков сразу. Он также может воспроизводить тот же звук над собой (он не заменяет предыдущий экземпляр звука) надеюсь, что это поможет!

класс является одноэлементным, поэтому для доступа к классу использовать:

GSAudio.sharedInstance

например, для воспроизведения звука вы могли бы назвать:

GSAudio.sharedInstance.playSound("AudioFileName")

и играть несколько звуков одновременно:

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

или вы можете загрузить звуки в массив где-нибудь и вызвать функцию playSounds, которая принимает массив:

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

Я также добавил функцию playSounds, которая позволяет задерживать каждый звук, воспроизводимый в каскадном формате. Итак:

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

будет воспроизводить sound2 через секунду после sound1, затем sound3 будет играть через секунду после sound2 и т. д.

вот класс:

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
    }

}

вот Свифт 4 версия код @Оливер Уилкинсон с некоторыми safechecks и улучшенное форматирование кода:

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

}

Я создал вспомогательную библиотеку, которая упрощает воспроизведение звуков в Swift. Он создает несколько экземпляров AVAudioPlayer, чтобы позволить воспроизводить один и тот же звук несколько раз одновременно. Вы можете скачать его с Github или импортировать с Cocoapods.

вот ссылка: SwiftySound

использование так же просто, как это может быть:

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

все ответы публикуют страницы кода; это не должно быть так сложно.

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