获取 AVAudioPlayer 一次播放多种声音
Get AVAudioPlayer to play multiple sounds at a time
我试图让多个声音文件在 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 ()
}
}
任何人都可以告诉我如何同时播放多个声音文件来帮助我吗?任何帮助深表感谢。
非常感谢,
凯
音频停止的原因是因为您只设置了一个 AVAudioPlayer,所以当您要求 class 播放另一种声音时,您当前正在用 AVAudioPlayer 的新实例替换旧实例。你基本上是在覆盖它。
您可以创建两个 GSAudio class 实例,然后在每个实例上调用 playSound,或者使 class 成为使用 audioPlayers 字典的通用音频管理器。
我更喜欢后一种选择,因为它允许更简洁的代码并且也更高效。您可以检查一下您之前是否已经为声音制作过播放器,而不是制作一个新的播放器。
无论如何,我为您重新制作了 class,这样它就可以同时播放多种声音。它还可以在自身上播放相同的声音(它不会替换之前的声音实例)希望对您有所帮助!
class 是单例,因此要访问 class 使用:
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:
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
}
}
我创建了一个帮助程序库,可以简化 Swift 中的声音播放。它创建 AVAudioPlayer 的多个实例以允许同时多次播放相同的声音。您可以从 Github 下载它或使用 Cocoapods 导入它。
这里是link: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
}
}
}
这是一个 Swift 4 版本的 @Oliver Wilkinson 代码,其中包含一些安全检查和改进的代码格式:
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 5+
整理之前的一些答案,改进代码风格和复用性
我通常在我的项目中避免松散的字符串,而是使用自定义协议来保存这些字符串属性的对象。
与 enum
方法相比,我更喜欢这种方法,因为枚举往往会很快将您的项目结合在一起。每次添加新案例时,您都必须使用枚举编辑同一个文件,这在某种程度上打破了 SOLID 的 Open-Closed 原则并增加了出错的机会。
在这种特殊情况下,您可以有一个定义声音的协议:
protocol Sound {
func getFileName() -> String
func getFileExtension() -> String
func getVolume() -> Float
func isLoop() -> Bool
}
extension Sound {
func getVolume() -> Float { 1 }
func isLoop() -> Bool { false }
}
当你需要一个新的声音时,你可以简单地创建一个新的结构或 class 来实现这个协议(如果你的 IDE,它甚至会在自动完成时被建议,就像 Xcode,支持它,为您提供与枚举类似的好处......并且它在大中型多框架项目中工作得更好)。
(通常我将音量和其他配置保留为默认实现,因为它们不太经常定制)。
例如,您可以发出硬币掉落的声音:
struct CoinDropSound: Sound {
func getFileName() -> String { "coin_drop" }
func getFileExtension() -> String { "wav" }
}
然后,您可以使用一个单例 SoundManager
来管理播放的音频文件
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 { [=12=] == player }
}
}
我在这里创建了一个帮助程序 getAudioPlayer
以便能够 return 尽早执行代码并利用 guard let
.
在大多数情况下,更频繁地使用 guard let
并偏好较少的嵌套代码可以极大地提高可读性。
要在项目的任何地方使用这个 SoundManager
,只需访问它的共享实例并传递一个符合 Sound
.
的对象
例如,给定前面的CoinDropSound
:
SoundManager.shared.play(sound: CoinDropSound())
您可以省略 sound
参数,因为它可以提高可读性
class SoundManager {
// ...
func play(_ sound: Sound) {
// ...
}
// ...
}
然后:
SoundManager.shared.play(CoinDropSound())
我试图让多个声音文件在 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 ()
}
}
任何人都可以告诉我如何同时播放多个声音文件来帮助我吗?任何帮助深表感谢。
非常感谢, 凯
音频停止的原因是因为您只设置了一个 AVAudioPlayer,所以当您要求 class 播放另一种声音时,您当前正在用 AVAudioPlayer 的新实例替换旧实例。你基本上是在覆盖它。
您可以创建两个 GSAudio class 实例,然后在每个实例上调用 playSound,或者使 class 成为使用 audioPlayers 字典的通用音频管理器。
我更喜欢后一种选择,因为它允许更简洁的代码并且也更高效。您可以检查一下您之前是否已经为声音制作过播放器,而不是制作一个新的播放器。
无论如何,我为您重新制作了 class,这样它就可以同时播放多种声音。它还可以在自身上播放相同的声音(它不会替换之前的声音实例)希望对您有所帮助!
class 是单例,因此要访问 class 使用:
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:
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
}
}
我创建了一个帮助程序库,可以简化 Swift 中的声音播放。它创建 AVAudioPlayer 的多个实例以允许同时多次播放相同的声音。您可以从 Github 下载它或使用 Cocoapods 导入它。
这里是link: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
}
}
}
这是一个 Swift 4 版本的 @Oliver Wilkinson 代码,其中包含一些安全检查和改进的代码格式:
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 5+
整理之前的一些答案,改进代码风格和复用性
我通常在我的项目中避免松散的字符串,而是使用自定义协议来保存这些字符串属性的对象。
与 enum
方法相比,我更喜欢这种方法,因为枚举往往会很快将您的项目结合在一起。每次添加新案例时,您都必须使用枚举编辑同一个文件,这在某种程度上打破了 SOLID 的 Open-Closed 原则并增加了出错的机会。
在这种特殊情况下,您可以有一个定义声音的协议:
protocol Sound {
func getFileName() -> String
func getFileExtension() -> String
func getVolume() -> Float
func isLoop() -> Bool
}
extension Sound {
func getVolume() -> Float { 1 }
func isLoop() -> Bool { false }
}
当你需要一个新的声音时,你可以简单地创建一个新的结构或 class 来实现这个协议(如果你的 IDE,它甚至会在自动完成时被建议,就像 Xcode,支持它,为您提供与枚举类似的好处......并且它在大中型多框架项目中工作得更好)。
(通常我将音量和其他配置保留为默认实现,因为它们不太经常定制)。
例如,您可以发出硬币掉落的声音:
struct CoinDropSound: Sound {
func getFileName() -> String { "coin_drop" }
func getFileExtension() -> String { "wav" }
}
然后,您可以使用一个单例 SoundManager
来管理播放的音频文件
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 { [=12=] == player }
}
}
我在这里创建了一个帮助程序 getAudioPlayer
以便能够 return 尽早执行代码并利用 guard let
.
在大多数情况下,更频繁地使用 guard let
并偏好较少的嵌套代码可以极大地提高可读性。
要在项目的任何地方使用这个 SoundManager
,只需访问它的共享实例并传递一个符合 Sound
.
例如,给定前面的CoinDropSound
:
SoundManager.shared.play(sound: CoinDropSound())
您可以省略 sound
参数,因为它可以提高可读性
class SoundManager {
// ...
func play(_ sound: Sound) {
// ...
}
// ...
}
然后:
SoundManager.shared.play(CoinDropSound())