I have a class defined as Music.swift coded as follows:
import Foundation
import AVFoundation
class Music {
var isPlaying: Bool = false
public var backgrndSound = AVAudioPlayer()
func isMusicPlaying() -> Bool {
isPlaying = UserDefaults.standard.bool(forKey: "isPlaying")
return isPlaying
}
func StartPlaying() {
let path = Bundle.main.path(forResource: "Music.mp3", ofType: nil)!
let url = URL(fileURLWithPath: path)
do {
self.backgrndSound = try AVAudioPlayer(contentsOf: url)
self.backgrndSound.numberOfLoops = -1
self.backgrndSound.play()
UserDefaults.standard.setValue(true, forKey: "isPlaying")
} catch {
// couldn't load file :(
}
}
func StopPlaying() {
self.backgrndSound.pause()
self.backgrndSound.stop()
}
}
On first load of the app, the music is automatically started with a call to StartPlaying(). That works just fine. Afterwards I have a settings menu that has a switch for the music play :
import UIKit
import AVFoundation
class SettingsViewController: UIViewController {
@IBOutlet weak var swMusic: UISwitch!
var myMusic = Music()
override func viewDidLoad() {
super.viewDidLoad()
swMusic.isOn = UserDefaults.standard.bool(forKey: "isPlaying")
}
@IBAction func musicSwitch(_ sender: Any) {
if swMusic.isOn == true {
// turn on music
myMusic.StartPlaying()
} else {
myMusic.StopPlaying()
}
}
}
When I tap the switch it does fire StopPlaying() but the music in the background continues to play despite the tap.
I am not sure why that is, unless the AV object isn't accessible from the original creation and therefore can't stop it properly; but so far I have been unable to figure that out either.
Any help or suggestions would be greatly appreciated.
By instantiating a new instance of the Music
class in SettingsViewController
you're effectively creating a new AVAudioPlayer
instance that knows nothing about the one already instantiated.
Consider this code, which contains static properties and class methods:
import Foundation
import AVFoundation
class Music
{
public static var backgrndSound: AVAudioPlayer?
// AVAudioPlayer already has an isPlaying property
class func isMusicPlaying() -> Bool
{
return backgrndSound?.isPlaying ?? false
}
class func StartPlaying()
{
let path = Bundle.main.path(forResource: "Music.mp3", ofType: nil)!
let url = URL(fileURLWithPath: path)
do
{
backgrndSound = try AVAudioPlayer(contentsOf: url)
backgrndSound?.numberOfLoops = -1
backgrndSound?.play()
}
catch
{
// couldn't load file :(
}
}
class func StopPlaying()
{
backgrndSound?.pause()
backgrndSound?.stop()
}
}
then access this using:
Music.isMusicPlaying()
Music.startPlaying()
Music.stopPlaying()
i.e. you'd not do var myMusic = Music()
This way there will always be a single instance of the AVAudioPlayer
, Music.backgrndSound
This sample code changes backgrndSound
to an optional ... you're effectively creating an unused AVAudioPlayer
instance that is discarded as soon as you startPlaying
.
It also removes the unnecessary isPlaying
property, as AVAudioPlayer
already has a property for this purpose.