Fading volume with AVPlayer in Swift
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! 😎