EXC_BAD_ACCESS when using AVAudioPlayer

Hello,


I am trying to use an AVAudioPlayer to play a song from iTunes, but I'm having a lot of difficulty.


Whenever I try to do something to my AVAudioPlayer even after giving it a URL, such as pausing it or trying to play at a certain time, my app crashes with EXC_BAD_ACCESS. I've looked online and it seems ike this is because I haven't put a URL in, but I have already done that.


Let me run you through my code.


First, I make an AVAudioPlayer in the class-level.

var player = AVAudioPlayer()


Next, in a function called a short time after the viewDidLoad, I run this code.


//Gathering the song to play

        let songs = MPMediaQuery.songs()

       let predicateByGenre = MPMediaPropertyPredicate(value: "[EXACT SONG TITLE]", forProperty: MPMediaItemPropertyTitle)

        songs.filterPredicates = NSSet(object: predicateByGenre) as? Set
        
        let url = (songs.items![0]).assetURL
        do {
            try player = AVAudioPlayer(contentsOf: url!)
        } catch {
            fatalError("Could not load the file when trying to load a song in wander mode")
        }
        
        //Updating the nowPlayingItem
        nowPlayingItem = songs.items![0]
       //nowPlayingItem is an MPMediaItem variable created so that the code can find out attributes about the current song when it needs to such as title, artist, etc.

        //Playing the player
        NSLog("Playing!")
        player.play()

Now, the player should hopefully have a url assigned to it with the song I have in my iTunes library I want it to play.


Now back to the viewDidLoad. In it, I have a small chunk of code:

delay(5, completion: {
            self.player.pause()
        })

The delay function is a function that, obivously, delays the code in completion argument by a set time defined in the argument just before.


Pausing the player seems like an easy thing to do, but when I do, the app crashes with EXC_BAD_ACCESS on the line where I try to pause it.

I don't understand what I going on; the player should have a url and shouldn't cause a bad access. Does anyone know what to do in this situation? Thanks in advance. Any help is appreciated.

Accepted Reply

So I actually found out why this is happening and fixed it. It turned out to be that my before the player was given a song, my code went out into a different file to do a few things and created a new instance of viewcontroller when it came back and tried to edit player. Thank you both for your help!

Replies

Is delay() your own function ?


Usually, you do it with :

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    self.player.pause()
}


Is it what you have implemented ?


Could you show the trace of the crash ?


could you tst what is player value inside delay ?

Here is the full delay function, which is inside an extension of the UIViewController class so I can have it in all my view controller classes.

func delay(_ seconds: Double, completion: @escaping () -> ()) {
        DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
            completion()
        }
    }



The full error is as follows: "Thread 1: EXC_BAD_ACCESS (code=1, address=0x48)" In the console, there is nothing except (11db) when it crashes. Here is an image of the full Debug Navigator: https://i.imgur.com/P47zWRz.png


So, each on separate occasions, I've told the console to print a few different values relating to the player in the delay. Here they are:

self.player: "<AVAudioPlayer: 0x1c0014ee0>"

self.player.data: [Crashes with the same error, except the adress in the error title is 0x10, not 0x48]

self.player.isPlaying: [Crashes with the exact same error, even the 0x48]

self.player.currentTime: [Crashes with the exact same error, even the 0x48]

One bad thing in your code exists in this line:

var player = AVAudioPlayer() 

With this code, you create a completely useless instance and assigning it to the property `player`.

You cannot ditinguish if the `player` has something properly initialized, or if it has a completely useless instance.


Change it to:

var player: AVAudioPlayer?

And use Optional chaining when accessing the `player`.

For example:

delay(5, completion: {
    self.player?.pause()
})


You are not showing enough code to reproduce your issue, we cannot find how long is a short time and how you run this code.

And it is not obvious how you define your `delay` function.

You may need to reveal all such things if the fix above is not sufficient to resolve your issue.

By a short time, I meant after going through a few other functions (because my app is really complex) and checks, it runs that function. None of the functions or checks would impact this issue, though.


Your solution would work, it's just that when the player is actually playing it doesn't pause it. When that code in the delay function is called, there is music playing, I can hear it, yet it doesn't ever get paused.


The full delay function can be seen above in another reply once it gets approved by the moderator (right now it still says pending).

it's just that when the player is actually playing it doesn't pause it.

Then you may have instantiated another AVAudioPlayer than in the property `player`.

Anyway if you say you cannot show relevant parts of your code, we can be very little help of you.

Code is moderated probably because you have a url inside.


If so, clear the URL replace by "anURL", and post the rest of delay function.


In fact, if player is nil, player?.pause is just skipped. It doean't crash, but nothing is executed.

So I did a search for "AVAudioPlayer" in my file and only found two occurances. One was the inital declaration "var player: AVAudioPlayer?", and one was in the function where it assigns the url. I have a log statement at the beginning of this function and it only shows up once in the console, like it should. The exact code for that is:


let url = (songs.items![0]).assetURL
        do {
            player?.pause()
            try player = AVAudioPlayer(contentsOf: url!)
        } catch {
            fatalError("Could not load the file when trying to load a song")
        }

So, could you post the delay function ?

Did you test if player is nil in the delay func ?

As I wrote, I do not know how you run the code, so I cannot see if the code may be executed twice (or more times) or not.


Please try to make a minimized project which can reproduce the same issue, and show all relevant codes.

So now I've been looking at different ways to see if I couod fix this still to no avail. I've done some testing, and it seems that while the player is not nil in the function where the music is set, the player is nil in the delay function called in viewDidLoad. I even called a separate function from the function where I set the music for the player and it was not nil while in the delay function it was. I've checked over and over again and there is only one variable or constant called player, nothing else. I also did a search for the word "player" and made sure every instance was supposed to be there. There was nothing wrong I could find, and certainly nothing that set it to nil. What is going on here?

Could you show the delay func ?


The question is to understand why it is nil. Have you looked at everyplace where you reference player ? and added a test to check if it is nil:

print(#function, player == nil ? "It is nil" : "non nil")


You could also run a thread sanitizer to see if you have a thread issue.

Sorry about the delay function thing; I tried to upload on a previous reply but apparently it didn't work. Here it is:

func delay(_ seconds: Double, completion: @escaping () -> ()) {
        DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
            completion()
        }
    }


So in every place the player could possiby change value I put that line of code. Here's what it looked like:

print(#function, player == nil ? "3It is nil" : "3non nil")
        //Gathering the song to play
        let songs = MPMediaQuery.songs()
        let predicateByGenre = MPMediaPropertyPredicate(value: (Locations().wanderLocations[locationID]!)[2] as! String, forProperty: MPMediaItemPropertyTitle)
        ///EVENTUALLY UPON COMPLETION OF THE APP WANDERLOCATIONS[LOCATIONID][2] WILL NOT BE A NAME, RATHER AN MPMEDIAITEM. THIS IS WHERE THAT CHANGE WILL TAKE PLACE
        songs.filterPredicates = NSSet(object: predicateByGenre) as? Set
        //Now we have the songs array which has 1 song
        print(#function, player == nil ? "4It is nil" : "4non nil")
        let url = (songs.items![0]).assetURL
        do {
            print(#function, player == nil ? "5It is nil" : "5non nil")
            player?.pause()
            try player = AVAudioPlayer(contentsOf: url!)
            print(#function, player == nil ? "6It is nil" : "6non nil")
        } catch {
            fatalError("Could not load the file when trying to load a song in wander mode")
        }
        print(#function, player == nil ? "7It is nil" : "7non nil")
       
        //Updating the nowPlayingItem
        nowPlayingItem = songs.items![0]
        print(#function, player == nil ? "8It is nil" : "8non nil")
       
        //Playing the player
        NSLog("Playing!")
        player?.play()
        print(#function, player == nil ? "9It is nil" : "9non nil")

I put the numbers in every print statement so I could tell which one it was. I also put one just before the player is told to pause in the delay function, which is still in the viewDidLoad.


delay(5, completion: {
            print(#function, self.player == nil ? "1It is nil" : "1non nil")
            self.player?.pause()
        })


In the console I filtered for "nil" since every outcome would have the word nil in it, and this is what it showed me.

g 3It is nil

playWanderSong 4It is nil

playWanderSong 5It is nil

playWanderSong 6non nil

playWanderSong 7non nil

playWanderSong 8non nil

playWanderSong 9non nil

viewDidLoad() 1It is nil


Hopefully this helps you figure it out. By the way, playWanderSong is the function in which the player is assigned a song and told to play.

Looks like you have a synchro issue. To confirm, would be useful to see as well the part where you call playWanderSong


What could happen:

- You call playWanderSong (in viewDidload ?)

- this is dispatched to another thread

- then you continue viewDidload in main thread

- you call delay, and when you copy self.player in the call, it is not yet initialized in the other thread.

- So, when you execute the completion code later, player is nil


One way to check would be to add:

print(#function, player == nil ? "0It is nil" : "0non nil")

just before the call to delay().

And see if it is executed before you get

playWanderSong 6non nil

As you clearly find that `player` in your `playWanderSong` is not nil, and `self.player` in `viewDidLoad` is nil.


As I wrote in another comment:

you may have instantiated another AVAudioPlayer than in the property `player`.


Don't you have another variable named `player`? Or aren't you executing the `playWanderSong` in another instance than the view controller executing `delay`?

So I actually found out why this is happening and fixed it. It turned out to be that my before the player was given a song, my code went out into a different file to do a few things and created a new instance of viewcontroller when it came back and tried to edit player. Thank you both for your help!