How to rapidly press a button to generate sound on Xcode? - ios

Here is my following code for an Xcode project. When I press "play()," a short drum sound occurs. The thing is, when I tap the custom view multiple times in succession, I want quick drum sounds to occur in succession. However, the sound occurs after the full clip (of 2 seconds) as finished, leaving lots of deadspace. I tried many different things but I can't.
#IBOutlet weak var tabla: TablaHead!
var tablaSoundUrl = NSURL(fileURLWithPath: Bundle.main.path(forResource: "TablaSound", ofType: "mp3")!)
var tablaAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
tablaAudioPlayer = try! AVAudioPlayer(contentsOf: tablaSoundUrl as URL)
}
#IBAction func tapped(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
playNoise()
}
}
public func playNoise() {
if (tablaAudioPlayer.isPlaying) {
tablaAudioPlayer.stop()
tablaAudioPlayer.play()
} else {
tablaAudioPlayer.play()
}
}

Try something like this
#IBOutlet weak var tabla: TablaHead!
var tablaSoundUrl = NSURL(fileURLWithPath: Bundle.main.path(forResource: "TablaSound", ofType: "mp3")!)
var tablaAudioPlayer = AVAudioPlayer()
var tapsCount = 0
override func viewDidLoad() {
super.viewDidLoad()
tablaAudioPlayer = try! AVAudioPlayer(contentsOf: tablaSoundUrl as URL)
}
#IBAction func tapped(_ sender: UITapGestureRecognizer) {
tapsCount += 1
if sender.state == .ended && tapsCount == 1 {
playNoise()
}
}
public func playNoise() {
tapsCount -= 1
tablaAudioPlayer.play()
if tapsCount > 0 {
playNoise()
}
}

Related

iOS Swift AVAudioPlayer how to reset currentTime to zero when finished playing

My code is supposed to play a C Major scale wave file on a button press. Hitting a separate stop button will stop playback and reset the currentTime to zero.
#IBAction func onPlayButtonClick(sender: UIButton)
{
play.enabled = false
let path = NSBundle.mainBundle().pathForResource("cmajor", ofType: "wav")!
let url = NSURL(fileURLWithPath: path)
do
{
scalePlayer = try AVAudioPlayer(contentsOfURL: url)
scalePlayer.prepareToPlay()
scalePlayer.enableRate = true
scalePlayer.rate = 0.75
scalePlayer.play()
}
catch
{
}
}
It works as intended, but how do I set the currentTime to zero when the file is finished playing? I couldn't find anything on the developer docs.
You have to set a delegate:
scalePlayer.delegate = self
Then you implement this callback
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
//set the current time here
scalePlayer.currentTime = 0
}
Update
Here is an example how you can implement this (based on your code):
class MyViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet weak var play: UIButton!
private var scalePlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("cmajor", ofType: "wav")!
let url = NSURL(fileURLWithPath: path)
scalePlayer = try? AVAudioPlayer(contentsOfURL: url)
scalePlayer?.delegate = self
}
#IBAction func onPlayButtonClick(sender: UIButton) {
play.enabled = false
scalePlayer?.prepareToPlay()
scalePlayer?.enableRate = true
scalePlayer?.rate = 0.75
scalePlayer?.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
scalePlayer?.currentTime = 0
}
}

Swift - Stop avaudioplayer

