How I built an audio player app – Part 1

Aug 2, 2017

Category

Author

Other posts in this series

Post 2 – How I Built an Audio Player App — Part 2

Post 3 – How I Built an Audio Player App — Part 3

Intro

I recently worked on a small music player app for Churches where they can play, control, and modulate different components of a song. Components being Drums, Vocals, Bass, Guitar etc.. Moreover, they can change Pitch as well as BPM (beats per minute – rate). This is way too many steps ahead of a simple audio player app where you only play one song, with no control on its components, no BPM change, no pitch change.

Creating a simple audio playback using AVAudioPlayer

If you just need to play a single audio file, mp3 etc., even with bitrate modulation, you can simply do that with AVAudioPlayer without digging deep into AVAudioEngine. AVAudioEngine is extremely powerful and essentially core if you want full control on audio in your app.

What you need?
I used AudioPlayer pod. It is a sugar syntax wrapper on top of AVAudioPlayer.

Playing an audio file:

import KDEAudioPlayer

class AudioManager() {
  var player: AudioPlayer()

  func loadAndPlay() {
    do {

      let directoryPath = FileManager.default.urls(for: 
        .documentDirectory, in: .userDomainMask).first

      let directoryContents = 
        try FileManager.default.contentsOfDirectory(
          at: directoryPath!, 
          includingPropertiesForKeys: nil, 
          options: [])

      let audioItem = AudioItem(mediumQualitySoundURL:     
        directoryContents[0])!
      player.play(item: audioItem)

    } catch {
      print("can't load directory contents")
    }
  }
}

Setting time labels and play button states

Player just isn’t only about having to listen the audio, we have to show users the progress, what’s the length of the song, change play/pause button’s state etc.. To do all these, KDEAudioPlayer has following delegate methods:

func audioPlayer(
  _ audioPlayer: AudioPlayer, 
  didUpdateProgressionTo time: TimeInterval, 
  percentageRead: Float) {

  currentTimeLabel.text = time.toHHMMSSString()  //return "03:42"
}

func audioPlayer(
  _ audioPlayer: AudioPlayer, 
  didFindDuration duration: TimeInterval, 
  for item: AudioItem) {

  durationLabel.text = duration.toHHMMSSString()
}

func audioPlayer(
  _ audioPlayer: AudioPlayer, 
  didChangeStateFrom from: AudioPlayerState, 
  to state: AudioPlayerState) {

  playButton.isSelected = (state == .playing)
}

A simple method for HH:MM:SS string can be created as:

extension TimeInterval {
  func toHHMMSSString() -> String {
    let hours = Int(self / 3600)
    let minutes = 
      String(format: "%02d", Int(self) % 3600 / 60)
    let seconds = String(format: "%02d", Int(self) % 60)

    if hours != 0 {
      return "\(hours):\(minutes):\(seconds)"
    } else {
      return "\(minutes):\(seconds)"
    }
  }
}

Change BPM (rate)

Now that we have an audio playing with time labels, play button are updating, you might want to change the BPM of the song. How you can do it?

Let’s say your song plays at 82 BPM, and you want to play it at 90 BPM, then we have to calculate how much of a percent change that is.

Percentage Change = (90 - 82) x 100 / 82
                  = 9.756 %     // (rounding off)

Since the rate change is 9.756%, we have to change the rate at which the song is playing by the same percentage. It can simply be done by rate attribute in player:

func setRate(_ value: Float) {
  player.rate = value
}

// when changing rate
setRate(1.09756)

NOTE: we are passing value as "1.09756" because we are converting 
percentage into actual value scaled from 0 to 1.

Wrapping: This concludes the first part of this article series. We have a single audio file playing right now. Next up is modulating pitch.

LEAVE A COMMENT