Using a UITapGestureRecognizer to play/pause audio - ios

This seems like a fairly simple task, but have been trying for a while to figure it out... I have set up a UITapGestureRecognizer to play an audio file from the bundle. That works well, but I would like to have the user be able to tap the item again and have the audio stop. Basically have the ability to start and replay the audio by tapping the object with single taps.
#IBAction func page1Tapped(_ sender: UITapGestureRecognizer) {
let url:URL = Bundle.main.url(forResource: "Opening Solo", withExtension: "mp3")!
do {
audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: nil)
} catch {
return
}
audioPlayer.prepareToPlay()
audioPlayer.play()
if audioPlayer.isPlaying {
audioPlayer.stop()
} else {
audioPlayer.play()
}
}
Here is the function that I have created. The audio only plays and just restarts at 0 with every tap at this point.
Any help is incredibly appreciated!!

This because every click creates a new re-assings the old instance , you can declare the player as instance variable
var audioPlayer:AVAudioPlayer?
and check it's value before assigning
if audioPlayer == nil
{
audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: nil)
audioPlayer.play()
}
else
{
if audioPlayer.isPlaying {
audioPlayer.stop()
} else {
audioPlayer.play()
}
}

Related

Playing sound effect in Xcode

I have created a button in Xcode that plays a short sound effect. I am not able to retrigger the button/sound, while it's playing. Therefore I have to wait until the sound has faded to repeat the effect. My intention is that every time I press the button, the sound effect starts to play, even if it is still fading out. Does anybody know how to achieve this? I am using XCode 11.6. My code is below. Many thanks in advance.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "1", ofType: "mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
#IBAction func Play(_ sender: Any) {
audioPlayer.play()
}
}
You could just initialize the audioPlayer in Play method instead of viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func Play(_ sender: Any) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "1", ofType: "mp3")!))
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print(error)
}
}
if I understand correctly you want to prevent play again when sound is playing and just play sound when it finished already so
you can make condition according sound is playing or not
(try it in your button action):
if audioPlayer.isPlaying(){
// your statement when sound is playing in other word nothing to do
}else{
// your statement when sound is not playing and you want to start play
audioPlayer.play()
}
https://developer.apple.com/documentation/avfoundation/avaudioplayer/1390139-isplaying

Multiple Audio Files In One ViewController With Button

Want to play multiple audio files in the same ViewController. When a user clicks on Button1 the file1.mp3 should be loaded into ViewController and when user hit play same should play and for button2 file2.mp3 should load and play.
I know how to do this for a single file but for multiple files I have to create ViewController again and again which is so hard. This is my code for single file
#IBAction func buttonPressed(_ sender: UIButton) {
if player.isPlaying {
player.stop()
sender.setImage(UIImage(named:"play.png"),for: .normal)
} else {
player.delegate = self
player.play()
player.numberOfLoops = 107
sender.setImage(UIImage(named:"pause.png"),for: .normal)
}
}
do {
let audioPlayer = Bundle.main.path(forResource: "chalisa", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPlayer!) as URL)
}catch {
//ERROR
}
You can use Tableview List all songs Name and Play Button use to Multiple audio files use in one viewcontroller
import AVFoundation
//MARK:- Variable Declaration
var player: AVAudioPlayer?
func playMusic() {
guard let url = Bundle.main.url(forResource: "chalisa", withExtension: "mp3") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}

MP3 File Not Playing On Button Select

For simplicity, assume this View says alphabet letters in button when you click on said button.
I have been attempting to play the audio files associated with each button, but the files never play. The buttons, individually, are never given outlets, but they are all connected to the function on Touch Up Inside.
class UIAlphabetController: UIViewController, AVAudioPlayerDelegate{
#IBAction func PlaySound(Sender: UIButton) {
let soundname:String = Sender.currentTitle!
var player: AVAudioPlayer?
let url = Bundle.main.url(forResource: soundname, withExtension: "mp3")
do {
player = try AVAudioPlayer(contentsOf: url!)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error { print(error.localizedDescription) }
}
}
Since I have had this problem, the AQDefaultDevice (188): skipping input stream 0 0 0x0 message has been coming up. On testing with breakpoints, both soundname and url have expected values.
Does anybody have any idea as to how to fix this?
Move your instance of AVAudioPlayer outside of the method. If you do this it will keep player around long enough to play the sound. The reason it's not working is because it trashes the player instance after the method is complete because it thinks you are done using it.
class UIAlphabetController: UIViewController, AVAudioPlayerDelegate {
var player: AVAudioPlayer?
#IBAction func PlaySound(Sender: UIButton) {
let soundname:String = Sender.currentTitle!
let url = Bundle.main.url(forResource: soundname, withExtension: "mp3")
do {
player = try AVAudioPlayer(contentsOf: url!)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error { print(error.localizedDescription) }
}
}