I am trying to build a soundboard into an app and have figured out an efficient way of using tags to control playing the sounds. However I am now trying to integrate a pause button that can be used with the .stop() method on the AVAudioPlayer however I get an error with my current code:
EXC_BAD_ACCESS
This is what I am using at the moment, any ideas?
import UIKit
import AVFoundation
let soundFilenames = ["sound","sound2","sound3"]
var audioPlayers = [AVAudioPlayer]()
class SecondViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
for sound in soundFilenames {
do {
let url = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(sound, ofType: "mp3")!)
let audioPlayer = try AVAudioPlayer(contentsOfURL: url)
audioPlayers.append(audioPlayer)
} catch {
//Catch error thrown
audioPlayers.append(AVAudioPlayer())
}
}
}
#IBAction func buttonPressed(sender: UIButton) {
let audioPlayer = audioPlayers[sender.tag]
audioPlayer.play()
}
#IBAction func stop(sender: UIButton) {
audioPlayer.stop()
}
}
Your audioPlayer in stop function is not the playing player. You should assign it in buttonPressed function.
#IBAction func buttonPressed(sender: UIButton) {
audioPlayer = audioPlayers[sender.tag]
audioPlayer.play()
}
By the way, You can mark audioPlayer as a "?" property, it will be more efficient when init this Controller.
class SecondViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
let enableMuiltPlayers = false
....
#IBAction func buttonPressed(sender: UIButton) {
if sender.tag < audioPlayers.count else {
print("out of range")
return
}
if enableMuiltPlayers {
audioPlayers[sender.tag].play()
} else {
audioPlayer?.stop()
//set the current playing player
audioPlayer = audioPlayers[sender.tag]
audioPlayer?.play()
}
}
#IBAction func stop(sender: UIButton) {
let wantToStopAll = false
if enableMuiltPlayers && wantToStopAll {
stopAll()
} else {
audioPlayer?.stop()
}
audioPlayer = nil
}
}
to stop all:
fun stopAll() {
for player in audioPlayers {
player.stop()
}
}
Your code may have other faults, but there's one thing sure:
You should not instantiate AVAudioPlayer using default initializer AVAudioPlayer().
Change this line:
var audioPlayer = AVAudioPlayer()
to:
var playingAudioPlayer: AVAudioPlayer?
And change this part:
} catch {
//Catch error thrown
audioPlayers.append(AVAudioPlayer())
}
to something like this:
} catch {
//Catch error thrown
fatalError("Sound resource: \(sound) could not be found")
}
(The latter part is very important to solve the issue. But I found it had become just a duplicate of some part of Hao's answer after I edited it...)
And start method:
#IBAction func start(sender: UIButton) {
let audioPlayer = audioPlayers[sender.tag]
audioPlayer.start()
playingAudioPlayer = audioPlayer
}
And stop should be:
#IBAction func start(sender: UIButton) {
playingAudioPlayer?.stop()
}
if audioPlayer != nil {
if audioPlayer.playing {
audioPlayer.stop()
}
}

AVFoundation: prevent background music playing twice when returning to first screen

At the moment I have a home screen which plays background music using AVFoundation. If you click play the music stops (which is what I want).
If you move to the instructions screen the music continues (which I want), however when you click to return to home screen the background music continues (which I want), but a new track starts over the top.
In my mind what I ideally need is an if statement which prevents the music from restarting when I return to the home screen (if it is already playing). I have scoured the internet but I can't find any suggestions that will work.
This is the what I am currently working with,
class firstPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
backgroundMusic = self.setupAudioPlayerWithFile("background", type:"mp3")
backgroundMusic.volume = 0.3
backgroundMusic.numberOfLoops = -1
backgroundMusic.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
var backgroundMusic = AVAudioPlayer()
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer {
//1
var path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
var url = NSURL.fileURLWithPath(path!)
//2
var error: NSError?
//3
var audioPlayer:AVAudioPlayer?
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: &error)
//4
return audioPlayer!
}
#IBAction func startGame(sender: UIButton) {
backgroundMusic.stop()
}
#IBAction func instructionsButton(sender: UIButton) {
}
var isMusicPlaying = 0
class firstPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
backgroundMusic = self.setupAudioPlayerWithFile("background", type:"mp3")
if isMusicPlaying == 0 {
backgroundMusic.volume = 0.3
backgroundMusic.numberOfLoops = -1
backgroundMusic.play()
}
if backgroundMusic.playing == true {
isMusicPlaying = 1
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
var backgroundMusic = AVAudioPlayer()
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer {
//1
var path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
var url = NSURL.fileURLWithPath(path!)
//2
var error: NSError?
//3
var audioPlayer:AVAudioPlayer?
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: &error)
//4
return audioPlayer!
}

Make a playlist (start next song) in swift

