Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Audio playback not returning to original volume in Swift

I am trying to play a "Ding" noise to alert the user of an event. The "Ding" plays but the background audio (in this case, the default Music.App) does not return to its original volume after playing the ding. It will return to normal volume after closing the app however. This is what I have:

This is where I set my audio session category:

     public override func viewDidLoad() {
            super.viewDidLoad()
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.DuckOthers)
                //refer to this link for swift's sound reference: https://developer.apple.com/ios/human-interface-guidelines/interaction/audio/
            } catch {
                print("unable to load audio session")
            }
            ....
     }

This is where I call my functions:

if(!currentTimer.launchedNotification){
            playFinishedSound()
            AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) //handles phone vibration
            audioPlayerDidFinishPlaying(audioPlayer!, successfully: true)

        }

And here is my private function:

private func playFinishedSound(){
        if let pathResource = NSBundle.mainBundle().pathForResource("Ding", ofType: "wav"){
            let soundToPlay = NSURL(fileURLWithPath: pathResource)
            do {
                audioPlayer = try AVAudioPlayer(contentsOfURL: soundToPlay)
                if(audioPlayer!.prepareToPlay()){
                    print("preparation success")
                    audioPlayer!.delegate = self
                    setAVAudioSession()
                    if(audioPlayer!.play()){
                        print("Sound play success")
                    }else{
                        print("Sound file could not be played")
                    }
                }else{
                    print("preparation failure")
                }

            }catch{
                print("Sound file could not be found")
            }
        }else{
            print("path not found")
        }
    }

Here is where I set my audio session:

private func setAVAudioSession() {
        let session:AVAudioSession = AVAudioSession.sharedInstance()
        do{
            try session.setActive(true)
            print("session is active")
        }catch{
            print("could not make session active")
        }
    }

This is the delegate method for the AVAudioPlayerDelegate protocol:

public func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
        do {
            player.stop()
            try AVAudioSession.sharedInstance().setActive(false, withOptions: AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation)
            player.prepareToPlay()
            print("Session stopped successfully")
        }catch{
            print("Could not end audio session")
        }
    }

Again, the problem is that if there is music playing in the background, the music volume will soften, but when the audio session is inactive, the music volume does not return back to normal.

like image 856
Q.H. Avatar asked Oct 29 '22 22:10

Q.H.


1 Answers

After experimenting with this, the solution I have works well. The only caveat here is a delay on the device for playing the sound if you are trying to play a countdown sound or other sounds in quick succession, so may need some thread management or other means to resolve that issue. Any improvements to this solution are welcome.

private let session = AVAudioSession.sharedInstance()

private func setSession(isActive: Bool) {
    if isActive {
        try! session.setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.duckOthers)
    } else {
        try! session.setCategory(AVAudioSession.Category.ambient, options: AVAudioSession.CategoryOptions.mixWithOthers)
    }
    try! self.session.setActive(isActive, options: .notifyOthersOnDeactivation)
}

When you are about to play, pause or stop your audio, call this method.

  1. Setting the category to playback and option to duckOthers ensures your playback is the loudest and other audio is decreased in volume.

  2. Setting the category to ambient and option to mixWithOthers ensures your playback is equal to other audio types which results in the audio increasing back to the volume it was in prior to playing.

  3. Finally set the audio state to active or inactive (setActive)

Word of caution:

Using try! is certainly not a recommended practice and I have provided this code as a means to get your ball rolling. Implement best safety practices where applicable.

like image 76
App Dev Guy Avatar answered Nov 11 '22 17:11

App Dev Guy