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

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!
}

Related

How to track when song is finished AVAudioPlayer

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.

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

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()
}
}

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
}
}

Conditional audio playing using AV Audio Player in Swift

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.

recording audio in swift and passing the recorded audio to the next view controller

I am trying to record Audio, and pass the recorded audio, to the next view controller. Here is my code for recording Audio
class RecordSoundsViewController: UIViewController, AVAudioRecorderDelegate {
#IBOutlet weak var recording: UILabel!
#IBOutlet weak var recordButton: UIButton!
#IBOutlet weak var stopButton: UIButton!
var audioRecorder:AVAudioRecorder!
var recordedAudio : RecordedAudio!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
// enables record button
// hides the stop button
recordButton.enabled = true
stopButton.hidden = true
}
#IBAction func recordAudio(sender: UIButton) {
//Shows recording label
recording.hidden = false
//diabling record button
recordButton.enabled = false
stopButton.hidden = false
//Filepath Creation
let dirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let currentDateTime = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "ddMMyyyy-HHmmss"
let recordingName = formatter.stringFromDate(currentDateTime)+".wav"
let pathArray = [dirPath, recordingName]
let filePath = NSURL.fileURLWithPathComponents(pathArray)
println(filePath)
// Recording Session
var session = AVAudioSession.sharedInstance()
session.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
audioRecorder = AVAudioRecorder(URL: filePath, settings: nil, error: nil)
audioRecorder.delegate = self
audioRecorder.meteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()
}
func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
// ToDo create recorded audio file
if(flag)
{ recordedAudio = RecordedAudio()
recordedAudio.filepathURL = recorder.url
recordedAudio.title = recorder.url.lastPathComponent
// ToDo Perform segue
self.performSegueWithIdentifier("stopRecording", sender: recordedAudio)
}
else {
println("Recording was unsuccessfull")
stopButton.hidden = true
recordButton.enabled = true
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue == "stopRecording") {
let PlaySoundsVC:PlaySoundsViewController = segue.destinationViewController as! PlaySoundsViewController
let data = sender as! RecordedAudio
PlaySoundsVC.receivedAudio = data
}
}
#IBAction func stopAudio(sender: UIButton) {
// Hides recording
recording.hidden = true
audioRecorder.stop()
var audioSession = AVAudioSession.sharedInstance()
audioSession.setActive(false, error: nil)
}
}
My Model class is ,
import Foundation
class RecordedAudio : NSObject{
var filepathURL :NSURL!
var title : String!
}
Here is how My second viewcontroller catch the data and uses it,
class PlaySoundsViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
var receivedAudio: RecordedAudio!
func rateplay (rtt : Float32) {
audioPlayer.stop()
audioPlayer.rate = rtt
audioPlayer.currentTime = 0.0
audioPlayer.play()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3")
// {
// // if path is there for mp3
// let filepathurl = NSURL.fileURLWithPath(filePath)
//
// // println(receivedAudio.title)
//
// }
// else {
// println("Path is empty")
//
// }
audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filepathURL, error: nil)
audioPlayer.enableRate = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func playSlow(sender: UIButton) {
// play sloooowllyyyyy
audioPlayer.stop()
audioPlayer.rate = 0.5
audioPlayer.currentTime = 0.0
audioPlayer.play()
}
#IBAction func playFast(sender: UIButton) {
rateplay(1.5)
}
#IBAction func stopAudio(sender: UIButton) {
audioPlayer.stop()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Untill I add the below code,
audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filepathURL, error: nil)
audioPlayer.enableRate = true
I was able to move to the second scene, which means, the audio is successfully recorded. But as soon as i access the data like "receivedAudio.filepathURL" I am getting the error,
fatal error: unexpectedly found nil while unwrapping an Optional value
In the prepareForSegue function of the RecordSoundsViewController you need to write segue.identifier == "stopRecording" as the condition.
Currently you have segue == "stopRecording".
Happy Coding!

Resources