Monday, November 24, 2014

Use a slider to control audio speed in Swift.

Here is what we are going to create. 

Step 1


Lets get started by creating a Single View Application
 iOS >> Application >> Single View Application.












Lets change the Size class to make it simpler. Go to Main.storyboard to do this .
wCompact hAny 



















First things first locate ViewController.swift and add: import AVFoundation 
at the very top under: import UIKit

We are going to be using AVFoundation to work with audio. 


https://developer.apple.com/library/ios/documentation/avfoundation/reference/avaudioplayerclassreference/index.html

Step 2


Locate Main.storyboard click on it and find the black ViewController named "ViewController".

Drag 3  buttons name them Play, Pause, and Stop. (I think their intended purpose is clear) 

Drag a label and replace the default text Label with Speed: 1.00. 

Drag a slider onto the ViewController.

Finally organize it how you see fit and add Constraints.

Mine looks like this 
















Step 3


We are going to need Outlets for all these items. 

Make sure you are still on Main.storyboard. Click on the yellow square on top of the ViewController, this will ensure you selected ViewController. 
Switch to assistant Editor and Control click drag to start creating outlets for all the UI elements that are in the View Controller. 
Make sure that you enter the correct Type. The slider should be UISlider and the buttons should be UIButton.




Here is what I named my outlets 

    @IBOutlet weak var slider: UISlider!
    
    @IBOutlet weak var speedLabel: UILabel!
    
    @IBOutlet weak var playButton: UIButton!
    
    @IBOutlet weak var pauseButton: UIButton!
    
    @IBOutlet weak var stopButton: UIButton!
    
We are going to need these outlets to change and access some of the attributes these UI elements have. For example we might need to disable the pause button if there is no audio playing. It wouldn't make sense to have some buttons enabled at certain times. This will save the user the hassle of figuring out what buttons her or she could press. 

Step 4

Lets Create Actions for the buttons and for the slider. You create the actions the same way that you would create an outlet except that instead of Outlet choose Action.















Here are my Actions 


   @IBAction func sliderMoved(sender: UISlider) {
        
    }
    
    
    @IBAction func playAudio(sender: UIButton) {

    }
    
    
    
    @IBAction func pauseButton(sender: UIButton) {
        
    }
    
    
    @IBAction func stopButton(sender: UIButton) {

    }
    

When the slider is moved @IBAction func sliderMoved(sender: UISlider) will be triggered. 
The buttons work in a similar way except that the action is triggered when a user presses the button. 


Step 5

Lets load up some audio. 
Drag an audio clip into the Supporting Files Folder on the left Navigator. 















The file that I inserted into my project was named: movie_quote.mp3 . 
We need to create a variable of type AVAudioPlayer in order to play and control various aspects of our audio file. (like the rate or speed ) 

Insert var audioPlayer:AVAudioPlayer!  above or bellow your outlets. Its a good idea to group all the variable together in your code.  

The next part is going to be the most complicated part of the code. Don't worry I'll do my best to explain it all. 
Insert this into your ViewDidLoad Function. 


if  var filePath = NSBundle.mainBundle().pathForResource("movie_quote",   ofType: "mp3"){
            
            var filePathUrl = NSURL.fileURLWithPath(filePath)

            audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
            audioPlayer.enableRate = true
            
        }else {
            
            println("the file path is empty")
        }



In the first part of the code 

if  var filePath = NSBundle.mainBundle().pathForResource("movie_quote",   ofType: "mp3"){
            

We have an if statement its used for safety. Think of everything to the right of if as a big Bool value. 

if (bool) {
//some code 
}

if the variable filePath returns nil or nothing the if statement doesn't get executed. 

So what does this code actually return ?  NSBundle.mainBundle().pathForResource("movie_quote",   ofType: "mp3")

If you look at the documentation for pathForResources()

func pathForResource(name: String?, ofType ext: String?) -> String?



One can see that its return type is ->String? 
This means that it could return a String or it could return nil. 
This is why we have to rap it around an if statement to make make sure we don't make assumptions and use a nil variable as a String. ( a variable containing nothing )  

So what is NSBundle.mainBundle() for ? 

NSBundle is a class used to help us locate project resources. 

We then use 
class func mainBundle() -> NSBundle

This class function returns the directory where the application executable is located.

Then we use pathForResource() to located our audio file and acquire the file path as a string. It takes in two arguments the file name and the extension.
 
movie_qoute.mp3  is searched like so 
pathForResource("movie_quote",   ofType: "mp3")

Feel free to read the description for yourself. 

https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/index.html




So once we are in the if statement we execute a couple of lines of code. 

//here we convert our string file path into a NSURL 
           var filePathUrl = NSURL.fileURLWithPath(filePath)
//here we assign audioPlayer an audio file. 
            audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
//This will allow us to change the rate the audio plays  
            audioPlayer.enableRate = true
            

Finally we have an else statement 
        }else {
            
            println("the file path is empty")
        }

This is the code that is executed if the if's statement is false or nil. 
In other words if the file couldn't be found we print an error message. 




We have successfully loaded an audio file. Next Lets Play it! 

