Why am I getting this error?

I’ve got a number of mp3 files in my project bundle and am trying to create an audio player view.
I’ve set a number of breakpoints and error messages for the console, but none of them appear to get triggered. The code seems to run successfully until AFTER the URL and assignment of the AVAudioPlayer objects (not triggering either of the print statements in my error-catching code) and then the app crashes with ‘Fatal error: unexpectedly found nil while unwrapping an optional value’

Here is my code:


import SwiftUI
import AVFoundation

struct AudioPlayerView: View {
    
    @State var audioPlayer = AVAudioPlayer()
    
    
    var body: some View {
        
        ZStack {
            VStack {
                Text("Play").font(.system(size: 45)).font(.largeTitle)
                HStack {
                    Spacer()
                    Button(action: {
                        self.audioPlayer.play()
                    }) {
                        Image(systemName: "play.circle.fill").resizable()
                            .frame(width: 50, height: 50)
                            .aspectRatio(contentMode: .fit)
                    }
                    Spacer()
                    Button(action: {
                        self.audioPlayer.pause()
                    }) {
                        Image(systemName: "pause.circle.fill").resizable()
                            .frame(width: 50, height: 50)
                            .aspectRatio(contentMode: .fit)
                    }
                    Spacer()
                }
            }
        }.onAppear {
            initSound()
        }
        
    }
    
    func initSound() {
        let soundPath = Bundle.main.path(forResource: "cleansing_clearing_breath", ofType: "mp3")
        let soundUrl = URL(fileURLWithPath: soundPath!)
        
        if soundUrl != nil {
            
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: soundUrl)
            } catch {
                print("Could not pass file to AVAudioPlayer")
            }
            
        } else {
            print("Could not create URL object")
            return
        }
    }
}

Here’s how I fixed it - I changed the audioPlayer variable to optional and implemented some logic around the display buttons. I think the problem is that the buttons were connected to an AVAudioPlayer which had not been initialized:

import SwiftUI
import AVFoundation

struct AudioPlayerView: View {
    
    @State var audioPlayer: AVAudioPlayer?
    
    var audioMeditation: String
    var audioMeditationFile: String
    
    var body: some View {
        
        ZStack {
            VStack {
                
                
                Text(audioPlayer != nil ? audioMeditation : "Could not find file")
                    .font(.title)
                HStack {
                    Spacer()
                    
                    if audioPlayer != nil {
                    Button(action: {
                        self.audioPlayer!.play()
                    }) {
                        Image(systemName: "play.circle.fill").resizable()
                            .frame(width: 50, height: 50)
                            .aspectRatio(contentMode: .fit)
                    }
                    Spacer()
                    Button(action: {
                        self.audioPlayer!.pause()
                    }) {
                        Image(systemName: "pause.circle.fill").resizable()
                            .frame(width: 50, height: 50)
                            .aspectRatio(contentMode: .fit)
                    }
                    Spacer()
                }
                }

            }
        }.onAppear {
            initSound()
        }
        
    }
    
    func initSound() {
        let soundPath = Bundle.main.path(forResource: "cleansing_clearing_breath", ofType: "mp3")
        let soundUrl = URL(fileURLWithPath: soundPath!)
        
        if soundUrl != nil {
            
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: soundUrl)
            } catch {
                print("Could not pass file to AVAudioPlayer")
            }
            
        } else {
            print("Could not create URL object")
            return
        }
    }
}

It would be better to create a class that is a SoundManager like this:

class SoundManager {
    static var audioPlayer: AVAudioPlayer?

    static func playMusic() {
        let soundFilename = "cleansing_clearing_breath"
        let bundlePath = Bundle.main.path(forResource: soundFilename, ofType: "mp3")

        guard bundlePath != nil else {
            print("Couldn't find sound file \(soundFilename) in the bundle")
            return
        }

        //  Create a URL object from this string path
        let soundURL = URL(fileURLWithPath: bundlePath!)

        do {
            //  Create audio player object
            audioPlayer = try AVAudioPlayer(contentsOf: soundURL)

            //  Play the sound
            audioPlayer?.play()

        } catch {
            // Could not create audio player object
            print("Could not create the audio player object for sound file \(soundFilename).")
        }
    }
}

and then change your AudioPlayerView to this:

import SwiftUI

struct AudioPlayerView: View {

    var body: some View {

        VStack {
            Text("Play")
                .font(.largeTitle)
            HStack {
                Spacer()
                Button(action: {
                    SoundManager.playMusic()
                }) {
                    Image(systemName: "play.circle.fill")
                        .resizable()
                        .frame(width: 50, height: 50)
                        .aspectRatio(contentMode: .fit)
                }
                Spacer()
                Button(action: {
                    SoundManager.audioPlayer?.stop()
                }) {
                    Image(systemName: "pause.circle.fill")
                        .resizable()
                        .frame(width: 50, height: 50)
                        .aspectRatio(contentMode: .fit)
                }
                Spacer()
            }
        }
        .onAppear {
            SoundManager.playMusic()
        }

    }
}
2 Likes

Thank you - this is a much cleaner approach!