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
Multiple Bottom Sheets - the Content Doesn't Load Swiftui
Create Codable Struct with Generic Type
Iso8601 Date JSON Decoding Using Swift4
Swift Convert String to Unsafemutablepointer<Int8>
Difference Between Using Objectidentifier() and '===' Operator
How to Retrieve a Random Object from Firebase Using a Sequential Id
Swift Parsing Attribute Name for Given Elementname
Uiscrollview with Embedded Uiimageview; How to Get the Image to Fill the Screen
Swift Combine: How to Create a Single Publisher from a List of Publishers
Convert Generic Free Function into Array Extension
How to Install Xcode on an External Hard Drive Along with the iPhone Simulator.App
Macos Menubar Application: Main Menu Not Being Displayed
How to Open a Nspopover at a Distance from the System Bar
Value of Optional Type Cgfloat Not Unwrapped Error in Swift
How to Link a .Sks File to a .Swift File in Spritekit
Implementing a Function with a Default Parameter Defined in a Protocol