How to lower background music volume when playing audio in Swift

Rahul Sharma
3 min readJul 30, 2023

--

If you are playing audio in your app, but the audio is not audible when user is listening to Music, you need to lower the backround music. With this, the background music’s volume will be reduced, then audio will be played. When the audio had been played, the background music will return to it’s original volume. This is called ducking background audio. We will learn how to do this in Swift using AVAudioPlayer and AVAudioSession.

Creating Audio Player

We need to create the audio player and audio session objects. For this, we will create a new class called SoundService that will handle the audio playing. We will be using AVFoundation so import it in the class file.

import AVFoundation

Inside the class, create 2 properties called alertPlayer and audioSession like this:

private var alertPlayer: AVAudioPlayer?
private var audioSession = AVAudioSession.sharedInstance()

Next, we need to prepare the audio player and set the audio session. We will create a function called loadSounds that will do this.

func loadSounds() {
guard let url = Bundle.main.url(forResource: "Alert", withExtension: "m4a") else {
print("ERROR:- Audio file named Alert.m4a not found.")
return
}
do {
try audioSession.setCategory(.ambient, options: .duckOthers) // 1
alertPlayer = try AVAudioPlayer(contentsOf: url) // 2
alertPlayer?.prepareToPlay() // 3
try audioSession.setActive(false) // 4
} catch let error {
print(error.localizedDescription)
}
}

Here’s what this code does:

  1. This sets the audioSession category of the app to ambient. Ambient is one of the many options available. When ambient mode is selected and the phone is in silent mode, the audio will be not be played.

Description of ambient category mode from Apple Developer Documentation:

This category is also appropriate for “play-along” apps, such as a virtual piano that a user plays while the Music app is playing. When you use this category, audio from other apps mixes with your audio. Screen locking and the Silent switch (on iPhone, the Ring/Silent switch) silence your audio.

We have also provided duckOthers in the options of the category. Because we want to duck the background audio instead of mixing with it.

Description of ambient category mode from Apple Developer Documentation:

An option that reduces the volume of other audio sessions while audio from this session plays.

2. We create the AVAudioPlayer object with the URL path of audio file that we imported.

3. We call prepareToPlay. This function prepares the audioPlayer to play the audio. Calling play() also calls prepareToPlay but calling it in advance reduces the delay when we play the audio. However, this is only for before the first play of audio. When the audio has been played successfully, we will need to call prepareToPlay again.

4. Calling prepareToPlay activates the audioSession, i.e. the audio will be ducked already. Since immediate playback is not required, we need to deactivate the audio session.

All this code is in a do catch block because the code can throw error.

We can now call loadSounds method to prepare the player and session.

Playing Audio

Next, we need to actually play the audio. For that, we will create a new method called playSound that will duck the audio, then play the sound.

func playAlertSound() {
do {
try self.audioSession.setActive(true) // 1
alertPlayer?.play() // 2
} catch let error {
print(error.localizedDescription)
}
}

Here’s what this code does:

  1. This line activates the audio session, i.e. duck the audio. Background music’s volume will now be lowered so that we can hear the sound that we play.
  2. This line will play audio from the audio player that we created in previous step.

To play the audio in our app, we just need to call playAlertSound().

However, when the audio has finished playing, the volume of background music does not return back to normal. We will fix this in next step.

Bringing Back Background Music’s Volume to Normal

To bring back background music’s volume to normal, we need to deactive the audioSession. But we also need to wait for the audio to complete playing before deactiving the session. For this, we will use delegate method of AVAudioPlayer called audioPlayerDidFinishPlaying.

We need to conform SoundService class to NSObject to be able to extend it to AVAudioPlayerDelegate.

class SoundService: NSObject
extension SoundService: AVAudioPlayerDelegate { }

We also need to set the delegate of alertPlayer to self, so add this line after we initialise the alert player:

alertPlayer?.delegate = self

Inside this extension, we will add the audioPlayerDidFinishPlaying method.

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.global().async {
do {
try self.audioSession.setActive(false, options: .notifyOthersOnDeactivation)
} catch let error {
print(error.localizedDescription)
}
}
}

Now, when the audio in our app has finished playing, the background music’s volume comes back to original volume automatically.

--

--