xcode - stop audio files from playing over each other - ios

So I am new to xcode and programming in general. I am creating a simple soundboard and I am running into an issue. What I want to do is stop the current audio from playing when a different audio button is selected. Then have the stopped audio reset itself and be ready to start playing from the beginning when pressed again. I had added a stop() to my code and it would stop() the audio from playing but when I would try to restart it, I discovered that the audio just paused, and continued from the where it stopped.
Can anyone provide me with the code set that I need to fix this, and if not can anyone point me in the direction of a good tutorial that can lead me down the right path. I looked at the apple Audio documentation and unfortunately I got lost.
My code is as follows.
var SoundPlayer1:AVAudioPlayer = AVAudioPlayer()
var SoundPlayer2:AVAudioPlayer = AVAudioPlayer()
var SoundPlayer3:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let FileLocation1 = Bundle.main.path(forResource: "Audio1", ofType: "mp3")
let FileLocation2 = Bundle.main.path(forResource: "Audio2", ofType: "mp3")
let FileLocation3 = Bundle.main.path(forResource: "Audio3", ofType: "mp3")
do {
SoundPlayer1 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation1!))
SoundPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation2!))
SoundPlayer3 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation3!))
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
}
catch {
print(error)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func catButton(_ sender: AnyObject) {
SoundPlayer1.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
SoundPlayer2.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
SoundPlayer3.play()
}

This is just a bit of Swift logic you can carry out to check if any other of the players are playing and then stop them!
// Make sure all audio playback times are reset
func resetAllAudioTime(){
SoundPlayer1.currentTime = 0.0;
SoundPlayer2.currentTime = 0.0;
SoundPlayer3.currentTime = 0.0;
}
IBAction func catButton(_ sender: AnyObject) {
// Check SoundPlayer2 and stop if playing
if(SoundPlayer2.playing){
SoundPlayer2.pause();
}
// Check SoundPlayer3 and stop if playing
if(SoundPlayer3.playing){
SoundPlayer3.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer1.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
// Check SoundPlayer1 and stop if playing
if(SoundPlayer1.playing){
SoundPlayer1.pause();
}
// Check SoundPlayer3 and stop if playing
if(SoundPlayer3.playing){
SoundPlayer3.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer2.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
// Check SoundPlayer1 and stop if playing
if(SoundPlayer1.playing){
SoundPlayer1.pause();
}
// Check SoundPlayer2 and stop if playing
if(SoundPlayer2.playing){
SoundPlayer2.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer3.play()
}
Note: This code is untested but should work. See the AVAudioPlayer docs for any further help.
You will see that rather than calling the AVAudioPlayer.stop() method, I have used the AVAudioPlayer.pause() and then reset the playback time to the start. If you read the documentation it states calling the AVAudioPlayer.stop() method:
func stop()
Stops playback and undoes the setup needed for playback.
Therefore, calling pause and resetting the time doesn't keep undoing the setup needed for playback (should be more efficient!).
I hope this helps! :-)

WoodyDev, thank you for the info it was clean and easy to understand. I was able to fix my issue with two functions.
func stopAll() {
SoundPlayer1.stop()
SoundPlayer2.stop()
}
func resetAudioTime() {
SoundPlayer1.currentTime = 0.0;
SoundPlayer2.currentTime = 0.0;
}
#IBAction func catButton(_ sender: AnyObject) {
stopAll();<br/>
resetAudioTime();<br/>
SoundPlayer1.play()
}

Related

AVAudioPlayer doesn't stop while playing

I'm working on developing an app that plays narration by playing sentence-by-sentence sound file one after another.
With below code, it played as expected. However, after adding "Stop" button to stop what's playing, I found that "Stop" button didn't stop the sound.
I tested the "Stop" button before pressing "Play" button, which worked no problem (message was printed). However, after pressing "Play" and while NarrationPlayer is playing, "Stop" button didn't work (no message was printed).
Any idea what's wrong?
import UIKit
import AVFoundation
class ViewController: UIViewController,AVAudioPlayerDelegate {
var NarrationPlayer:AVAudioPlayer = AVAudioPlayer()
var soundlist: [String] = []
var counter = 0
}
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
#IBAction func play(_ sender: Any) {
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
playSound("first")
while counter < soundlist.count{
if NarrationPlayer.isPlaying == true{
}
else{
playSound(soundlist[counter])
counter += 1
}
}
}
#IBAction func StopPlay(_ sender: Any) {
print("stop button worked")
}
The problem you're running into is that this line here:
while counter < soundlist.count{
is hogging the main thread, keeping any click on your "Stop Playing" button from actually firing.
You've set a delegate method though, and one of the very handy things you can do here is increment your counter and play your next sound file each time a sound file finishes up.
Something like this:
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
playSound(self.soundlist[counter])
counter += 1
}
#IBAction func play(_ sender: Any) {
playSound("first")
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
}
One last piece of advice:
Change the name NarrationPlayer to narrationPlayer. Variables in Swift, like in Objective-C, should start with lowercase (also known as lowerCamelCase).

