I am working on application that records audio from the user, and displays all earlier recordings made from this application on another screen in a table view. On clicking a particular row, the recording must be played. How do I achieve this and are there any resources to help me out with the same?
My code currently saves the recording and plays it on the same screen. However, new recordings overwrite earlier ones and only one recording is saved into the file manager. I have added the "Privacy - Microphone usage required" to the plist. The audio is successfully recorded and played.
import UIKit
import AVFoundation
import MobileCoreServices
class ViewController: UIViewController, AVAudioRecorderDelegate, AVAudioPlayerDelegate{
@IBOutlet var recordingTimeLabel: UILabel!
@IBOutlet var record_btn_ref: UIButton!
@IBOutlet var play_btn_ref: UIButton!
//Variables:
var audioRecorder: AVAudioRecorder!
var audioPlayer : AVAudioPlayer!
var meterTimer:Timer!
var isAudioRecordingGranted: Bool!
var isRecording = false
var isPlaying = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Check recording permission:
check_record_permission()
//Add right bar button:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Documents", style: .plain, target: self, action: #selector(OpenDoc))
}
//Button action to start recording:
@IBAction func start_recording(_ sender: UIButton)
{
//If already recording:
if(isRecording)
{
//Stop recording:
finishAudioRecording(success: true)
//Set the title back to "Record":
record_btn_ref.setTitle("Record", for: .normal)
//Enable the play button:
play_btn_ref.isEnabled = true
//Set the value of the variable "isRecording" to false
isRecording = false
}
//If audio was not being recorded:
else
{
//Setup the recorder:
setup_recorder()
//Start recording:
audioRecorder.record()
//Update label every 1 sec:
meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
//Set the title of the label to "Stop":
record_btn_ref.setTitle("Stop", for: .normal)
//Enable the play button:
play_btn_ref.isEnabled = false
//Set "isRecording" to true:
isRecording = true
}
}
//Button action for play/pause:
@IBAction func play_recording(_ sender: Any)
{
//If audio is already being played (i.e. it should pause on being clicked again):
if(isPlaying)
{
//Stop audio player
audioPlayer.stop()
//Enable record button:
record_btn_ref.isEnabled = true
//Set the title to "Play"
play_btn_ref.setTitle("Play", for: .normal)
//Set value of "isPlaying" to false:
isPlaying = false
}
//It is not playing (i.e. it should play when button is clicked)
else
{
//If file path exists:
if FileManager.default.fileExists(atPath: getFileUrl().path)
{
//Disable the record button:
record_btn_ref.isEnabled = false
//Set the title of the button to "Pause":
play_btn_ref.setTitle("Pause", for: .normal)
//Prepare to play:
prepare_play()
//Implement play method of audioPlayer:
audioPlayer.play()
//Set variable "isPlaying" to true:
isPlaying = true
}
//If file path doesn't exist:
else
{
display_alert(msg_title: "Error", msg_desc: "Audio file is missing.", action_title: "OK")
}
}
}
//Function that checks permission to record:
func check_record_permission()
{
//Switch record permission instances:
switch AVAudioSession.sharedInstance().recordPermission {
//Case granted:
case AVAudioSessionRecordPermission.granted:
isAudioRecordingGranted = true
break
//Case denied:
case AVAudioSessionRecordPermission.denied:
isAudioRecordingGranted = false
break
//Case not determined, in which case ask for permission:
case AVAudioSessionRecordPermission.undetermined:
AVAudioSession.sharedInstance().requestRecordPermission({ (allowed) in
if allowed {
self.isAudioRecordingGranted = true
} else {
self.isAudioRecordingGranted = false
}
})
break
//Default case:
default:
break
}
}
//Function that gets the directory path:
func getDocumentsDirectory() -> URL
{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
//Function that gets the URL file path:
func getFileUrl() -> URL
{
let filename = "myRecording.m4a"
let filePath = getDocumentsDirectory().appendingPathComponent(filename)
return filePath
}
//Function that sets up the recorder:
func setup_recorder()
{
if isAudioRecordingGranted
{
let session = AVAudioSession.sharedInstance()
do
{
try session.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
try session.setActive(true)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
]
audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
}
catch let error {
display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
}
}
else
{
display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
}
}
//Objective C function to update text of the timer label:
@objc func updateAudioMeter(timer: Timer)
{
if audioRecorder.isRecording
{
let hr = Int((audioRecorder.currentTime / 60) / 60)
let min = Int(audioRecorder.currentTime / 60)
let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
recordingTimeLabel.text = totalTimeString
audioRecorder.updateMeters()
}
}
//Function for finish audio recording:
func finishAudioRecording(success: Bool)
{
//If recording was successful:
if success
{
audioRecorder.stop()
audioRecorder = nil
meterTimer.invalidate()
print("recorded successfully.")
}
//If recording was not successful:
else
{
display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK")
}
}
//Prepare to play:
func prepare_play()
{
do
{
audioPlayer = try AVAudioPlayer(contentsOf: getFileUrl())
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
}
catch{
print("Error")
}
}
//Function for audio record did finish recording:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)
{
if !flag
{
finishAudioRecording(success: false)
}
play_btn_ref.isEnabled = true
}
//If recorded audio was played:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
record_btn_ref.isEnabled = true
play_btn_ref.setTitle("Play", for: .normal)
}
//Function to display alerts:
func display_alert(msg_title : String , msg_desc : String ,action_title : String)
{
let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: action_title, style: .default)
{
(result : UIAlertAction) -> Void in
_ = self.navigationController?.popViewController(animated: true)
})
present(ac, animated: true)
}
@objc func OpenDoc()
{
let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeMPEG4Audio as String], in: .import)
documentPicker.delegate = self as? UIDocumentPickerDelegate
documentPicker.allowsMultipleSelection = false
present(documentPicker, animated: true, completion: nil)
}
}
After going through a few articles and the answer here: https://stackoverflow.com/a/57629598/11830020, I have figured out the answer and it is as below:
The storyboard contains two buttons,(one for record/stop and the other for play/pause) and one label to display the length of the recorded sound.
import UIKit
import AVFoundation
import MobileCoreServices
class ViewController: UIViewController,AVAudioRecorderDelegate,AVAudioPlayerDelegate {
//Variables:
var audioRecorder: AVAudioRecorder!
var player: AVAudioPlayer!
var meterTimer:Timer!
var isAudioRecordingGranted: Bool!
var isRecording = false
var isPlaying = false
var totalTimeString = ""
//IBOutlets:
@IBOutlet var recordingTimeLabel: UILabel!
@IBOutlet var record_btn_ref: UIButton!
@IBOutlet weak var playBtn: UIButton!
//View Did Load:
override func viewDidLoad() {
super.viewDidLoad()
//Check for recording permission:
check_record_permission()
}
//Button action to start recording:
@IBAction func start_recording(_ sender: UIButton)
{
//If already recording:
if(isRecording)
{
//Stop recording:
finishAudioRecording(success: true)
//Set the title back to "Record":
record_btn_ref.setTitle("Record", for: .normal)
//Enable the play button:
playBtn.isEnabled = true
//Set the value of the variable "isRecording" to false
isRecording = false
}
//If audio was not being recorded:
else
{
//Setup the recorder:
setup_recorder()
//Start recording:
audioRecorder.record()
//Update label every 1 sec:
meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
//Set the title of the label to "Stop":
record_btn_ref.setTitle("Stop", for: .normal)
//Disable play:
playBtn.isEnabled = false
//Set "isRecording" to true:
isRecording = true
}
}
//Play/pause button action
@IBAction func playBtnAction(_ sender: Any) {
//playSound()
//If audio is already being played (i.e. it should pause on being clicked again):
if(isPlaying)
{
//Stop audio player
player.stop()
//Enable record button:
record_btn_ref.isEnabled = true
//Set the title to "Play"
playBtn.setTitle("Play", for: .normal)
//Set value of "isPlaying" to false:
isPlaying = false
}
//It is not playing (i.e. it should play when button is clicked)
else
{
let filename = "myRecording\(totalTimeString).m4a"
let url = getDocumentsDirectory().appendingPathComponent(filename)
//If file path exists:
if FileManager.default.fileExists(atPath: url.path)
{
//Disable the record button:
record_btn_ref.isEnabled = false
//Set the title of the button to "Pause":
playBtn.setTitle("Pause", for: .normal)
//Prepare to play:
prepare_play()
//Implement play method of audioPlayer:
player.play()
//Set variable "isPlaying" to true:
isPlaying = true
}
//If file path doesn't exist:
else
{
display_alert(msg_title: "Error", msg_desc: "Audio file is missing.", action_title: "OK")
}
}
}
//Recording permissions:
//Function that checks for permission to record:
func check_record_permission()
{
//Switch record permission instances:
switch AVAudioSession.sharedInstance().recordPermission {
//Case granted:
case AVAudioSessionRecordPermission.granted:
isAudioRecordingGranted = true
break
//Case denied:
case AVAudioSessionRecordPermission.denied:
isAudioRecordingGranted = false
break
//Case not determined, in which case ask for permission:
case AVAudioSessionRecordPermission.undetermined:
AVAudioSession.sharedInstance().requestRecordPermission({ (allowed) in
if allowed {
self.isAudioRecordingGranted = true
} else {
self.isAudioRecordingGranted = false
}
})
break
//Default case:
default:
break
}
}
//Function that gets the directory path:
func getDocumentsDirectory() -> URL
{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
//Function that gets the URL file path:
func getFileUrl() -> URL
{
let date = Date()
let calendar = Calendar.current
let hr = calendar.component(.hour, from: date)
let min = calendar.component(.minute, from: date)
let sec = calendar.component(.second, from: date)
totalTimeString = String(format: "%02d.%02d.%02d", hr, min, sec)
let filename = "myRecording\(totalTimeString).m4a"
let filePath = getDocumentsDirectory().appendingPathComponent(filename)
return filePath
}
//Audio recorder functions:
//Function that sets up the recorder:
func setup_recorder()
{
//If access to record:
if isAudioRecordingGranted
{
let session = AVAudioSession.sharedInstance()
do
{
try session.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
try session.setActive(true)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
]
audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
}
catch let error {
display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
}
}
//If permission not granted:
else
{
display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
}
}
//Function that defines what to do when audio recording is finished successfully/unsuccessfully:
func finishAudioRecording(success: Bool)
{
//If recording was successful:
if success
{
//Stop recording
audioRecorder.stop()
//Reset recorder
audioRecorder = nil
//Invalidate meter timer:
meterTimer.invalidate()
}
//If recording was not successful:
else
{
//Call function to display alert:
display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK")
}
}
//Function for audio record did finish recording:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)
{
if !flag
{
//Audio recording was not successful:
finishAudioRecording(success: false)
}
//Enable play button
playBtn.isEnabled = true
}
//Play/pause recorded audio:
//Prepare to play:
func prepare_play()
{
do
{
let filename = "myRecording\(totalTimeString).m4a"
let url = getDocumentsDirectory().appendingPathComponent(filename)
player = try AVAudioPlayer(contentsOf: url)
player.delegate = self
player.prepareToPlay()
}
catch{
print("Error")
}
}
//If recorded audio was played:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
//Enable record button:
record_btn_ref.isEnabled = true
//Set title of play button to Play:
playBtn.setTitle("Play", for: .normal)
}
//Alerts:
//Function to display alerts:
func display_alert(msg_title : String , msg_desc : String ,action_title : String)
{
let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: action_title, style: .default)
{
(result : UIAlertAction) -> Void in
_ = self.navigationController?.popViewController(animated: true)
})
present(ac, animated: true)
}
//Timer label:
//Objective C function to update text of the timer label:
@objc func updateAudioMeter(timer: Timer)
{
if audioRecorder.isRecording
{
let hr = Int((audioRecorder.currentTime / 60) / 60)
let min = Int(audioRecorder.currentTime / 60)
let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
recordingTimeLabel.text = totalTimeString
audioRecorder.updateMeters()
}
}
}