Search code examples
swiftmacosavaudioplayer

AVAudioPlayer on Mac won't play / crashes


I'm trying to play an mp3 from my Mac Cocoa app. It looked pretty straightforward to use AVAudioPlayer. But it is not working. I've looked online and most of the problems I see are related to the player object being freed, but I don't think that is my problem. The player object is persistent and responds to member function calls.

I create the AVAudioPlayer from a URL like this:

class Soundtrack:Bobject, AVAudioPlayerDelegate {

    var player:AVAudioPlayer!
    var volume:Float = 1.0
    var pan:Float = 0.0
    var rate:Float = 1.0
    var numberOfLoops:Int = -1
    var url:URL?
    var reference = false

    ...

   func setFile(_ url:URL) throws {
    self.url = url
    player = try AVAudioPlayer(contentsOf: url)
    player.prepareToPlay()
    print("player duration: \(player.duration)")
    print("player settings: \(player.settings)")
    player.play()  // this is commented / uncommented out for testing
}

The Soundtrack object is an object I create and add to a persistent hierarchy of objects before I call the setFile function. The url points to a file that I browse to on the desktop before creating the Soundtrack -- for example "file:///Users/bob/Desktop/2_02_Cycle.mp3".

If I immediately call player.play(), as shown above, then it works. The song plays until I pause it. However, if I comment out that play() and call it later, by pressing a key for example, then it does not play. It says that it is successful:

player.prepareToPlay()
let ok = player!.play(atTime:atTime)
print("play \(ok)")

Here 'ok' returns true. Also, if I check the value of player.isPlaying over time, for about a second or so (80-90 frames) it returns TRUE also, then it stops. But I never hear anything. Checking the player settings reports the duration as correct, 175 seconds, and all of the rate/volume/pan information is also correct. I can call play() and pause() repeatedly and each time I play, it thinks it is playing for about one second.

However, I mentioned that the song does play if I start it immediately after creating the AVAudioPlayer. In this case, I can pause the song by calling pause(), but if I then try to play it again with play() or play(atTime:), then it crashes, with this: "com.apple.audio.IOThread.client (36): EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)". I have attached a picture of the stack trace.

EDIT: this morning after having the problem yesterday, it is no longer crashing. It just doesn't play unless I play it immediately after creating. Though it says it is playing, for about a second. Also, I printed out the player.currentTime variable for the second that it does "play", silently, and it says the time is some huge number, like 297130 or 594407. This despite the fact that I request a start play time of 0. Actually, upon further testing this number seems random -- sometimes it is low, like 10 or 35. When it is a reasonable number, then the player does report that it continues to play -- it does not stop after a second. But I never hear any sound.

I don't think it can be an issue of the player being freed, because I'm able to continue to reference it and call its functions throughout the process. Anyone know what else could be the problem?

enter image description here


Solution

  • Ok - I resolved this. I didn't realize that play(atTime:) uses the audio device current time. Once I used the deviceCurrentTime, it played. Pretty dumb error on my part. I have no idea though why it was crashing yesterday. That has not reoccurred.