How To Make UISlider match Audio progress

I am creating a simple music app, and I was wondering how I can make a UiSlider to follow the progress of a audio file. Here's my project so far:
Code:
import UIKit
import AVFoundation
class SongDetailViewController: UITableViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Song Name", ofType: "mp3")!))
audioPlayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
}
catch {
print(error)
}
}
// Buttons
// Dismiss
#IBAction func dismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// Play
#IBAction func play(_ sender: Any) {
audioPlayer.stop()
audioPlayer.play()
}
// Pause
#IBAction func pause(_ sender: Any) {
audioPlayer.pause()
}
// Restart
#IBAction func restart(_ sender: Any) {
audioPlayer.currentTime = 0
}
}
I'm wanting to create the uislider similar to the Apple Music app where it follows the audio file's progress and whenever the user slides the ball thing (lol) it goes to that time of the song. If you could give me some code to complete this, that would be amazing!
Please keep in mind that I am fairly new to coding and am still learning swift, so keep it simple :-) Thanks again!
If you switch to using an AVPlayer, you can add a periodicTimeObserver to your AVPlayer. In the example below you'll get a callback every 1/30 second…
let player = AVPlayer(url: Bundle.main.url(forResource: "Song Name", withExtension: "mp3")!)
player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 30), queue: .main) { time in
let fraction = CMTimeGetSeconds(time) / CMTimeGetSeconds(player.currentItem!.duration)
self.slider.value = fraction
}
Where you create an audioPlayer in your code, replace with the code above.
Using AVAudioPlayer you could create a periodic timer that fires several times a second (up to 60 times/second - any more would be a waste) and updates your slider based on your audio player's currentTime property. To sync the update with screen refresh you could use a CADisplayLink timer.
Edit:
This part of my answer doesn't work:
It should also be possible to set up a Key Value Observer on your
AVAudioPlayers currentTime property so that each time the value
changes your observer fires. (I haven't tried this, but it should
work.)

Problems playing multiple sounds with TheAmazingAudioEngine

I'm implementing a low-latency drum set with TheAmazingAudioEngine framework. I have a scene with a single button and a viewController with the methods below. This code works very well if I touch the button slowly. But if I touch it many times in a short period of time --- 10 times per second, for instance ---, the sound is not played in some touches, without error messages. The audio sample is short (less than 2 seconds).
Why does it happen? What is wrong in my implementation?
I choose TheAmazingAudioEngine instead of AVAudioPlayer to get low-latency between the touch and the sound.
override func viewDidLoad() {
super.viewDidLoad()
// Enable multiple touch for the button
for v in view.subviews {
if v.isKindOfClass(UIButton) {
v.multipleTouchEnabled = true
}
}
// Init audio
audioController = AEAudioController(audioDescription: AEAudioController.nonInterleavedFloatStereoAudioDescription())
audioURL = NSBundle.mainBundle().URLForResource("shortSound", withExtension: "wav")!
do {
try audioController?.start()
} catch {
print("AudioController start Error")
}
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
audioController?.stop()
}
#IBAction func playSound(sender: UIButton) {
do {
let player = try AEAudioFilePlayer(URL: audioURL)
player.removeUponFinish = true
audioController?.addChannels([player])
} catch {
print("Player start Error")
}
}

