Swift - How to pause a playing audio by clicking the same button? - ios

So I have a list of audio files and when a cell is pressed it plays the audio. This works fine but I am not able to pause it by clicking the same cell again.
My code for the table view:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let a = indexPath.section;
let b = indexPath.row;
var path = NSBundle.mainBundle().pathForResource(musicArray[b], ofType: "mp3")
var error:NSError?
do{
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!))
}
catch
{
print("Something bad happened")
}
if (audioPlayer.playing)
{
audioPlayer.pause();
}
else
{
audioPlayer.play();
}
}
musicArray has the titles of the audio files

You're creating a new AVAudioPlayer every time you select the cell. You need to keep a reference to the previous one and tell that audio player to pause.

You can create a singleton object.
import AVFoundation
class SomeAudioManager: NSObject, AVAudioPlayerDelegate
{
class var sharedInstance: SomeAudioManager {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: SomeAudioManager? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SomeAudioManager()
}
return Static.instance!
}
func audioView(songname: String,format: String) {
let audioPlayer: AVAudioPlayer
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(songname, ofType:format)!), fileTypeHint: AVFileTypeMPEGLayer3)
audioPlayer.delegate = self;
audioPlayer.play()
} catch {
// error
}
}
}
It will be alive through the whole app.
How to implement AVAudioPlayer Inside Singleton Method?
You can just create a singleton of the player so that you will always know if it's playing or not.

Related

Reusable extension to play sound without memory leak in iOS and Swift?

Playing a sound is so verbose in code and would like to create an extension of AVAudioSession if possible. The way I'm doing it is assigning an object to a variable, but need help/advise on how to set up this function so it's reusable and optimized.
Here's what I have:
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
I think I have to create the audioPlayer variable outside of this function, otherwise I had a hard time playing anything. Maybe it can be self contained? I'm hoping to use it something like this:
AVAudioSession.sharedInstance().play("bebop")
Taking the code straight from your example I see two options:
1) Is there any particular reason why you want to make it as an extension of AVAudioSession? If not, just make your own service!
class AudioPlayerService {
static let sharedInstance = AudioPlayerService()
var audioPlayer: AVAudioPlayer?
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
2) If you do need to make it as an extension of AVAudioSession, then take a look at associated objects
extension AVAudioSession {
private struct AssociatedKeys {
static var AudioPlayerTag = "AudioPlayerTag"
}
var audioPlayer: AVAudioPlayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.AudioPlayerTag) as? AVAudioPlayer
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.AudioPlayerTag,
newValue as AVAudioPlayer?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
This is the best way I have found to add sound
Filename is shootMissile.wav
func shootMissileSound() {
if let soundURL = NSBundle.mainBundle().URLForResource("shootMissile", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}

How to Play/Stop background music and sound effects?

I have 2 sound effects and i can't stop them , however my background music works perfect.
My sound effects are in a different class - They Are in GameScene I run them from there.
How i can stop my sound effects correct?
For example: i have CatchObjectSound and i created the functions that i need. but still i can hear the sound effect when I'm clicking on the button.
What i want to do?
I want to stop my sound effects.
SKTAudio Class :
import UIKit
import AVFoundation
public class Singleton {
// Initialize
public var backgroundMusicPlayer: AVAudioPlayer?
public var soundEffectCatchObject: AVAudioPlayer?
//public var soundEffectGameOver: AVAudioPlayer?
//public var soundEffectClickedButton: AVAudioPlayer?
public class func sharedInstance() -> Singleton {
return SingletonInstance
}
// ---------------------------------------------
// Play Background Music
// ---------------------------------------------
func playBackgroundMusic() {
let url = NSBundle.mainBundle().URLForResource("Unity.mp3", withExtension: nil)
guard let newURL = url else {
print("Could not find file: \("Unity.mp3")")
return
}
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: newURL)
backgroundMusicPlayer!.numberOfLoops = -1
backgroundMusicPlayer!.prepareToPlay()
backgroundMusicPlayer!.play()
} catch let error as NSError {
print(error.description)
}
}
func pauseBackgroundMusic() {
backgroundMusicPlayer!.pause()
}
func resumeBackgroundMusic() {
backgroundMusicPlayer!.play()
}
// ---------------------------------------------
// Play Sound Effect Catch Object
// ---------------------------------------------
func playSoundEffectCatchObject() {
let url = NSBundle.mainBundle().URLForResource("Magical Echo Spell 03.wav", withExtension: nil)
guard let newURL = url else {
print("Could not find file: \("Magical Echo Spell 03.wav")")
return
}
do {
let error: NSError? = nil
soundEffectCatchObject = try! AVAudioPlayer(contentsOfURL: newURL)
if let player = soundEffectCatchObject {
player.numberOfLoops = 0
player.prepareToPlay()
player.play()
} else {
print("Could not create audio player: \(error!)")
}
}
func pauseSoundEffectCatchObject() {
if let player = soundEffectCatchObject {
if player.playing {
player.pause()
}
}
}
func resumeSoundEffectCatchObject() {
if let player = soundEffectCatchObject {
if player.playing {
player.play()
}
}
}
}
}
let SingletonInstance = Singleton()
I have button that pause/resume in main scene.
the button works perfectly , but i can't stop my sound effect, i tried.
Main Scene
if(SoundOnOff.containsPoint(location)) {
if (Singleton.sharedInstance().backgroundMusicPlayer?.playing == true) {
Singleton.sharedInstance().pauseBackgroundMusic()
Singleton.sharedInstance().pauseSoundEffectCatchObject()
self.SoundOnOff.texture = SKTexture(imageNamed:"MusicOff.png")
} else {
Singleton.sharedInstance().resumeBackgroundMusic()
Singleton.sharedInstance().resumeSoundEffectCatchObject()
self.SoundOnOff.texture = SKTexture(imageNamed:"MusicOn.png")
}
}
So create a sound playing singleton. Give it methods to start and stop the 2 sound effects. Since it's a singleton you can call it from anywhere in your program.

