I am using AVFoundation and numberOfLoops = -1 to loop a sound when called. When the sound has finished playing (and loops to the beginning), there is a very slight but noticeable gap between the sounds. I want the transition to be seamless. I have followed a suggestion to use m4a files to no avail.
var soundPlayer:AVAudioPlayer = AVAudioPlayer()
#IBOutlet var buttonPlay: UIButton!
let soundLocation = NSBundle.mainBundle().pathForResource("soundexample", ofType: ".m4a")
do {
soundPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: soundLocation!))
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
}
catch {
print(error)
}
}
Then the button action:
#IBAction func soundButtonTap(sender: UIButton) {
if (!sender.selected) {
soundPlayer.play()
soundPlayer.numberOfLoops = -1
}
else {
soundPlayer.stop()
}
sender.selected = !sender.selected
}
Related
I'm new in iOS. I have written this code, which plays only one audio file when tapping a UIButton. I would like to play multiple sound randomly. How to set it? Thank you!
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
#IBAction func playButtonPressed(_ sender: UIButton) {
if let soundURL = Bundle.main.url(forResource: "kompilacja", withExtension: "mp3") {
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
}
catch {
print(error)
}
audioPlayer.play()
}else{
print("Karwasz twarz! Brak pliku audio, Panie!")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Put all of your sound file names in an array and use the randomElement method to choose your sound.
#IBAction func playButtonPressed(_ sender: UIButton) {
let sounds = ["kompilacja", "another sound", "yet another sound"]
guard let sound = sounds.randomElement(),
let soundURL = Bundle.main.url(forResource: sound, withExtension: "mp3") else { return }
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
}
catch {
print(error)
}
audioPlayer.play()
}
I want to track when playing song is finished. I tried different solutions from the web but they could not solve my problem.
I implemented audioPlayerDidFinishPlaying method but it is not working.
How can I understand if playing song is finished?
I am playing songs with playSound function
playSound func:
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
audioPlayerDidFinishPlaying func:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is not working, not printing "finished"
}
How can I solve my problem? How to track when playing song is finished
EDIT: I am adding whole code.
//
// ViewController.swift
import UIKit
import SwiftVideoBackground
import AudioToolbox
import AVFoundation
class ViewController: UIViewController,AVAudioPlayerDelegate {
var player: AVAudioPlayer?
#IBOutlet weak var backgroundVideo: BackgroundVideo!
#IBOutlet weak var initialLabel: UILabel!
#IBOutlet weak var statementLabel: UILabel!
var mp3: [String] = ["turk_milleti_demokrattir","xyz"]
var fav: [String] = ["0","0"]
var name: [String] = ["Türk milleti demokrattır","xy"]
var toggleState = 1
#IBOutlet weak var playB: UIButton!
var counter = 0
var duration = 0.1
override func viewDidLoad() {
super.viewDidLoad()
player?.delegate = self
playB.setImage(UIImage(named: "playbtn.png"), for: .normal)
statementLabel.text = name[counter]
backgroundVideo.createBackgroundVideo(name: "abc", type: "mp4")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func likeButton(_ sender: Any) {
fav[counter] = "1"
print(fav[0...1])
}
#IBAction func playButton(_ sender: Any) {
let name = mp3[counter]
playSound(name: name)
let playBtn = sender as! UIButton
if toggleState == 1 {
player?.play()
toggleState = 2
playBtn.setImage(UIImage(named: "pausebtn.png"), for: .normal)
} else {
player?.pause()
toggleState = 1
playBtn.setImage(UIImage(named:"playbtn.png"),for: .normal)
}
}
#IBAction func nextButton(_ sender: Any) {
counter = counter + 1
if counter == mp3.count {
counter = 0
}
toggleState = 2
playB.setImage(UIImage(named: "pausebtn.png"), for: .normal)
playSound(name: mp3[counter])
statementLabel.text = name[counter]
}
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is not working, not printing "finished"
}
}
I solved my problem with help of Leo Dabus.
I changed my edited code. I moved player?.delegate = self
to playSound func. Finally, it is working.
playSound & audioPlayerDidFinishPlaying function:
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
player?.delegate = self
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is working now! printed "finished"!
}
Do not forget to add AVAudioPlayerDelegate to ViewController!
class ViewController: UIViewController,AVAudioPlayerDelegate {
You are not setting the player's delegate correctly.
In viewDidLoad, your player is going to be nil, so this line:
player?.delegate = self
Will do nothing (The question mark is optional chaining, so if player == nil, it does nothing.)
You need to set the delegate after loading the player.
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()
}
}
I have three buttons and I'm trying to get a sound to play with each button.
The sounds don't play on the simulator wondering what was going wrong in my code.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func mmm_sound(sender: UIButton) {
playSound("mmm")
}
#IBAction func aww_sound(sender: UIButton) {
playSound("aww")
}
#IBAction func maniacal_sound(sender: UIButton) {
playSound("hah")
}
//sound function
func playSound(soundName: String)
{
let sound = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource(soundName, ofType: "wav")!)
do{
let audioPlayer = try AVAudioPlayer(contentsOfURL:sound)
audioPlayer.prepareToPlay()
audioPlayer.play()
}catch {
print("Error getting the audio file")
}
}
}
You should make AVAudioPlayer as global variable as local variable of 'AVAudioPlayer' get deallocate before it plays, your code can like this
//at top
var audioPlayer = AVAudioPlayer()
func playSound(soundName: String)
{
let sound = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource(soundName, ofType:"wav")!)
do{
audioPlayer = try AVAudioPlayer(contentsOfURL:sound)
audioPlayer.prepareToPlay()
audioPlayer.play()
}catch {
print("Error getting the audio file")
}
}
I would like to be able to play audio on pressing a button a maximum of twice. After pressing the button twice it should no longer play audio even when pressed. I have this code currently, but it doesn't work and I am unsure where I am going wrong:
var soundFileURLRef: NSURL!
var audioPlayer = AVAudioPlayer?()
var audioCounter = 0
override func viewDidLoad() {
super.viewDidLoad()
// setup for audio
let playObjects = NSBundle.mainBundle().URLForResource("mathsinfo", withExtension: "mp3")
self.soundFileURLRef = playObjects
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: soundFileURLRef)
} catch _ {
audioPlayer = nil
}
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
}
//function for counting times audio is played
func countAudio() {
if ((audioPlayer?.play()) != nil) {
++audioCounter
}
}
//MARK: Actions
//action for button playing the audio
#IBAction func playMathsQuestion(sender: AnyObject) {
countAudio()
if audioCounter < 2 {
audioPlayer?.play()
}
}
The countAudio function in your code have the side effect of playing the sound asynchronously since you call audioPlayer?.play(). The audioCounter variable is only incremented when audioPlayer is nil. Try this version
var soundFileURLRef: NSURL!
var audioPlayer: AVAudioPlayer?
var audioCounter = 0
override func viewDidLoad() {
super.viewDidLoad()
// setup for audio
let playObjects = NSBundle.mainBundle().URLForResource("mathsinfo", withExtension: "mp3")
self.soundFileURLRef = playObjects
audioPlayer = try? AVAudioPlayer(contentsOfURL: soundFileURLRef)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
}
//MARK: Actions
//action for button playing the audio
#IBAction func playMathsQuestion(sender: AnyObject) {
if (audioPlayer != nil) {
if (audioCounter < 2 && audioPlayer!.play()) {
++audioCounter
}
}
}
You can see that here we first check to ensure the audioPlayer is not nil. After this we only do the audioPlayer!.play() call when audioCounter < 2. The call to play() returns a boolean that is true when the audio is successfully queued for play (see the documentation), and in that case we increment audioCounter.
I also simplified the initialization of audioPlayer by using the try? version of try which returns nil when the callee throws.