Play sound effect without latency

I am trying to make a little app to teach myself some swift and I'm having some problems figuring out how to get my app to function a certain way.
My app should be able to play an airhorn sound just like the way it sounds in this video...
https://www.youtube.com/watch?v=Ks5bzvT-D6I
But each time I tap the screen repeatedly there is a slight delay before the sound is played so it's not sounding like that at all.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
var hornSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: hornSound, error: &error)
audioPlayer.prepareToPlay()
}
#IBAction func playSound(sender: UIButton) {
audioPlayer.pause()
audioPlayer.currentTime = 0
audioPlayer.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have also come across this thread about using spritekit
Creating and playing a sound in swift
And in trying that I got it to play the sound without the delay, but with sprite kit I can't stop the existing sound, so they just overlap which is not the effect I want.
Is there a work around to get this working the way it sounds in the video.
Apple recommends AVAudioPlayer for playback of audio data unless you require very low I/O latency.
So you might want to try another approach.
In one of my apps I play countdown sounds by creating a system sound ID from my wav file. Try this in your class:
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var sound: SystemSoundID = 0
override func viewDidLoad() {
super.viewDidLoad()
var hornSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")!)
AudioServicesCreateSystemSoundID(hornSound!, &self.sound)
}
#IBAction func playSound(sender: UIButton) {
AudioServicesPlaySystemSound(self.sound)
}
...
}

Have a sound stop and start immediately on button press

I am making a little app for fun to test my learning and I've gotten it for the most part but there is a little caveat to the solution I've come up with.
The app should play a horn sound everytime you click the screen. But this should allow you to continually press it in quick succession and the horn sound plays each time. It should also stop the previous horn sound as well so there are no overlapping sounds.
Here is what I've managed so far
import UIKit
import AVFoundation
class ViewController: UIViewController {
var horn:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudios()
}
#IBAction func buttonPressed(sender: AnyObject) {
horn.stop()
horn.currentTime = 0
horn.play()
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")
horn = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn.prepareToPlay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And it works, sort of. It stops the sound and sets it back to the beginning, but if you click really quickly it skips and doesn't play sometimes. I want to try ti get around this but I'm not finding much on google. Is there a better solution that will allow me to play this sound like this really quickly?
UPDATE
As per the answer from Duncan, I tried to create two instances of the sound to play and switch between them both but I can't seem to get them to start the sound right when the screen is touched each time it's touched in quick succession. Here is the code I have tried
class ViewController: UIViewController {
var horn1:AVAudioPlayer = AVAudioPlayer()
var horn2:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudios()
}
var bool = true
#IBAction func butonPressed(sender: AnyObject) {
if(bool){
horn1.play()
horn2.stop()
horn2.currentTime = 0
bool = false
}else{
horn2.play()
horn1.stop()
horn1.currentTime = 0
bool = true
}
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")
horn1 = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn2 = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn1.prepareToPlay()
horn2.prepareToPlay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
On each press of the button there is a slight delay before the sound plays. And I want the sound to start over immediately. I want the horn to be able to sound like in this video
https://www.youtube.com/watch?v=Ks5bzvT-D6I
it's so much simpler than that! if you want the audio to start exactly after the button is pressed, you should change the event to "Touch Down" when you're connecting the button to your code. That's it! and for the button this is enough :
#IBAction func buttonPressed(sender: AnyObject) {
horn.currentTime = 0
horn.play()
}
Try creating 2 instances of your horn player, and calling prepareToPlay each time. Then when you click your button, stop the one that's playing, start the new one, and call prepareToPlay on the one that you stopped in order to get it ready. You'll need to build some program logic to implement that.

Resources