Background music overlap when back to creator viewController

I have a game which consists of 3 view controllers:
viewController
settingViewController
GameViewController
I have set the background music and played it in the viewController class
var backgroundMusic : AVAudioPlayer!
func setUpSounds(){
//button sound
if let buttonSoundPath = NSBundle.mainBundle().pathForResource("buttonClick", ofType: "mp3") {
let buttonSoundURL = NSURL(fileURLWithPath: buttonSoundPath)
do {
try buttonSound = AVAudioPlayer(contentsOfURL: buttonSoundURL)
}catch {
print("could not setup button sound")
}
buttonSound.volume = 0.5
}
//background sound
if let backgroundMusicPath = NSBundle.mainBundle().pathForResource("BackgroundMusic", ofType: "mp3") {
let backgroundMusicURL = NSURL(fileURLWithPath: backgroundMusicPath)
do {
try backgroundMusic = AVAudioPlayer(contentsOfURL: backgroundMusicURL)
}catch {
print("could not setup background music")
}
backgroundMusic.volume = 0.2
/*
set any negative integer value to loop the sound
indefinitely until you call the stop method
*/
backgroundMusic.numberOfLoops = -1
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.setUpSounds()
self.playBackgroundSound()
// Do any additional setup after loading the view, typically from a nib.
}
when I move to the settingViewController then back again to the viewController the sound replay and overlap the old played music.
What is the solution of this problem?
You have to check if the AVAudioPlayer is playing a music before call playBackgroundSound.
You can also put your SoundManager as a singleton, so you can manipulate it, from other parts of the app.
class SoundManager{
static var backgroundMusicSharedInstance = AVAudioPlayer?
}
In the View
func setUpSounds(){
//background sound
if SoundManager.backgroundMusicSharedInstance == nil {
if let backgroundMusicPath = NSBundle.mainBundle().pathForResource("BackgroundMusic", ofType: "mp3") {
let backgroundMusicURL = NSURL(fileURLWithPath: backgroundMusicPath)
do {
try SoundManager.backgroundMusicSharedInstance = AVAudioPlayer(contentsOfURL: backgroundMusicURL)
}catch {
print("could not setup background music")
}
SoundManager.backgroundMusicSharedInstance!.volume = 0.2
/*
set any negative integer value to loop the sound
indefinitely until you call the stop method
*/
SoundManager.backgroundMusicSharedInstance!.numberOfLoops = -1
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.setUpSounds()
if SoundManager.backgroundMusicSharedInstance!.playing == false{
self.playBackgroundSound()
}
}

Issue with "try?" and AVAudioPlayer

I am having the following issue with AVAudioPlayer:
import Foundation //Needed for dispatch_once_t
import AVFoundation //Needed to play sounds
class PlayStartSong {
var song: AVAudioPlayer = AVAudioPlayer()
var songStarted: Bool = false
class var sharedInstance: PlayStartSong {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: PlayStartSong? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = PlayStartSong()
}
return Static.instance!
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("start-2.0", ofType: "mp3")
song = try? AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!)) //Error Here
song.prepareToPlay()
song.numberOfLoops = -1 //Makes the song play repeatedly
}
}
On the line that assigns the value of the variable "song" in the "prepareAudios" function, I am getting the following error after converting to Swift 2.0:
Value of optional type 'AVAudioPLayer?' not unwrapped; did you mean to use '!' or '?'?
However, upon using the suggested fix, it is telling me to remove the exclamation point I just added. What is the exact issue here?
To use try? as you intended your song variable has to be an Optional:
class PlayStartSong {
var song: AVAudioPlayer?
var songStarted: Bool = false
class var sharedInstance: PlayStartSong {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: PlayStartSong? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = PlayStartSong()
}
return Static.instance!
}
func prepareAudios() {
let path = NSBundle.mainBundle().pathForResource("start-2.0", ofType: "mp3")
song = try? AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!))
song?.prepareToPlay()
song?.numberOfLoops = -1 //Makes the song play repeatedly
}
}
To not make it an Optional, you would use try inside do catch:
func prepareAudios() {
do {
let path = NSBundle.mainBundle().pathForResource("start-2.0", ofType: "mp3")
song = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!))
song.prepareToPlay()
song.numberOfLoops = -1 //Makes the song play repeatedly
} catch {
print(error)
}
}
Also, if you are absolutely sure that creating the AVAudioPlayer instance will always succeed, you can ignore "do catch" by using try!:
func prepareAudios() {
let path = NSBundle.mainBundle().pathForResource("start-2.0", ofType: "mp3")
song = try! AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!))
song.prepareToPlay()
song.numberOfLoops = -1 //Makes the song play repeatedly
}