Step 6 

As I promised Step 5 was the hardest. 
lets play something already ! 


Locate the Action that is triggered when you press the Play button.
Mine is    

 @IBAction func playAudio(sender: UIButton) {

    }

Insert the code to that should playAudio. 

audioPlayer.play()



Go on give it a Play. 

Build your project and test it out. 

Step 7 

Lets set up the slider. 

Click on the slider and change the attributes like so. 
Like so 





















Minimum: 0.5
Maximum: 2 
Current: 1 

Why those numbers ? 

Well the rate property that audioPlayer has is in charge of adjusting the speed the audio is being played at. Its default values is 1.0 . Its fastest playback speed is 2.0. Its slowest speed is 0.5 . 


Step 8 

Lets allow changes to the playback speed with the Slider. 

In the Action connected to the slider insert this. 



    @IBAction func sliderMoved(sender: UISlider) {

//This changes the text in the label.        
    speedLabel.text = String(format: "Speed: %.2f", slider.value)

//This changes the rate or speed the audio should be played at.        
    audioPlayer.rate = slider.value
        
    }



String(format: "Speed: %.2f"slider.value)
This returns a formatted string. 
The float value that  slider.value returns is formatted by %.2f which only allows two decimal places. 
Read for further explanation: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Strings/Articles/formatSpecifiers.html


audioPlayer.rate = slider.value

The pice of code above is what will change the rate or speed of the audio. 
slider.value returns the current value of the slider and assigns it to audioPlayer.rate

Step 9 

The Stop and Pause buttons. 

From here on everything is pretty much self explanatory. 

audioPlayer.stop() basically pauses the audio that audioPlayer is playing. 

if you want it to completely stop and restart from the beginning you have to 
add this line of code  audioPlayer.currentTime = 0.0

You should add the appropriate code to your Actions that are connected to your Pause and Play buttons.

Ill show you the  final code in the last section. 

Before that I want you to think about when certain buttons should be disabled and create an algorithm for that.

You can use the following code to enable or disable a button.

exampleButton.enabled = false

Shouldn't the stop and pause button be disabled when there is nothing playing ? (and when audio is done playing  )



Step 10

AVAudioPlayerDelegate


We should disable the stop and play button when the audio is done playing. 
We do this with delegation. 
We need to know when audioPlayer finishes playing its audio. 
You do this by becoming its delegate. 

Add this in the bottom of ViewDidLoad : audioPlayer.delegate = self



If xCode gave you an error message its because ViewController has to say its is a delegate of AVAudioPlayer. 

On the very top of the class deceleration add AVAudioPlayerDelegate

Like so 
class ViewController: UIViewControllerAVAudioPlayerDelegate {

Next read the documentation to find out what delegate methods are available for use to implement. 

https://developer.apple.com/library/ios/documentation/avfoundation/reference/avaudioplayerdelegateprotocolreference/index.html



Final Code 

import UIKit
import AVFoundation

class ViewController: UIViewController, AVAudioPlayerDelegate {


    @IBOutlet weak var slider: UISlider!
    
    @IBOutlet weak var speedLabel: UILabel!
    
    @IBOutlet weak var playButton: UIButton!
    
    @IBOutlet weak var pauseButton: UIButton!
    
    @IBOutlet weak var stopButton: UIButton!
    
    var audioPlayer:AVAudioPlayer!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
       
  
        if  var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3"){
            
            var filePathUrl = NSURL.fileURLWithPath(filePath)
            audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
            audioPlayer.enableRate = true
            
        }else {
            
            println("the file path is empty")
        }
        
        pauseButton.enabled = false
        
        stopButton.enabled = false
        
        audioPlayer.delegate = self
        
    }


    @IBAction func sliderMoved(sender: UISlider) {
        
    speedLabel.text = String(format: "Speed: %.2f", slider.value)
        
        audioPlayer.rate = slider.value
        
    }
    
    
    @IBAction func playAudio(sender: UIButton) {
        
        audioPlayer.play()
        playButton.enabled = false
        
        if !pauseButton.enabled {
            pauseButton.enabled = true
        }
        
        if !stopButton.enabled {
            stopButton.enabled = true
        }
    }
    
    
    
    @IBAction func pauseButton(sender: UIButton) {
        
        if audioPlayer.playing == true{
        
            audioPlayer.stop()
        
            pauseButton.enabled = false
        
            playButton.enabled = true
        }
        
    }
    
    
    @IBAction func stopButton(sender: UIButton) {
   
            audioPlayer.stop()
            audioPlayer.currentTime = 0.0
            
            playButton.enabled = true
            
            pauseButton.enabled = false
        
            stopButton.enabled = false
        
    }
    
    
    
    // AVAudioPlayerDelegate Methods 
    
   func audioPlayerDidFinishPlaying(player: AVAudioPlayer!,
        successfully flag: Bool){
            
            playButton.enabled = true
            
            pauseButton.enabled = false
            
            stopButton.enabled = false
    }
    
}




I learned how to create this at Udacity.com.

Learn to program in Swift here: https://www.udacity.com/course/ud585