dmann200
dmann200

Reputation: 589

Calling stop() on AVAudioPlayerNode after finished playing causes crash

I have an AVAudioPlayerNode object which sends a callback once it is finished playing. That callback triggers many other functions in the app, one of which sends out a courtesy stop() message. For some reason, calling stop() at the moment the AVAudioPlayerNode finishes causes a crash. In the code here, I have abbreviated it so that the AVAudioPlayerNode just calls stop immediately to demonstrate the effect (rather than including my whole application). You can see clearly that it crashes. I don't understand why. Either a) the node is still playing and stop() stops it or b) it is done playing and stop can be ignored.

My guess is that this is some edge case where it is at the very end of the file buffer, and it is in an ambiguous state where has no more buffers remaining, yet is technically still playing. Perhaps calling stop() tries to flush the remaining buffers and they are empty?

func testAVAudioPlayerNode(){
    let testBundle = Bundle(for: type(of: self))
    let url = URL(fileURLWithPath: testBundle.path(forResource: "19_lyrics_1", ofType: "m4a")!)
    let player = AVAudioPlayerNode()
    let engine = AVAudioEngine()
    let format = engine.mainMixerNode.outputFormat(forBus: 0)
    engine.attach(player)
    engine.connect(player, to: engine.mainMixerNode, format: format)
    let delegate = FakePlayMonitorDelegate()
    do {
        let audioFile = try AVAudioFile(forReading: url)
        let length = audioFile.length
        player.scheduleFile(audioFile, at: nil, completionCallbackType: AVAudioPlayerNodeCompletionCallbackType.dataPlayedBack, completionHandler: {(completionType) in
            print("playing = \(player.isPlaying)")
            player.stop()
        })
        try engine.start()
        let expectation = self.expectation(description: "playback")
        delegate.expectation = expectation
        player.play()
        self.waitForExpectations(timeout: 6.0, handler: nil)
    } catch {

    }
}

Upvotes: 3

Views: 1838

Answers (3)

Alexander
Alexander

Reputation: 51

From the docs

Note that a player should not be stopped from within a completion handler callback because it can deadlock while trying to unschedule previously scheduled buffers.

https://developer.apple.com/documentation/avfoundation/avaudioplayernode?language=objc

Upvotes: 0

dakjac
dakjac

Reputation: 1

Similarly to Orest, I was able to overcome this issue by placing the .stop() within Task.

Task { 
player.stop()
}

Upvotes: 0

Orest
Orest

Reputation: 61

In my case calling the .stop() method from the Main thread (you can use another) has resolved the issue.

DispatchQueue.main.async {
    player.stop()
}

It can be the deadlock. I have noticed that the completionHandler is invoked from the background queue which is a serial queue, let's call it a "render queue". Seems that the .stop() method is trying to do some work on a "render queue" but at the moment a "render queue" is busy with the completionHandler.

Deadlock screenshot

Upvotes: 6

Related Questions