How to Play Multiple Audio Files Simultaneously Using Avplayer

Play multiple Audio Files with AVPlayer

For every sound you want to make make a new AVPlayer.

NSURL *url = [NSURL URLWithString:pathToYourFile];

AVPlayer *audioPlayer = [[AVPlayer alloc] initWithURL:url];


[audioPlayer play];

How to play multiple Audio Files simultaneously using AVPlayer?

You would be better off using AVAudioPlayerNode, AVAudioMixerNode, AVAudioEngine. Using these classes you won't have problems like you have right now. It's also not that difficult to set up.

You can check out my gist, in order to play the sounds in your Playgrounds you would need to put audio files into Resources folder in Project Navigator:
https://gist.github.com/standinga/24342d23acfe70dc08cbcc994895f32b

The code works without stopping background audio when top sounds are triggered.

Here's also the same code:

import AVFoundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class AudioPlayer {
var backgroundAudioFile:AVAudioFile
var topAudioFiles: [AVAudioFile] = []
var engine:AVAudioEngine
var backgroundAudioNode: AVAudioPlayerNode
var topAudioAudioNodes = [AVAudioPlayerNode]()
var mixer: AVAudioMixerNode
var timer: Timer!
var urls: [URL] = []

init (_ url: URL, urls: [URL] = []) {
backgroundAudioFile = try! AVAudioFile(forReading: url)
topAudioFiles = urls.map { try! AVAudioFile(forReading: $0) }

engine = AVAudioEngine()
mixer = AVAudioMixerNode()

engine.attach(mixer)
engine.connect(mixer, to: engine.outputNode, format: nil)
self.urls = urls
backgroundAudioNode = AVAudioPlayerNode()
for _ in topAudioFiles {
topAudioAudioNodes += [AVAudioPlayerNode()]
}
}

func start() {
engine.attach(backgroundAudioNode)
engine.connect(backgroundAudioNode, to: mixer, format: nil)
backgroundAudioNode.scheduleFile(backgroundAudioFile, at: nil, completionHandler: nil)

try! engine.start()
backgroundAudioNode.play()

for node in topAudioAudioNodes {
engine.attach(node)
engine.connect(node, to: mixer, format: nil)
try! engine.start()
}
// simulate rescheduling files played on top of background audio
DispatchQueue.global().async { [unowned self] in
for i in 0..<1000 {
sleep(2)
let index = i % self.topAudioAudioNodes.count
let node = self.topAudioAudioNodes[index]
node.scheduleFile(self.topAudioFiles[index], at: nil, completionHandler: nil)
node.play()
}
}
}
}

let bundle = Bundle.main
let beepLow = bundle.url(forResource: "beeplow", withExtension: "wav")!
let beepMid = bundle.url(forResource: "beepmid", withExtension: "wav")!
let backgroundAudio = bundle.url(forResource: "backgroundAudio", withExtension: "wav")!
let audioPlayer = AudioPlayer(backgroundAudio, urls: [beepLow, beepMid])
audioPlayer.start()

Swift 5: AVPlayer layers multiple audio files on each other

Release/free and stop AVPlayer and AVPlayerItem before going back to your table view controller

avPlayer.pause()
avPlayer.cancelPendingPrerolls() // stops network requests
avPlayer.replaceCurrentItem(with: nil)

Playing multiple audio files using AVAudioPlayer

EDIT: This is the method for AVAudioPlayer

So basically:

1: Add only one player, remove the rest of the players.

2: Create an NSMutableArray with your objects that you want to play.

3: Depending on what button gets pressed, you can re-arrange the NSMutableArray objects to set the correct order as you wish. (Simply recreate the array if you want to make it easy) And start playing the firstObject (url) in the array adding it accordingly to your player.

4: When the player finished playing, you can start playing the next object adding it to your player from the NSMutableArray the same way as done above.

Following above steps will avoid having multiple players playing simultaniously, together with other benefits (like avoid loading multiple URLs at the same time etc)

You may need some variable to check the currentPlayingObject to determine which one is next, maybe an int, and check that you don't try to load a new item from the array if it is the last item playing, to avoid crash on accessing objectAtIndex that does not exist.

Get AVAudioPlayer to play multiple sounds at a time

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
}

}

How to play multiple audio files in a row with AVAudioPlayer?

For every song you want to make make a single AVPlayer.

NSURL *url = [NSURL URLWithString:pathToYourFile];

AVPlayer *audioPlayer = [[AVPlayer alloc] initWithURL:url];


[audioPlayer play];

You can get a Notification when the player ends. Check AVPlayerItemDidPlayToEndTimeNotification when setting up the player:

  audioPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[audioPlayer currentItem]];

this will prevent the player to pause at the end.

in the notification:

- (void)playerItemDidReachEnd:(NSNotification *)notification
{
// start your next song here
}

You can start your next song as soon as you get a notification that the current playing song is done. Maintain some counter which is persistent across selector calls. That way using counter % [songs count] will give you an infinite looping playlist :)

Don't forget un unregister the notification when releasing the player.

Play multiple audio files in sequence as one

You can use AVQueuePlayer for achieving this. You need to create AVPlayerItem using your files url and then need to initialize the AVQueuePlayer using it's queuePlayerWithItems: method.

AVPlayerItem *firstItem  = [[AVPlayerItem alloc] initWithURL:firstPartURL];
AVPlayerItem *secondItem = [[AVPlayerItem alloc] initWithURL:secondPartURL];
AVPlayerItem *thirdItem = [[AVPlayerItem alloc] initWithURL:thirdPartURL];

NSArray *itemsToPlay = [NSArray arrayWithObjects:firstItem, secondItem, thirdItem, nil];
AVQueuePlayer *qPlayer = [AVQueuePlayer queuePlayerWithItems:itemsToPlay];

[qPlayer play];


Related Topics



Leave a reply



Submit