Fading volume with AVPlayer in Swift

Evandro Harrison Hoffmann
3 min readMay 21, 2020

--

Your experience with AVPlayer has been, from the beginning, a love/hate thing, hasn’t it? There are things that are so straightforward, but some that, well, better we change the subject, right? 😂

Maybe you’ve reached a point in your experience with AVPlayer that demanded you to fade in/out the volume of your player. Maybe to smoothly finish your playback, maybe to nicely start or transition playbacks, whatever the reason, you may have reached the point to try for yourself:

And you realise that there isn’t a function available to fade the audio. 🤦🏻‍♂️

Alright, before we panic, here are the options:

1. In case all you need is a single audio file, you can switch to use AVAudioPlayer instead as it has a default fade function.

player.setVolume(0.8, fadeDuration: 2.0)

2. The other option is, if your application needs to use AVPlayer because you also support video or if you are already used to it and the only one thing missing is fading, here’s a solution:

Start by opening an extension of AVPlayer with the following content:

import AVFoundationextension AVPlayer {  /// Fades player volume FROM any volume TO any volume
/// - Parameters:
/// - from: initial volume
/// - to: target volume
/// - duration: duration in seconds for the fade
/// - completion: callback indicating completion
/// - Returns: Timer?
func fadeVolume(from: Float, to: Float, duration: Float, completion: (() -> Void)? = nil) -> Timer? { // we will add the code here }}

Then, we need to make sure we start the fade animation with the given origin volume. Let’s replace the contents of the function above with the following:

volume = from

Having done that, we don’t want to fade if there’s nothing to fade to, right? So add the following code after the code above:

// There's nothing to fade if target volume is the same as initial
guard from != to else { return nil }

Then, we will set the interval, the range and the step we need to take to complete the fade with the given duration.

// 1. We define the time interval the interaction will loop into (fraction of a second)let interval: Float = 0.1// 2. Set the range the volume will movelet range = to-from// 3. Based on the range, the interval and duration, we calculate how big is the step we need to take in order to reach the target in the given durationlet step = (range*interval)/duration

Ok, so we need to know when we reached the target, for that, let’s add this internal function right after the previous code:

// 1. internal function whether the target has been reached or notfunc reachedTarget() -> Bool {  // 2. volume passed max/min

guard volume >= 0, volume <= 1 else {
volume = to return true } // 3. checks whether the volume is going forward or backward and compare current volume to target if to > from { return volume >= to } return volume <= to}

So we have all we need, so let’s finally get wrap the solution by adding this last piece of code after the code above:

// 1. We create a timer that will repeat itself with the given intervalreturn Timer.scheduledTimer(withTimeInterval: Double(interval), repeats: true, block: { [weak self] (timer) in  guard let self = self else { return }  DispatchQueue.main.async {    // 2. Check if we reached the target, otherwise we add the volume    if !reachedTarget() {      // note that if the step is negative, meaning that the to value is lower than the from value, the volume will be decreased instead      self.volume += step    } else {      timer.invalidate()      completion?()    }  }})

Wrapping it up, all you need to do now is to call the function in whatever way you like and see the magic happening:

private lazy var player: AVPlayer = .init()private var fadeTimer: Timer?private func setupPlayer() {  // Setup player  let url = URL(string: "https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3")!  let item = AVPlayerItem(url: url)  player.replaceCurrentItem(with: item)  player.volume = 0  player.play()  // Fade player volume from 0 to 1 in 5 seconds  fadeTimer = player.fadeVolume(from: 0, to: 1, duration: 5)}

You can find the full code source here.

Happy coding! 😎

--

--

Evandro Harrison Hoffmann
Evandro Harrison Hoffmann

Written by Evandro Harrison Hoffmann

Adventure seeker, dreamer, foodie and a magician 😉

No responses yet