Search code examples
iosswiftavaudioplayer

AVAudioPlayerDidFinishPlaying never gets called, so "other sounds" remain "ducked" - What am I doing wrong?


Many questions on this topic, but most are so old the answers no longer apply due to both Swift and iOS having evolved so much since they were posted. The few that look helpful at first glance don't actually tell me anything I haven't already figured out, and haven't done anything to help me fix my incarnation of the problem, despite appearing to be nearly identical to the problem I'm having.

Aside from AVAudioPlayerDidFinishPlaying never being called (And therefore leaving "other sounds" being made by the device "ducked" until the app is totally shut down) the code below functions exactly as expected, in both Simulator and on a physical iPhone 7 Plus running iOS 13.3. If it makes a difference, I'm coding to Swift 5.1, targeting iOS 13.3, in Xcode 11.3, on a rig that runs Catalina (10.15.2).

Here's the "broken" code:

import Foundation
import AVFoundation

class NoiseMaker: NSObject, AVAudioPlayerDelegate {
    var ourSession = AVAudioSession()
    var ourPlayer  = AVAudioPlayer()

    func playSound(sound: String) {
        print("Entered NoiseMaker.playSound(sound: \(sound))")
        let theURL: URL = Bundle.main.url(forResource: sound, withExtension: "mp3")!

        try!ourSession.setCategory(AVAudioSession.Category.ambient, options: AVAudioSession.CategoryOptions.duckOthers)
        try!ourSession.setActive(true)
        ourPlayer.delegate = self
        ourPlayer.prepareToPlay()
        do {
            ourPlayer = try AVAudioPlayer(contentsOf: theURL)
            ourPlayer.play()
        } catch let theError as NSError {
            print("Couldn't load \(sound).mp3 - Error \(theError)")
        }
    }

    // MARK: - AVAudioPlayer delegate methods
    // Never gets called - Other sound sources remain "ducked" until the app is completely shut down.
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        print("Arrived in audioPlayerDidFinishPlaying")
        try!ourSession.setActive(false)
    }

    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        print("Decoding error from AVAudioPlayer: \(error!.localizedDescription)")
    }
}

As noted, everything works as expected - EXCEPT for my audioPlayerDidFinishPlaying method never being called, meaning I never get a chance to "unduck" other sounds that might be playing. (although everything "unducks" as soon as the app is completely shut down)

Why? How am I screwing up what seems to be a nearly trivial task?


Solution

  • I see the problem, you set self as the delegate but then you change the reference to a new instance of AVAudioPlayer

    You should set the delegate after creating new instance in here

    ourPlayer = try AVAudioPlayer(contentsOf: theURL)
    ourPlayer.delegate = self
    ourPlayer.play()