I have created a sound player in swift with AVFoundation. I am trying to start the next song in array when the playing song is finished. I was trying to implement this code
if (audioPlayer.currentTime >= audioPlayer.duration){
var recentSong = songPlaylist[selectedSongNumber + 1]
audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath:
NSBundle.mainBundle().pathForResource(recentSong, ofType: "mp3")!), error: nil)
audioPlayer.play()
}
but I am not being able to implement this code (I do not know where to implement it).Here is my complete code
import UIKit
import AVFoundation
import AVKit
public var audioPlayer = AVPlayer()
public var selectedSongNumber = Int()
public var songPlaylist:[String] = ["song1", "song2"]
public var recentSong = "song1"
let playImage = UIImage(named: "Play.png") as UIImage!
let pauseImage = UIImage(named: "Pause.png") as UIImage!
class FirstViewController: UIViewController {
#IBOutlet weak var musicSlider: UISlider!
#IBOutlet weak var PlayPause: UIButton!
var audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath:
NSBundle.mainBundle().pathForResource(recentSong, ofType: "mp3")!), error: nil)
override func viewDidLoad() {
super.viewDidLoad()
musicSlider.maximumValue = Float(audioPlayer.duration)
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateMusicSlider"), userInfo: nil, repeats: true)
if (audioPlayer.currentTime >= audioPlayer.duration){
var recentSong = songPlaylist[selectedSongNumber + 1]
audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath:
NSBundle.mainBundle().pathForResource(recentSong, ofType: "mp3")!), error: nil)
audioPlayer.play()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func PlayPauseButton(sender: AnyObject) {
if (audioPlayer.playing == false){
audioPlayer.play()
PlayPause.setImage(pauseImage, forState: .Normal)
}else{
audioPlayer.pause()
PlayPause.setImage(playImage, forState: .Normal)
}
}
#IBAction func StopButton(sender: AnyObject) {
audioPlayer.stop()
audioPlayer.currentTime = 0
PlayPause.setImage(playImage, forState: .Normal)
}
#IBAction func musicSliderAction(sender: UISlider) {
audioPlayer.stop()
audioPlayer.currentTime = NSTimeInterval(musicSlider.value)
audioPlayer.play()
}
func updateMusicSlider(){
musicSlider.value = Float(audioPlayer.currentTime)
}
}
I am updating my code with something different:
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var counter = 0
var song = ["1","2","3"]
var player = AVAudioPlayer()
#IBOutlet weak var musicSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
musicSlider.value = 0.0
}
func updateMusicSlider(){
musicSlider.value = Float(player.currentTime)
}
#IBAction func playSong(sender: AnyObject) {
music()
}
#IBAction func sliderAction(sender: AnyObject) {
player.stop()
player.currentTime = NSTimeInterval(musicSlider.value)
player.play()
}
func music(){
var audioPath = NSBundle.mainBundle().pathForResource("\(song[counter])", ofType: "mp3")!
var error : NSError? = nil
player = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
musicSlider.maximumValue = Float(player.duration)
var timer = NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: Selector("updateMusicSlider"), userInfo: nil, repeats: true)
player.delegate = self
if error == nil {
player.delegate = self
player.prepareToPlay()
player.play()
}
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool)
{
println("Called")
if flag {
counter++
}
if ((counter + 1) == song.count) {
counter = 0
}
music()
}
}
You can do it this way.
Hope It will help and HERE is sample project for more Info.
You need to implement AVAudioPlayerDelegate Protocol's method:
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer!, successfully flag: Bool)
Documentation link
Play your next music item here.
But I will not recommend, since AVAudioPlayer can only play one item at a time. You need to instantiate again with another music item after completion. I will suggest you to use AVQueuePlayer. Detaled answer has been given here. Hope it helps!
I created a sound player in swift with AVFoundation. I used progressView to time the song and then when it hits 0.98743 it will update to the next song automatically this is the Github link: https://github.com/ryan-wlr/MusicPlayerIOS
func updateProgressView() {
if (progressView.progress > a.advanced(by: 0.98743)) {
audioPlayerDidFinishPlayeing()
}
if audioPlayer.isPlaying {
let progress = Float(audioPlayer.currentTime/audioPlayer.duration)
progressView.setProgress(progress, animated: true)
}
}

How do I change a play button to a pause button when tapped on?

I need to change my play button to a pause button and vice versa when tapped on. I am still new to this, so I don't know how to identify buttons in the .swift file or how to change icons programmatically in the .swift file.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
var toggleState = 1
#IBAction func playPauseButton(sender: AnyObject) {
if toggleState == 1 {
player.play()
toggleState = 2
} else {
player.pause()
toggleState = 1
}
}
#IBAction func stopButton(sender: AnyObject) {
player.stop()
player.currentTime = 0
}
#IBAction func sliderChanged(sender: AnyObject) {
player.volume = sliderValue.value
}
#IBOutlet weak var sliderValue: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
var audioPath = NSBundle.mainBundle().pathForResource("StarWars", ofType: "mp3")!
var error: NSError?
player = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can change the image of the button depending on the state:
#IBAction func playPauseButton(sender: AnyObject) {
var playBtn = sender as UIButton
if toggleState == 1 {
player.play()
toggleState = 2
playBtn.setImage(UIImage(named:"pause.png"),forState:UIControlState.Normal)
} else {
player.pause()
toggleState = 1
playBtn.setImage(UIImage(named:"play.png"),forState:UIControlState.Normal)
}
}
The sender object passed to the playPauseButton is your UIButton which calls the method. Because it is sent as an object of type AnyObject we cast it to a UIButton. If you do not want to cast it, and are sure only a UIButton will call this method you can simply replace AnyObject with UIButton in the function parameter.

Resources