xcodeplaybackvoice-recordingspeakerswift4.2

Voice Recorder: speaker playback by default


I've created a voice recorder that plays back through the earpiece; however, I want to play back by speaker. I've added the following (Swift 4):

let audioSession = AVAudioSession.sharedInstance()

    do {
        try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
    } catch let error as NSError {
        print("Audio Session error: \(error.localizedDescription)")
    }

When I initialize the app, I am able to playback previous recordings by speaker; however, as soon as I record a new voice memo, the playback goes back to earpiece. I've looked up solutions but they all refer to setCategory which throws an error in Swift 4.

Does anyone know how to default recorded playbacks to speaker?

Here is my view controller in full:

import UIKit
import AVFoundation

class ViewController2: UIViewController, AVAudioRecorderDelegate, UITableViewDelegate, UITableViewDataSource {


// Setting up Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return numberOfRecords
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = String(indexPath.row + 1)
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let path = getDirectory().appendingPathComponent("\(indexPath.row + 1).m4a")

    do {
        audioPlayer = try AVAudioPlayer(contentsOf: path)
        audioPlayer.play()
    }

    catch {

    }
}


var audioPlayer : AVAudioPlayer!
var recordingSession : AVAudioSession!
var audioRecorder : AVAudioRecorder!

var numberOfRecords : Int = 0

@IBOutlet weak var buttonLabel: UIButton!
@IBOutlet weak var myTableView: UITableView!

@IBAction func record(_ sender: Any) {
    // Check if we have an active recorder
    if audioRecorder == nil {
        numberOfRecords += 1
        let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")

        let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                        AVSampleRateKey: 12000,
                        AVNumberOfChannelsKey: 1,
                        AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]

        // Start audio recording
        do {
            audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
            audioRecorder.delegate = self
            audioRecorder.record()

            //buttonLabel.setTitle("Stop", for: .normal)
        }

        catch {
            displayAlert(title: "Oops!", message: "Recording failed")
        }
    }
    else {
        // Stop audio recording
        audioRecorder.stop()
        audioRecorder = nil

        UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
        myTableView.reloadData()

        //buttonLabel.setTitle("Start", for: .normal)
    }

}

override func viewDidLoad() {
    super.viewDidLoad()

    // Setting up Recording session
    recordingSession = AVAudioSession.sharedInstance()

    if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
        numberOfRecords = number
    }

    AVAudioSession.sharedInstance().requestRecordPermission { (hasPermission) in
        if hasPermission {
            print ("Accepted")
        }
    }

    // Play speaker instead of earpiece
    let audioSession = AVAudioSession.sharedInstance()

    do {
        try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)

    } catch let error as NSError {
        print("Audio Session error: \(error.localizedDescription)")
    }


    // Gesture recognizers

    let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
    leftSwipe.direction = UISwipeGestureRecognizer.Direction.left
    self.view.addGestureRecognizer(leftSwipe)

    let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
    rightSwipe.direction = UISwipeGestureRecognizer.Direction.right
    self.view.addGestureRecognizer(rightSwipe)

    let upSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
    upSwipe.direction = UISwipeGestureRecognizer.Direction.up
    self.view.addGestureRecognizer(upSwipe)

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


// Function that gets path to directory
func getDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentDirectory = paths[0]
    return documentDirectory
}

// Function that displays an alert
func displayAlert(title: String, message: String) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "dismiss", style: .default, handler: nil))
    present(alert, animated: true, completion: nil)
}

Solution

  • The do catch statement needs to be within the if and not else and not in the viewDidLoad () section.

    // Setting up Table View
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return numberOfRecords
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = String(indexPath.row + 1)
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let path = getDirectory().appendingPathComponent("\(indexPath.row + 1).m4a")
    
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: path)
            audioPlayer.play()
        }
    
        catch {
    
        }
    }
    
    
    var audioPlayer : AVAudioPlayer!
    var recordingSession : AVAudioSession!
    var audioRecorder : AVAudioRecorder!
    
    var numberOfRecords : Int = 0
    
    @IBOutlet weak var buttonLabel: UIButton!
    @IBOutlet weak var myTableView: UITableView!
    
    @IBAction func record(_ sender: Any) {
        // Check if we have an active recorder
        if audioRecorder == nil {
            numberOfRecords += 1
            let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")
    
            let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                            AVSampleRateKey: 12000,
                            AVNumberOfChannelsKey: 1,
                            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
    
            // Start audio recording
            do {
                audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
                audioRecorder.delegate = self
                audioRecorder.record()
    
                //buttonLabel.setTitle("Stop", for: .normal)
            }
    
            catch {
                displayAlert(title: "Oops!", message: "Recording failed")
            }
    
            // Play speaker instead of earpiece
            let audioSession = AVAudioSession.sharedInstance()
    
            do {
                try audioSession.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
    
            } catch let error as NSError {
                print("Audio Session error: \(error.localizedDescription)")
            }
    
    
        }
        else {
            // Stop audio recording
            audioRecorder.stop()
            audioRecorder = nil
    
            UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
            myTableView.reloadData()
    
            //buttonLabel.setTitle("Start", for: .normal)
        }
    
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Setting up Recording session
        recordingSession = AVAudioSession.sharedInstance()
    
        if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
            numberOfRecords = number
        }
    
        AVAudioSession.sharedInstance().requestRecordPermission { (hasPermission) in
            if hasPermission {
                print ("Accepted")
            }
        }
    
    
    
        // Gesture recognizers
    
        let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
        leftSwipe.direction = UISwipeGestureRecognizerDirection.left
        self.view.addGestureRecognizer(leftSwipe)
    
        let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
        rightSwipe.direction = UISwipeGestureRecognizerDirection.right
        self.view.addGestureRecognizer(rightSwipe)
    
        let upSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
        upSwipe.direction = UISwipeGestureRecognizerDirection.up
        self.view.addGestureRecognizer(upSwipe)
    
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    // Function that gets path to directory
    func getDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let documentDirectory = paths[0]
        return documentDirectory
    }
    
    // Function that displays an alert
    func displayAlert(title: String, message: String) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "dismiss", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }