import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioPlayer : AVAudioPlayer!
let soundArray = ["dog","cow","bear","pig"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func dogSound(_ sender: UIButton) {
playSound(soundName: soundArray[sender.tag - 1])
}
func playSound (soundName : String) {
let soundURL = Bundle.main.url(forResource:soundName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}
You can see the image below I have a problem with unwrapping. I am new to the Swift language, and this is the 3rd day, I am trying to solve this problem but I can't find the solution. Can you help me and tell how to fix this nil thing? Cheers
fatal error: unexpectedly found nil while unwrapping an Optional value
Reason for error : The sound file being used doesn't exist in the project. Please check either the names or the extension(.wav, .mp3) of the sound files.
Detail description :
This error occurs when the value you are using doesn't exist, in your case the sound file that you are trying to use by passing names in the array doesn't exist.
Possible reasons :-
1) The sound file that you are using doesn't exists in the project
2) There is some name or extension(.wav, .mp3) mismatch of the sound file in the project and their name being used in code.
Debugging issue :-
1) Try print the name of the sound file (soundArray[sender.tag - 1]) as 1st line in
func playSound (soundName : String) {
}
Related
I am building an app with several view controllers. I need to control music from all of them so I have created a dedicated music class which I use to setup / play / stop / pause.
I have recorded audio questions and answers and I need to be able to play the question and then the answer mp3 files.
So I believe that here a few way to accomplish this delegate and protocols, using the func audioPlayerDidFinishPlaying and using closures. From what I can understand closures are the best option for what I am trying to achieve.
My starting point in the MakeMusic Class is:
class MakeMusicClass : NSObject, AVAudioPlayerDelegate {
static let shared = MakeMusicClass()
var audioPlayer = AVAudioPlayer()
override init() { }
func setup(Selection: String) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: Selection, ofType: "mp3")!))
audioPlayer.prepareToPlay()
audioPlayer.delegate=self
} catch {
print (error)
}
}
func play() {
audioPlayer.play()
}
My calling file is:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
MakeMusicClass.shared.setup(Selection: "Question11")
MakeMusicClass.shared.play()
MakeMusicClass.shared.setup(Selection: "Answer11")
MakeMusicClass.shared.play()
To get this working I understand that I need to add a closure to the called class:
func play() {
var closure = { in
audioPlayer.play()
}
}
And I need to update where I need to call the function something like:
override func viewDidLoad() {
super.viewDidLoad()
MakeMusicClass.shared.setup(Selection: "Question11")
MakeMusicClass.shared.play() {
MakeMusicClass.shared.setup(Selection: "Answer11")
MakeMusicClass.shared.play()
}
I have spent ages trying to get my head around this, but I am struggling. My code here is clearly not working as there is something fundamental that I am missing. I have tried passing void and parameters, but I don't understand what parameters should be passed. The closest I have come is using the audioPlayerDidFinishPlaying within the makemusic class trigger the next audio file, but I don't know that this is ideal.
I think your best bet will be in your MakeMusicClass taking an Array as an initialiser e.g. a question and and answer, and then use AVAudioPlayerDelegate to trigger the next file, which I think is what you are trying to get at (and very close ^________*).
For example:
class AudioLooper: NSObject, AVAudioPlayerDelegate {
var debugView = true
var audioLoopPlayer: AVAudioPlayer!
var audioFileIndex: Int = 0
var audioFileArray: [String] = []
//-------------------------------------------------
//MARK: Audio Player Initialisation & Functionality
//-------------------------------------------------
/// Function To Initialise The Audio Loop Player
///
/// - Parameter audioFiles: [String] - The Array Of Audio Files To Play
func initAudioPlayerWith(audioFiles: [String]) {
audioFileArray = audioFiles
if debugView { print("Audio Files To Play In Sequence == \(audioFileArray)") }
}
/// Function To Play An Array Of Audio Files
///
/// - Parameter index: (Int) - The Current Index Of The Audio Sequence
func playAudioLoopAt(index: Int) {
let currentAudioFile = "\(audioFileArray[audioFileIndex])"
if let audioFilePath = Bundle.main.path(forResource: currentAudioFile, ofType: "mp3"){
do {
let audioFileUrl = NSURL.fileURL(withPath: audioFilePath)
// Set An Instance Of AVAudioSession Which Allows Playback Even When Device Is Muted
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
audioLoopPlayer = try AVAudioPlayer(contentsOf: audioFileUrl)
audioLoopPlayer.prepareToPlay()
audioLoopPlayer.delegate = self
audioLoopPlayer.play()
if debugView { print("Playing \(currentAudioFile) ") }
audioFileIndex += 1
} catch {
print("Error Playing Audio")
}
} else {
print("Error Finding File: \(currentAudioFile)")
}
}
/// Function To Continue The Audio Sequence
#objc func continueAudioLoop(){
playAudioLoopAt(index: audioFileIndex)
}
/// Function To Stop The Audio Looop Player
func stopAudioPlayer() {
if audioLoopPlayer != nil {
audioFileIndex=0
audioLoopPlayer.stop()
audioLoopPlayer = nil
}
}
//-----------------------------
//MARK: AVAudio Player Delegate
//-----------------------------
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if audioFileIndex < audioFileArray.count {
self.perform(#selector(continueAudioLoop), with: self, afterDelay: 0.5)
}else{
audioFileIndex=0
}
}
}
In my example you simply init like so:
audioLooper = AudioLooper()
audioLooper?.initAudioPlayerWith(audioFiles: ["question", "answer"])
audioLooper?.playAudioLoopAt(index: 0)
Where audioLooper is declared like so : var audioLooper:AudioLooper?
Clearly my example is not a Singleton, but it should give you an idea of how you can adjust your MakeMusicClass to make it fit...
You can also add a Delegate Method like so to inform the ViewController the audio has finished or perform some other task like update the next question etc e.g:
#objc protocol AudioLooperDelegate {
#objc optional func update()
}
Then in your ViewController:
var delegate: AudioLooperDelegate?
And in the AudioLooper Class you can then add the delegate method where needed e.g:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if audioFileIndex < audioFileArray.count {
self.perform(#selector(continueAudioLoop), with: self, afterDelay: 0.5)
}else{
audioFileIndex=0
delegate?.updateUI!()
}
}
}
I am developing an app which plays a random song on device shake. Following is my code
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if event?.subtype == UIEventSubtype.motionShake{
let soundArray=["firstsong","second","fourth"]
let randomNumber=Int(arc4random_uniform(UInt32(Int32(soundArray.count))))
let fileLocation = Bundle.main.path(forResource: soundArray[randomNumber], ofType: "mp3")
do {
try player=AVAudioPlayer(contentsOf:URL(fileURLWithPath:fileLocation!))
player.play()
}catch{
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have placed my mp3 files in music folder.
When i shake the device i get the following error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Any help would be greatly appreciated
Wait. You said you placed the files in your music folder? Your code is wrong then. The code you posted tries to read the files from inside your app bundle. You need to use a different approach entirely to read files from the user's music folder, and need to write that code to check to see if the file exists and fail gracefully if not.
I suggest dragging your sound files into the "resources" folder in your project in Xcode, and making sure the checkbox for the files is checked so that they are included in your app bundle. If you do that your code should work (but you should still check for nil rather than using force unwrapping, as mentioned in my comment above.)
I am working on this recorder project and this code is written in swift 2.0 and it is giving this issue!
I have seem similar title post but is not related to the issue I am having
import UIKit
import AVFoundation
class PlaySoundViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
var receivedAudio: AudioRecorded!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
do{
let audioPlayer = try AVAudioPlayer(contentsOfURL: receivedAudio.filePathUrl) --> ***The error happens here***
audioPlayer.enableRate = true
}catch let ErrorType {
NSLog("Could not create AVAudioPlayer: \(ErrorType)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func playFastSound(sender: AnyObject) {
audioPlayer.stop()
audioPlayer.play()
audioPlayer.rate = 2.0
}
#IBAction func playSlowSound(sender: AnyObject)
{
audioPlayer.stop()
audioPlayer.play()
audioPlayer.rate = 0.5
}
#IBAction func stopPlaying(sender: AnyObject) {
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.
}
*/
}
Your issue may be the use of try! during allocation.
As HWS puts it -
There's one more thing to discuss, which is what to do if you know a call simply can't fail, for whatever reason. Now, clearly this is a decision you need to make on a case-by-case basic, but if you know there's absolutely no way a method call might fail, or if it did fail then your code was so fundamentally broken that you might as well crash, you can use try! to signal this to Swift.
So that's not what you want, probably.
For error handling, you'd typically use try (without the !).
do {
let audioPlayer = try AVAudioPlayer(contentsOfURL: receivedAudio.filePathUrl)
// Do stuff with audio player
}
catch let error {
NSLog("Could not create AVAudioPlayer: \(error)")
}
Now that doesn't explain why the audio player isn't being created. Most likely the URL is invalid, the file it points to doesn't exist, or it's encoded in something AVAudioPlayer can't read. Typically you'll want a standard file format like mp3, mp4, etc.
So, I have mastered the art of playing sounds/music using one view in iOS, but I am now trying to make a more robust app for an musical artist. Thus far, it involves segues from a "menu" ViewController to his Bio, Show Times, and "Listening Room" etc. Thus far, I created a an "AudioManager" class:
import UIKit
import AVFoundation
import MediaPlayer
class AudioManager: NSObject {
let defaltSong = ["Hell&BackCaf/01 Black Sheep", "caf"]
weak var delegate : PlayerDelegate?
var musicPlayer1 = AVAudioPlayer()
var trackNumber = 0
var musicAudioPath = NSBundle.mainBundle().pathForResource("Hell&BackCaf/01 Black Sheep", ofType: "caf")
var musicAudioPathURL = NSURL()
var error:NSError? = nil
var songList = [["Hell&BackCaf/01 Black Sheep", "caf"], ["Hell&BackCaf/02 Hell & Back", "caf"], ["Hell&BackCaf/03 Save Me", "caf"], ["Hell&BackCaf/04 Broken feat. Hillary Dodson", "caf"], ["Hell&BackCaf/05 Do Or Die", "caf"], ["Hell&BackCaf/06 Divided", "caf"]]
func ButtonPlay(song: NSString, type: NSString) {
error = nil
musicAudioPath = NSBundle.mainBundle().pathForResource(song as String, ofType: type as String)
musicAudioPathURL = NSURL(fileURLWithPath: self.musicAudioPath!)!
musicPlayer1 = AVAudioPlayer(contentsOfURL: musicAudioPathURL, error: &error)
musicPlayer1.prepareToPlay()
musicPlayer1.play()
}
func loadFirstSong() {
error = nil
musicAudioPath = NSBundle.mainBundle().pathForResource("Hell&BackCaf/01 Black Sheep", ofType: "caf")
musicAudioPathURL = NSURL(fileURLWithPath: self.musicAudioPath!)!
musicPlayer1 = AVAudioPlayer(contentsOfURL: musicAudioPathURL, error: &error)
if error == nil {
musicPlayer1.prepareToPlay()
} else {
println(error)
}
}
func advanceTrack(){
if trackNumber < songList.count - 1 {
self.trackNumber++
ButtonPlay(songList[trackNumber][0], type: songList[trackNumber][1])
} else {
trackNumber = 0
ButtonPlay(songList[trackNumber][0], type: songList[trackNumber][1])
}
}
func previousTrack(){
if trackNumber > 0 {
trackNumber--
ButtonPlay(songList[trackNumber][0], type: songList[trackNumber][1])
} else {
trackNumber = songList.count - 1
ButtonPlay(songList[trackNumber][0], type: songList[trackNumber][1])
}
}
func audioPlayerDidFinishPlaying(AVAudioPlayer!, successfully: Bool) {
self.delegate?.soundFinished(self)
println("song over")
}
}
I then used it in my MusicRoomViewController:
import UIKit
import AVFoundation
class MusicRoomViewController: UIViewController, AVAudioPlayerDelegate {
let audioManager = AudioManager()
#IBAction func pressedBackButton(sender: UIButton) {
audioManager.previousTrack()
}
#IBAction func pressedPlayButton(sender: UIButton) {
audioManager.musicPlayer1.play()
}
#IBAction func pressedForwardButton(sender: UIButton) {
audioManager.advanceTrack()
}
override func viewDidLoad() {
super.viewDidLoad()
audioManager.loadFirstSong()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillDisappear(animated: Bool) {
if self.isMovingFromParentViewController()
{
audioManager.musicPlayer1.play()
}
}
}
But now when I navigate to any other 'page' of the app, the music stops. After researching, I know that I am going about this all wrong, but I can't seem to figure out what I need to do to keep the audio playing until the app is closed or the user presses stop (which does not exist yet, I know). Any thoughts??
You're going to have to make your AudioManager class a singleton. What is happening, is the AudioManager instance you're creating is being deallocated once you navigate away from the view you created it in. A singleton object is created once it is first accessed, and will persist in memory until the application's lifecycle ends or it is explicitly deallocated. For more information on Swift singletons, check out this page. It's a useful pattern to learn.
Make the following modifications to AudioManager (taken from the above website):
private let _AudioManagerSharedInstance = AudioManager()
class AudioManager {
static let sharedInstance = AudioManager()
}
Access AudioManager by using AudioManager.sharedInstance, for example you can call AudioManager.sharedInstance.previousTrack().
You could create a singleton, or instantiate the AudioManager in your AppDelegate class and refer to that like so:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.audioManger...
I'm trying to play a simple sound when I push a button. xcode is forcing me to use a "!" for my NSURL for the audio file, I followed other tutorials for how to play a sound in swift and for some reason I keep getting this error, why!? Code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("buttonSound", ofType: "mp3")
let fileURL = NSURL(fileURLWithPath: path!)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.prepareToPlay()
}
#IBAction func button21(sender: AnyObject) {
player.play()
}
}
When I do add the "!", the app will load without crashing, when I press the button the app crashes and the log outputs "fatal error: unexpectedly found nil while unwrapping an Optional value"
Good to see your problem got solved. Just adding to the conversation:
I had the same issue as you and I followed the advice given by other people here, but it didn't get rid of my problem (unexpectedly found nil while unwrapping an Optional value). I'm using Xcode 6.1 and the thing that solved it for me was deleting the file and adding it again except this time, instead of dragging the file in, I did it by right-clicking the project folder and selecting the "Add Files to your_app's_name" option.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var myPlayer = AVAudioPlayer()
var yourSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("alarm2", ofType: "mp3")!)
func prepareYourSound() {
myPlayer = AVAudioPlayer(contentsOfURL: yourSound, error: nil)
myPlayer.prepareToPlay()
}
override func viewDidLoad() {
super.viewDidLoad()
prepareYourSound()
// 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.
}
#IBAction func playMySound(sender: AnyObject) {
myPlayer.play()
}
}
HERE is the project sample link
Sample Project
A bit old now, but I had the same problem recently.
The way I fixed it was to select my file (an mp3, too) and go into the File Inspector and make sure the Target Membership was checked for my project.