iOS Sound not playing in Swift

I'm building a tableview in which each cell represents a sound that is played when the user taps that particular cell.
In objective-C it worked fine, but now that Apple released Swift I decided to move over instantly, but for some reason the sound does not play. My code when the user taps the cell:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: nil)
audioPlayer.play()
}
currentItem is the object in the array that has to be called. Each sound I can play is put in a custom object, together with a title and an image. that object is put in an instance of currentItem.
This is what the printNL outputs when I tapp one of my cells:
/private/var/mobile/Containers/Bundle/Application/ADF0CAFC-4C9E-475E-B3F0-CD85A1873CA5/Juichen.app/StupidQuestion.mp3
it does not give an error. I already tried moving the sound file to other folders, but that does not solve the problem either. therefore, I assume that this problem occurs because I am calling the audioPlayer incorrect?
Any help would be highly appreciated!
Let say you have a class myTable:
class myTable : UITableViewController
{
var audioPlayer:AVAudioPlayer? = nil
...
}
And to initialize audioPlayer:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!)
{
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var error : NSError? = nil
self.audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
if (self.audioPlayer == nil)
{
if let playerError = error as? NSError
{
let des : String? = playerError.localizedDescription
println("Error: \(des)")
}
}
else
{
self.audioPlayer.play()
}
}
Hope this helps.

Resources