Swift : Player audio file from another class file - ios

First of all, I just want to let you know I'm a newbie in iOS programming ;)
I would like to know if the following scenario is possible or which way I need to update my code to make it work.
I would like to play an audio file located in the player class file within the ViewController file.
I added the file "test.wav" to the root of project.
My problem
When I play the sound by tapping a button, the program can't find the sound file. I have the following error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Main swift file: ViewController.swift
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player1: Player!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playAudio(sender: AnyObject) {
player1.playAudioFile()
}
}
Class file: Player.swift
import Foundation
import AVFoundation
class Player {
// Variables
var vc: ViewController!
var audioFile: AVAudioPlayer!
// Initializer
init (){
}
// Methods
func playAudioFile() {
if audioFile.playing {
audioFile.stop()
} else {
audioFile.play()
}
}
// Intialize ViewController
init (vc:ViewController!) {
// Set path for the attack sound
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
let audioFileURL = NSURL(fileURLWithPath: audioSnd!)
do {
try audioFile = AVAudioPlayer(contentsOfURL: audioFileURL, fileTypeHint: nil)
audioFile.prepareToPlay()
} catch let err as NSError {
print(err.debugDescription)
}
}
}
More information
My program has a class "Player", with subclasses such as "Human" and "Monster".
By default, my class player has some audio files for attacks, dying, etc.
Under some conditions, the "player" can become a human or a monster and get custom attack and dying sounds.
Thanks a lot for your help! ;)

Why are you initializing you player with a viewController ? You dont need that.
Change the Player class to:
import Foundation
import AVFoundation
class Player {
// Variables
var audioFile: AVAudioPlayer!
// Initializer
init() {
// Set path for the attack sound
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
let audioFileURL = NSURL(fileURLWithPath: audioSnd!)
do {
try audioFile = AVAudioPlayer(contentsOfURL: audioFileURL, fileTypeHint: nil)
audioFile.prepareToPlay()
} catch let err as NSError {
print(err.debugDescription)
}
}
// Methods
func playAudioFile() {
if audioFile.playing {
audioFile.stop()
} else {
audioFile.play()
}
}
}
And your ViewController.swift to:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player1: Player!
override func viewDidLoad() {
super.viewDidLoad()
// Note, that you need to initialize the player1 variable
player1 = Player()
}
#IBAction func playAudio(sender: AnyObject) {
player1.playAudioFile()
}
}

Swift is functional programming, so it is way better to separate the audio operation and viewController into different file/class. Then they could be reused in any scenarios.
Audio part
import AVFoundation
class AudioPlayer {
// Singleton to keep audio playing anywhere
static let shared = AudioPlayer()
var player: AVAudioPlayer?
private init() {}
func play(url: URL) {
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch {
print("error occurred")
}
}
}
ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playAudio(sender: AnyObject) {
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
// don't use force unwrap
guard let audioFileURL = NSURL(fileURLWithPath: audioSnd) else { return }
// call the shared instance here
AudioPlayer.shared.play(url: url)
}
}

Related

Unable to get AVAudioPlayer to stop playing

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.

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

How to fix Thread 1: EXC_BAD_ACCESS (code=1, address=0x58) xcode

I am trying to play some sound files with buttons but pressing the button throws me this error Thread 1: EXC_BAD_ACCESS (code=1, address=0x58) on line
audioPlayer.play()
I have looked for possible solutions and I can't find anything related to that error, the function of my code runs well until the print, this is my complete code.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var track: String? = nil
var audioPlayer = AVAudioPlayer()
#IBAction func heavyButton(_ sender: Any) {
track = "H"
print("heavy machine gun \(track!)")
reproducirAudio(audio: track!)
audioPlayer.play()
}
func reproducirAudio(audio: String) {
do {
print("entro a la funcion de reproducir")
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: audio, ofType: "mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
}
i found a solution, this is for iOS13
import UIKit
import AVFoundation
class ViewController: UIViewController {
var track: String? = nil
var audioPlayer: AVAudioPlayer?
#IBAction func heavyButton(_ sender: Any) {
track = "H"
print("heavy machine gun \(track!)")
reproducirAudio(audio: track!)
}
func reproducirAudio(audio: String) {
let path = Bundle.main.path(forResource: "\(audio).mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
// couldn't load file :(
}
}

audioPlayerDidFinishPlaying func issues

I am trying to make my app so that when a user touches a UIImageView A certain sound will play. However, while that sound is playing, I want the UIImageView.isUserInteractionEnabled to be assigned to false. Once the sound is done playing I want the UIImageView.isUserInteractionEnabled to be assigned to true. When ever I run the following code I get an error in the card class, even if I force unwrap. Below is the class that contains the image view I want to disable.
class SecondViewController: UIViewController , UIGestureRecognizerDelegate {
#IBOutlet weak var imgPhoto: UIImageView!
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
imgPhoto.isUserInteractionEnabled = false
itemList[imageIndex].playSound()
}
}
This is the class where the playSound func is located.
import Foundation; import UIKit; import AVFoundation
class Card: NSObject
{
var player: AVAudioPlayer?
var svc = SecondViewController()
var image: UIImage
var soundUrl: String
init(image: UIImage, soundUrl: String, isActive:Bool = true) {
self.image = image
self.soundUrl = soundUrl
}
func playSound()
{
guard let url = Bundle.main.url(forResource: self.soundUrl, withExtension: "m4a") else { return }
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
player?.delegate = self
guard let player = player else { return }
player.prepareToPlay()
player.play()
audioPlayerDidFinishPlaying(player)
print("play")
} catch let error {
print(error.localizedDescription)
}
}
}
extension Card: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer){
svc.imgPhoto.isUserInteractionEnabled = true
}
}
this is the error I get
The problem is this line:
var svc = SecondViewController()
That svc is not your SecondViewController, the existing one in the interface with an imgPhoto. Instead, you are creating a new, separate, and above all blank view controller with an empty view, so its imgPhoto is nil and you crash when you refer to it.
What you want to do is use the "delegate" pattern to hold a reference to the real SecondViewController so you can talk to it.

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