AudioRecorderController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import Foundation
  2. import SCSiriWaveformView
  3. import AVKit
  4. protocol AudioRecorderControllerDelegate: class {
  5. func didFinishAudioAtPath(path: String)
  6. }
  7. class AudioRecorderController: UIViewController, AVAudioRecorderDelegate {
  8. weak var delegate: AudioRecorderControllerDelegate?
  9. //Recording...
  10. var meterUpdateDisplayLink: CADisplayLink?
  11. var isRecordingPaused: Bool = false
  12. // maximumRecordDuration > 0 -> restrict max time period for one take
  13. var maximumRecordDuration = 0.0
  14. //Private variables
  15. var oldSessionCategory: AVAudioSession.Category?
  16. var wasIdleTimerDisabled: Bool = false
  17. var recordingFilePath: String = ""
  18. var audioRecorder: AVAudioRecorder?
  19. var normalTintColor: UIColor = UIColor.sendButtonBlue
  20. var highlightedTintColor = UIColor.red
  21. var isFirstUsage: Bool = true
  22. lazy var waveFormView: SCSiriWaveformView = {
  23. let view = SCSiriWaveformView()
  24. view.alpha = 1.0
  25. view.backgroundColor = .clear
  26. view.translatesAutoresizingMaskIntoConstraints = false
  27. view.primaryWaveLineWidth = 3.0
  28. view.secondaryWaveLineWidth = 1.0
  29. return view
  30. }()
  31. lazy var noRecordingPermissionView: UIImageView = {
  32. let view = UIImageView(image: UIImage(named: "microphone_access"))
  33. view.translatesAutoresizingMaskIntoConstraints = false
  34. view.alpha = 0.0
  35. view.contentMode = UIView.ContentMode.scaleAspectFit
  36. return view
  37. }()
  38. var navigationTitle: NSString?
  39. lazy var cancelButton: UIBarButtonItem = {
  40. let button = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel,
  41. target: self,
  42. action: #selector(cancelAction))
  43. return button
  44. }()
  45. lazy var doneButton: UIBarButtonItem = {
  46. let button = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
  47. target: self,
  48. action: #selector(doneAction))
  49. return button
  50. }()
  51. lazy var cancelRecordingButton: UIBarButtonItem = {
  52. let button = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel,
  53. target: self,
  54. action: #selector(cancelRecordingAction))
  55. button.tintColor = highlightedTintColor
  56. return button
  57. }()
  58. lazy var pauseButton: UIBarButtonItem = {
  59. let button = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.pause,
  60. target: self,
  61. action: #selector(pauseRecordingButtonAction))
  62. button.tintColor = highlightedTintColor
  63. return button
  64. }()
  65. lazy var startRecordingButton: UIBarButtonItem = {
  66. let button = UIBarButtonItem.init(image: UIImage(named: "audio_record"),
  67. style: UIBarButtonItem.Style.plain,
  68. target: self,
  69. action: #selector(recordingButtonAction))
  70. button.tintColor = normalTintColor
  71. return button
  72. }()
  73. lazy var continueRecordingButton: UIBarButtonItem = {
  74. let button = UIBarButtonItem.init(image: UIImage(named: "audio_record"),
  75. style: UIBarButtonItem.Style.plain,
  76. target: self,
  77. action: #selector(continueRecordingButtonAction))
  78. button.tintColor = highlightedTintColor
  79. return button
  80. }()
  81. lazy var flexItem = {
  82. return UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
  83. }()
  84. override func viewDidLoad() {
  85. super.viewDidLoad()
  86. self.view.backgroundColor = UIColor.themeColor(light: .white, dark: .black)
  87. self.navigationController?.isToolbarHidden = false
  88. self.navigationController?.toolbar.isTranslucent = true
  89. self.navigationController?.navigationBar.isTranslucent = true
  90. self.navigationItem.title = String.localized("voice_message")
  91. self.navigationItem.leftBarButtonItem = cancelButton
  92. self.navigationItem.rightBarButtonItem = doneButton
  93. waveFormView.frame = self.view.bounds
  94. self.view.addSubview(waveFormView)
  95. self.view.addSubview(noRecordingPermissionView)
  96. waveFormView.fill(view: view)
  97. noRecordingPermissionView.fill(view: view, paddingLeading: 100, paddingTrailing: 100, paddingTop: 200, paddingBottom: 200)
  98. self.navigationController?.toolbar.tintColor = normalTintColor
  99. self.navigationController?.navigationBar.tintColor = normalTintColor
  100. self.navigationController?.navigationBar.isTranslucent = true
  101. self.navigationController?.toolbar.isTranslucent = true
  102. //Define the recorder setting
  103. let recordSettings = [AVFormatIDKey: kAudioFormatMPEG4AAC,
  104. AVSampleRateKey: 44100.0,
  105. AVNumberOfChannelsKey: 1] as [String: Any]
  106. let globallyUniqueString = ProcessInfo.processInfo.globallyUniqueString
  107. recordingFilePath = NSTemporaryDirectory().appending(globallyUniqueString).appending(".m4a")
  108. _ = try? audioRecorder = AVAudioRecorder.init(url: URL(fileURLWithPath: recordingFilePath), settings: recordSettings)
  109. audioRecorder?.delegate = self
  110. audioRecorder?.isMeteringEnabled = true
  111. }
  112. override func viewWillAppear(_ animated: Bool) {
  113. super.viewWillAppear(animated)
  114. startUpdatingMeter()
  115. wasIdleTimerDisabled = UIApplication.shared.isIdleTimerDisabled
  116. NotificationCenter.default.addObserver(self,
  117. selector: #selector(didBecomeActiveNotification),
  118. name: UIApplication.didBecomeActiveNotification,
  119. object: nil)
  120. validateMicrophoneAccess()
  121. }
  122. override func viewWillDisappear(_ animated: Bool) {
  123. super.viewWillDisappear(animated)
  124. NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
  125. audioRecorder?.delegate = nil
  126. audioRecorder?.stop()
  127. audioRecorder = nil
  128. stopUpdatingMeter()
  129. UIApplication.shared.isIdleTimerDisabled = wasIdleTimerDisabled
  130. }
  131. func startUpdatingMeter() {
  132. meterUpdateDisplayLink?.invalidate()
  133. meterUpdateDisplayLink = CADisplayLink.init(target: self, selector: #selector(updateMeters))
  134. meterUpdateDisplayLink?.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
  135. }
  136. func stopUpdatingMeter() {
  137. meterUpdateDisplayLink?.invalidate()
  138. meterUpdateDisplayLink = nil
  139. }
  140. @objc func updateMeters() {
  141. if let audioRecorder = audioRecorder {
  142. if audioRecorder.isRecording || isRecordingPaused {
  143. audioRecorder.updateMeters()
  144. let normalizedValue: Float = pow(10, audioRecorder.averagePower(forChannel: 0) / 20)
  145. waveFormView.waveColor = highlightedTintColor
  146. waveFormView.update(withLevel: CGFloat(normalizedValue))
  147. self.navigationItem.title = String.timeStringForInterval(audioRecorder.currentTime)
  148. } else {
  149. waveFormView.waveColor = normalTintColor
  150. waveFormView.update(withLevel: 0)
  151. }
  152. }
  153. }
  154. @objc func recordingButtonAction() {
  155. logger.debug("start recording")
  156. self.setToolbarItems([flexItem, pauseButton, flexItem], animated: true)
  157. self.navigationItem.setLeftBarButton(cancelRecordingButton, animated: true)
  158. if FileManager.default.fileExists(atPath: recordingFilePath) {
  159. _ = try? FileManager.default.removeItem(atPath: recordingFilePath)
  160. }
  161. let session = AVAudioSession.sharedInstance()
  162. oldSessionCategory = session.category
  163. _ = try? session.setCategory(AVAudioSession.Category.record)
  164. UIApplication.shared.isIdleTimerDisabled = true
  165. audioRecorder?.prepareToRecord()
  166. isRecordingPaused = false
  167. if maximumRecordDuration <= 0 {
  168. audioRecorder?.record()
  169. } else {
  170. audioRecorder?.record(forDuration: maximumRecordDuration)
  171. }
  172. }
  173. @objc func continueRecordingButtonAction() {
  174. logger.debug("continue recording")
  175. self.setToolbarItems([flexItem, pauseButton, flexItem], animated: true)
  176. isRecordingPaused = false
  177. audioRecorder?.record()
  178. }
  179. @objc func pauseRecordingButtonAction() {
  180. logger.debug("pause")
  181. isRecordingPaused = true
  182. audioRecorder?.pause()
  183. self.setToolbarItems([flexItem, continueRecordingButton, flexItem], animated: true)
  184. }
  185. @objc func cancelRecordingAction() {
  186. logger.debug("cancel recording")
  187. isRecordingPaused = false
  188. audioRecorder?.stop()
  189. _ = try? FileManager.default.removeItem(atPath: recordingFilePath)
  190. self.navigationItem.title = String.localized("voice_message")
  191. }
  192. @objc func cancelAction() {
  193. logger.debug("cancel Action")
  194. dismiss(animated: true, completion: nil)
  195. }
  196. @objc func doneAction() {
  197. logger.debug("done with Action")
  198. isRecordingPaused = false
  199. audioRecorder?.stop()
  200. if let delegate = self.delegate {
  201. delegate.didFinishAudioAtPath(path: recordingFilePath)
  202. }
  203. dismiss(animated: true, completion: nil)
  204. }
  205. @objc func didBecomeActiveNotification() {
  206. validateMicrophoneAccess()
  207. }
  208. func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
  209. if flag {
  210. self.setToolbarItems([flexItem, startRecordingButton, flexItem], animated: true)
  211. if let oldSessionCategory = oldSessionCategory {
  212. _ = try? AVAudioSession.sharedInstance().setCategory(oldSessionCategory)
  213. UIApplication.shared.isIdleTimerDisabled = wasIdleTimerDisabled
  214. }
  215. } else {
  216. try? FileManager.default.removeItem(at: URL(fileURLWithPath: recordingFilePath))
  217. }
  218. self.navigationItem.setLeftBarButton(cancelButton, animated: true)
  219. }
  220. func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
  221. logger.error("audio recording failed: \(error?.localizedDescription ?? "unknown")")
  222. }
  223. func validateMicrophoneAccess() {
  224. let audioSession = AVAudioSession.sharedInstance()
  225. audioSession.requestRecordPermission({(granted: Bool) -> Void in
  226. DispatchQueue.main.async { [weak self] in
  227. if let self = self {
  228. self.noRecordingPermissionView.alpha = granted ? 0.0 : 1.0
  229. self.waveFormView.alpha = granted ? 1.0 : 0.0
  230. self.doneButton.isEnabled = granted
  231. if self.isFirstUsage {
  232. if !granted {
  233. self.setToolbarItems([self.flexItem, self.startRecordingButton, self.flexItem], animated: true)
  234. self.startRecordingButton.isEnabled = false
  235. } else {
  236. self.pauseButton.isEnabled = granted
  237. self.recordingButtonAction()
  238. }
  239. self.isFirstUsage = false
  240. } else {
  241. self.startRecordingButton.isEnabled = granted
  242. }
  243. }
  244. }
  245. })
  246. }
  247. }