How we Skip Silences in podcasts with AVAudioPlayer

A popular feature on podcast player apps is the ability to skip over silences during playback. By removing dead air, one can shave seconds and often minutes off of the total playback time of an episode. If you are regular listener of podcasts, that time really adds up.

For the past few weeks I’ve been working to bring this functionality to Breaker in a feature called Skip Silences. Breaker is an awesome new app for listening to podcasts. You can read more about what makes it unique in this post.

We’d like to share the details of how we implemented Skip Silences. Our first version currently works on downloaded episodes only. We are planning to bring it to streaming episodes in the future, and at the end of this post I share some thoughts around how we might do it. We are always looking for ways to improve, so if you have any feedback on our code or on the user experience we would love to hear from you!

Skipping Silences with AVAudioPlayer

The easiest way to get a decibel reading from a downloaded audio file, is with AVAudioPlayer. AVAudioPlayer only works with downloaded files, but it comes with two very useful methods: peakPower(forChannel:) and averagePower(forChannel:). We use the latter.

When setting up your player, make sure metering is enabled (by default it is not). You also need to enable rate adjustment, which is how we are going to “skip” over the silences later.

let player = try? AVAudioPlayer(contentsOf: url)
player?.isMeteringEnabled = true
player?.enableRate = true

Once you set up your player, you can obtain a power reading at a point in time of the playback:

player?.updateMeters()
let averagePower = player?.averagePower(forChannel: 0)

The result will be a Float between 0 (max power) and -160 (min power). To get the current power reading you must call updateMeters() before calling averagePower(forChannel:).

Next, we set up a repeating Timer so that we can sample our power at an appropriate rate during playback:

playerTimeObserver = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(findSilences), userInfo: nil, repeats: true)

During playback, the timer calls findSilences() every 1/10th of a second:

let decibelThreshold = Float(-35)
let defaultPlaybackRate = 1
let sampleRate = 0.1
let skippedSeconds = 0.0
func findSilences() {
guard player?.isPlaying == true else { return }
player?.updateMeters()
    if let averagePower = player?.averagePower(forChannel: 0),
averagePower < decibelThreshold {
player?.rate = 3
skippedSeconds += sampleRate - (sampleRate / 3)
} else {
player?.rate = defaultPlaybackRate
}
}

First, we check to make sure the player is playing. Then if the average power is less than our decibel threshold, we increase the playback rate to 3x, otherwise we set the playback rate to the default rate, which in this case is 1x. This speeds up playback, skipping over silences without actually editing the audio file.

We set our decibel threshold for silence at -35. During our testing we found that readings less than -35 were not audible.

Finally, to keep track of how much time has been saved, we store it in a class property (skippedSeconds), which we increment each time we increase the playback rate.

Skip Silences in Breaker

The implementation we settled on in Breaker is slightly more complex than the example above. We have different rate adjustments for different decibel ranges, which we found smoothed out the transition between quiet moments.

We also decided to increase our sample rate to 0.05 (1/20th of a second) for more accurate readings. This worked well for the majority of podcasts that we tested with, but we are still tweaking these numbers for the best experience.

To turn on this feature in Breaker, simply tap on the rate icon from the fullscreen player view. This brings up the rate view (shown below) which allows you to turn on Skip Silences, as well as adjust the playback rate. Skip Silences will work with any of the adjusted rates from ½x to 3x.

Finally, we show on your profile how much total time you’ve saved using Skip Silences. I’ve already saved over 30 minutes!

Bringing Skip Silences to streaming episodes

AVAudioPlayer only works with downloaded files, so a different approach is necessary for streaming audio. While attending WWDC this year, we made it priority to talk to Apple engineers about how to remove silent gaps from streaming episodes.

Their suggestion was to try AVAssetReader to sample the files, extract information from each sample to obtain the decibel level, and then recreate the asset by removing the durations of time that met our threshold for silences.

I found some examples online that used AVAssetReader results to draw the waveform of an AVAsset. Since a wave form represents the power of the audio at different points in time, I thought this approach would make sense to convert to decibel level. I recorded the timestamp and duration for each sample reading. If the average decibel level was lower than the threshold, I would recreate the asset using AVMutableComposition, removing the durations that were too quiet. This would all happen while the episode was streaming with AVPlayer.

Our initial results were a bit choppy and inconsistent. Ultimately we wanted a better experience for our users, so we decided to ship the feature first using AVAudioPlayer and downloaded files. I’m not sure if the issue is in converting the byte size to decibel, getting an accurate timestamp and duration, or recreating the file — but I’m looking forward to digging into this again. If anyone out there has experience building a solution like this, I’d love to compare notes!

Skip Silences demo app

While working on this feature, I made a quick demo app to test different audio files, sampling rates, and decibel levels. The app allows you to listen to three different clips, comparing the original and the Skip Silences versions while adjusting the inputs.

I’ve made the demo app publicly available on GitHub if you want to see the code and tweak the inputs even further.

The current version is cleaned up example using AVAudioPlayer, but if you look through the git history you can also see my (messy) attempts at sampling and recreating the streaming episodes with AVPlayer.

We love user feedback!

Try out the new Skip Silences feature by downloading the latest version of Breaker today! We love hearing feedback from our users and you can reach out anytime feedback@breaker.audio.