How to track when song is finished AVAudioPlayer - ios

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.

Related

Ok so I'm new to Swift. I've been trying to make an audio player and the code just gives me this error. What can I do?

import UIKit
import AVFoundation
class AVAudioPlayer : NSObject {
}
class ViewController: UIViewController {
#IBAction func keyPressed(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
func playSound() {
let player: AVAudioPlayer?
guard let sound = Bundle.main.url(forResource: "C", withExtension: "wav") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOfURL: sound, fileTypeHint: AVFileType.wav.rawValue)
guard let player = player else { return }
func play() -> Bool {}
} catch let error {
print(error.localizedDescription)
}
Xcode gives me an error in this line player = try AVAudioPlayer(contentsOfURL: sound, fileTypeHint: AVFileType.wav.rawValue) saying that Argument passed to call that takes no arguments
Replace '(contentsOfURL: sound, fileTypeHint: AVFileType.wav.rawValue)' with '' and when I try to fix it, it gives me this error: Cannot assign value of type 'AVAudioPlayer.Type' to type 'AVAudioPlayer'. Can somebody help me? PS sorry for bold, Stack wouldn't let me post because it had "too much code in it".
After the help from Sweeper, my code looks like this:
import AVFoundation
class ViewController: UIViewController {
var player: AVAudioPlayer?
#IBAction func keyPressed(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
func playSound() {
guard let sound = Bundle.main.url(forResource: "C", withExtension: "wav") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer
guard let player = player else { return }
func play() -> Bool {}
} catch let error {
print(error.localizedDescription)
}
As I was saying in the comments, now it gives me this error: Use of unresolved identifier 'player'
You need to provide init(contentsOf:) throws method to initialize the player. Here is the code you need:
class ViewController: UIViewController {
var player: AVAudioPlayer?
func playSound() {
guard let sound = Bundle.main.url(forResource: "C", withExtension: "wav") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: sound)
player?.play()
} catch let error {
print(error.localizedDescription)
}
}
}

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

Audio not playing in simulator

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

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.

Playing a sound with AVAudioPlayer

I'm trying to play a sound with AVAudioPlayer but it won't work.
Edit 1: Still doesn't work.
Edit 2: This code works. My device was in silent mode.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("button-09", ofType: "wav"))
println(alertSound)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
Made modification to your code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("button-09", ofType: "wav"))
println(alertSound)
// Removed deprecated use of AVAudioSessionDelegate protocol
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
Swift 3 and Swift 4.1:
import UIKit
import AVFoundation
class ViewController: UIViewController {
private var audioPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let alertSound = URL(fileURLWithPath: Bundle.main.path(forResource: "button-09", ofType: "wav")!)
print(alertSound)
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try! AVAudioSession.sharedInstance().setActive(true)
try! audioPlayer = AVAudioPlayer(contentsOf: alertSound)
audioPlayer!.prepareToPlay()
audioPlayer!.play()
}
}
As per Swift 2 the code should be
var audioPlayer : AVAudioPlayer!
func playSound(soundName: String)
{
let coinSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(soundName, ofType: "m4a")!)
do{
audioPlayer = try AVAudioPlayer(contentsOfURL:coinSound)
audioPlayer.prepareToPlay()//there is also an async version
audioPlayer.play()
}catch {
print("Error getting the audio file")
}
}
Your audio play will be deallocated right after viewDidLoad finishes since you assign it to a local variable. You need to create a property for it to keep it around.
The solution is for AudioPlayer which has play/pause/stop button in the navigation bar as bar button items
In Swift 2.1 you can use the below code
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let audioPath = NSBundle.mainBundle().pathForResource("ARRehman", ofType: "mp3")
var error:NSError? = nil
do {
player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
}
catch {
print("Something bad happened. Try catching specific errors to narrow things down")
}
}
#IBAction func play(sender: UIBarButtonItem) {
player.play()
}
#IBAction func stop(sender: UIBarButtonItem) {
player.stop()
print(player.currentTime)
player.currentTime = 0
}
#IBAction func pause(sender: UIBarButtonItem) {
player.pause()
}
#IBOutlet weak var sliderval: UISlider!
#IBAction func slidermov(sender: UISlider) {
player.volume = sliderval.value
print(player.volume)
}
}
This will work.....
import UIKit
import AVFoundation
class ViewController: UIViewController {
var ding:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudios()
ding.play()
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("ding", ofType: "mp3")
ding = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
ding.prepareToPlay()
}
}
Use This Function to make sound in Swift (You can use this function where you want to make sound.)
First Add SpriteKit and AVFoundation Framework.
import SpriteKit
import AVFoundation
func playEffectSound(filename: String){
runAction(SKAction.playSoundFileNamed("\(filename)", waitForCompletion: false))
}// use this function to play sound
playEffectSound("Sound File Name With Extension")
// Example :- playEffectSound("BS_SpiderWeb_CollectEgg_SFX.mp3")
This is a pretty old question, but I did not see an answer that worked with Swift 3.0 so I modernized the code and added some safety checks to prevent crashes.
I also took the approach of pulling the playing part out into its own function to help with re-use for anyone coming across this answer.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
play(for: "button-09", type: "wav")
}
func play(for resource: String, type: String) {
// Prevent a crash in the event that the resource or type is invalid
guard let path = Bundle.main.path(forResource: resource, ofType: type) else { return }
// Convert path to URL for audio player
let sound = URL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOf: sound)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch {
// Create an assertion crash in the event that the app fails to play the sound
assert(false, error.localizedDescription)
}
}
}
Swift 4.2 onwards, AudioSession category setup options have been changed. (Try following code for Swift 4.2 onwards)
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
prepareAudioSession()
}
func prepareAudioSession() -> Void {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: AVAudioSession.CategoryOptions.defaultToSpeaker)
try AVAudioSession.sharedInstance().setActive(true)
prepareAudioPlayer()
} catch {
print("Issue with Audio Session")
}
}
func prepareAudioPlayer() -> Void {
let audioFileName = "test"
let audioFileExtension = "mp3"
guard let filePath = Bundle.main.path(forResource: audioFileName, ofType: audioFileExtension) else {
print("Audio file not found at specified path")
return
}
let alertSound = URL(fileURLWithPath: filePath)
try? audioPlayer = AVAudioPlayer(contentsOf: alertSound)
audioPlayer?.prepareToPlay()
}
func playAudioPlayer() -> Void {
audioPlayer?.play()
}
func pauseAudioPlayer() -> Void {
if audioPlayer?.isPlaying ?? false {
audioPlayer?.pause()
}
}
func stopAudioPlayer() -> Void {
if audioPlayer?.isPlaying ?? false {
audioPlayer?.stop()
}
}
func resetAudioPlayer() -> Void {
stopAudioPlayer()
audioPlayer?.currentTime = 0
playAudioPlayer()
}
}

Resources