Swift 2 - AVAudioPlayer not playing audio

I'm trying to play a sound with an AVAudioPlayer, but it doesn't play. I tried both a device (iPhone 6s) and the simulator. I'm not getting any errors and it logs it played. When I set a breakpoint before audioPlayer.play() and step over it plays for a few seconds and stops then. My code:
let soundURL = NSBundle.mainBundle().URLForResource("test", withExtension: "mp3")
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
if let soundURL = soundURL {
let audioPlayer = try AVAudioPlayer(contentsOfURL: soundURL)
audioPlayer.volume = 1
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
print(audioPlayer.duration)
audioPlayer.play()
print("PLAYED")
}
}catch {
print("Error getting the audio file")
}
Here is an example that will work. I am not sure where, in relation to the rest of your code, you have the code that you've shown above. Make sure your device volume is not on silent and is turned up...
//declare these as instance variables
var AudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("FileName", ofType: "mp3")!)
var AudioPlayer = AVAudioPlayer()
//inside viewDidLoad establish your AVAudioSession and configure the AudioPlayer with the contents of URL
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
//print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
//print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
do {
try AudioPlayer = AVAudioPlayer(contentsOfURL: AudioURL, fileTypeHint: nil)
} catch {
print("errorin do-try-catch")
}
}
//play audio
#IBAction func playAudio(sender: AnyObject){
AudioPlayer.play()
}

AVAudioPlayer produces lag despite prepareToPlay() in Swift

Playing a very short sound (~0.5s) produces a hiccup (like a lag) in my SpriteKit iOS game programmed in Swift. In other questions, I read that I should prepareToPlay() the sound, which I did.
I even used a variable (soundReady) to check if the sound is prepared before playing it. I also re-prepare the sound whenever it is finished playing (audioPlayerDidFinishPlaying()). Here are the relevant parts of the code:
class GameScene: SKScene, AVAudioPlayerDelegate {
var splashSound = NSURL()
var audioPlayer = AVAudioPlayer()
var soundReady = false
override func didMoveToView(view: SKView) {
let path = NSBundle.mainBundle().pathForResource("plopSound", ofType: "m4a")
splashSound = NSURL(fileURLWithPath: path)
audioPlayer = AVAudioPlayer(contentsOfURL: splashSound, error: nil)
audioPlayer.delegate = self
soundReady = audioPlayer.prepareToPlay()
}
func playSound(){
if(soundReady){
audioPlayer.play()
soundReady = false
}
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool){
//Prepare to play after Sound finished playing
soundReady = audioPlayer.prepareToPlay()
}
}
I have no idea where I've gone wrong on this one. I feel like I have tried everything (including, but not limited to: only preparing once, preparing right after playing, not using a variable, but just prepareToPlay()).
Additional information:
The sound plays without delay.
How quickly the sound is played after the last finish does not seem to impact the lag.
I ran into this same problem and played the sound in the backgroundQueue.
This is a good example: https://stackoverflow.com/a/25070476/586204.
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
audioPlayer.play()
})
Just adding a Swift 3 version of the solution from #brilliantairic.
DispatchQueue.global().async {
audioPlayer.play()
}
When I called play multiple times it would cause bad access. I believe the player is being deallocated since this is not thread safe. I created a serial queue to alleviate this problem.
class SoundManager {
static let shared = SoundManager()
private init() {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try? AVAudioSession.sharedInstance().setActive(true)
}
private let serialQueue = DispatchQueue(label: "SoundQueue", qos: .userInitiated)
private var player: AVAudioPlayer?
static func play(_ sound: Sound) {
shared.play(sound)
}
func play(_ sound: Sound) {
guard let url = Bundle.main.url(forResource: sound.fileName, withExtension: "mp3")
else { return }
do {
try serialQueue.sync {
self.player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
DispatchQueue.main.async {
self.player?.play()
}
}
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
}

Resources