【问题标题】:Get AVAudioPlayer to play multiple sounds at a time获取 AVAudioPlayer 一次播放多个声音
【发布时间】:2016-08-20 07:11:36
【问题描述】:

我正在尝试让多个声音文件在 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 ()
    }
}

谁能帮助我,告诉我如何一次播放多个声音文件?任何帮助深表感谢。

非常感谢, 启

【问题讨论】:

  • 你参加过我的课吗?
  • @OlivierWilkinson 我确实试过你的课,如果你想同时开始两种声音,这很好,但我想要这样,当第二个声音开始播放时,我不想要它突然停止已经播放的声音。感谢您的帮助
  • 我不确定我是否理解问题。
  • 声音同时播放,它们不会相互停止。如果你想单独调用声音,你可以调用 playSound() 而不是 playSounds()。即使使用 playSound() 也不会停止之前的声音。
  • 没问题,我会在接下来的一个小时左右编辑我的答案以包含该场景:)

标签: ios swift avfoundation avaudioplayer


【解决方案1】:

这是@Oliver Wilkinson 代码的 Swift 4 版本,其中包含一些安全检查和改进的代码格式:

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 4。
  • 播放效果很好,但是我将如何停止所有延迟调用的播放器,似乎只有当前在播放器字典 [URL: AVAudioPlayer] 中播放的播放器被停止,因为另一个(未来)由于延迟,玩家尚未实例化。 (例如,在创建玩家之前停止它不会做任何事情)。我错过了什么吗?
  • 我在尝试使用此类时遇到错误。任何想法? App/SceneDelegate.swift:140:31: 'GSAudio' initializer is inaccessible due to 'private' protection level
  • 尝试在类声明之前添加'public'关键字。
【解决方案2】:

所有答案都是张贴代码页;它不需要那么复杂。

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

【讨论】:

  • 由于某种原因,在一个非常快的循环中,其他答案不起作用,但你的答案却是。谢谢
  • soundPlayers - 是一个数组吗?
  • 是的 soundPlayers 是一个数组。
【解决方案3】:

我创建了一个帮助库来简化在 Swift 中播放声音的过程。它创建多个 AVAudioPlayer 实例以允许同时多次播放相同的声音。您可以从 Github 下载或使用 Cocoapods 导入。

这里是链接:SwiftySound

用法很简单:

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

【讨论】:

  • 你是英雄我的朋友
【解决方案4】:

音频停止的原因是因为您只设置了一个 AVAudioPlayer,因此当您要求类播放另一种声音时,您当前正在用新的 AVAudioPlayer 实例替换旧实例。您基本上是在覆盖它。

您可以创建 GSAudio 类的两个实例,然后在每个实例上调用 playSound,或者使该类成为使用 audioPlayers 字典的通用音频管理器。

我更喜欢后一种选择,因为它允许更简洁的代码并且效率更高。您可以检查您之前是否已经为声音制作了播放器,而不是例如制作新播放器。

无论如何,我为您重新制作了您的课程,以便它可以同时播放多种声音。它也可以在自身上播放相同的声音(它不会替换之前的声音实例)希望对您有所帮助!

这个类是一个单例,所以要访问这个类使用:

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)

会在 sound1 之后播放 sound2,然后 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
    }

}

【讨论】:

  • 感谢您的编辑,但是当我尝试使用此类时,我得到:由于信号而导致命令失败:分段错误:11。请问您知道如何解决这个问题吗?非常感谢
  • 是的,我自己也看到了,我盲目地复制了一个与另一个函数冲突的函数。两秒
  • 完全没问题!我将编辑答案以仅包含最后一堂课(目前有点长!哈哈)
  • 这适用于您只打算发出一些声音的简单应用程序......但是如果您想要快速机枪射击之类的东西,例如,您的应用程序会因不断的磁盘访问而陷入困境不必要地反复加载相同的声音。需要提前在内存中缓存声音,然后开始停止播放器。
  • 你的解释对我来说很有意义,所以我添加了第二个音频播放器var audioPlayer = AVAudioPlayer() var secondAudioPlayer = AVAudioPlayer() 并通过self.secondAudioPlayer = try AVAudioPlayer(contentsOf: self.AlarmEnd) self.secondAudioPlayer.play() 播放了我的第二个声音,它起作用了。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
相关资源
最近更新 更多