ViewController.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. * Copyright 2020, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import AVFoundation
  17. import GRPC
  18. import SnapKit
  19. import UIKit
  20. final class ViewController: UIViewController {
  21. private lazy var recordButton: UIButton = {
  22. var button = UIButton()
  23. button.setTitle("Record", for: .normal)
  24. button.setImage(UIImage(systemName: "mic"), for: .normal)
  25. button.backgroundColor = .darkGray
  26. button.layer.cornerRadius = 15
  27. button.clipsToBounds = true
  28. button.addTarget(self, action: #selector(recordTapped), for: .touchUpInside)
  29. return button
  30. }()
  31. private lazy var textView: UITextView = {
  32. var textView = UITextView()
  33. textView.isEditable = false
  34. textView.isSelectable = false
  35. textView.textColor = .white
  36. textView.textAlignment = .left
  37. textView.font = UIFont.systemFont(ofSize: 30)
  38. return textView
  39. }()
  40. private var isRecording: Bool = false {
  41. didSet {
  42. if self.isRecording {
  43. self.startRecording()
  44. } else {
  45. self.stopRecording()
  46. }
  47. }
  48. }
  49. private var audioData: Data = Data()
  50. private let speechService: SpeechService
  51. private let audioStreamManager: AudioStreamManager
  52. init(speechService: SpeechService,
  53. audioStreamManager: AudioStreamManager) {
  54. self.speechService = speechService
  55. self.audioStreamManager = audioStreamManager
  56. super.init(nibName: nil, bundle: nil)
  57. }
  58. convenience init() {
  59. self.init(
  60. speechService: SpeechService(),
  61. audioStreamManager: AudioStreamManager.shared
  62. )
  63. }
  64. required init?(coder: NSCoder) {
  65. return nil
  66. }
  67. override func viewDidLoad() {
  68. super.viewDidLoad()
  69. view.backgroundColor = .black
  70. title = "gRPC Speech To Text"
  71. self.audioStreamManager.delegate = self
  72. let recordingSession = AVAudioSession.sharedInstance()
  73. recordingSession.requestRecordPermission { [weak self] allowed in
  74. DispatchQueue.main.async {
  75. if allowed {
  76. do {
  77. try self?.audioStreamManager.configure()
  78. self?.setupRecordingLayout()
  79. } catch {
  80. self?.setupConfigurationFailedLayout()
  81. }
  82. } else {
  83. self?.setupErrorLayout()
  84. }
  85. }
  86. }
  87. }
  88. func setupRecordingLayout() {
  89. view.addSubview(self.textView)
  90. view.addSubview(self.recordButton)
  91. self.textView.snp.makeConstraints { make in
  92. make.top.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
  93. make.left.right.equalToSuperview()
  94. make.bottom.equalTo(recordButton.snp.top)
  95. }
  96. self.recordButton.snp.makeConstraints { make in
  97. make.height.equalTo(50)
  98. make.left.equalTo(40)
  99. make.right.equalTo(-40)
  100. make.bottom.equalToSuperview().inset(100)
  101. make.centerX.equalToSuperview()
  102. }
  103. }
  104. func setupErrorLayout() {
  105. self.textView.text = "Microphone Permissions are required in order to use this App."
  106. self.recordButton.isEnabled = false
  107. }
  108. func setupConfigurationFailedLayout() {
  109. self.textView.text = "An error occured while configuring your device for audio streaming."
  110. self.recordButton.isEnabled = false
  111. }
  112. @objc
  113. func recordTapped() {
  114. self.isRecording.toggle()
  115. }
  116. func startRecording() {
  117. self.audioData = Data()
  118. self.audioStreamManager.start()
  119. UIView.animate(withDuration: 0.02) { [weak self] in
  120. self?.recordButton.backgroundColor = .red
  121. }
  122. }
  123. func stopRecording() {
  124. self.audioStreamManager.stop()
  125. self.speechService.stopStreaming()
  126. UIView.animate(withDuration: 0.02) { [weak self] in
  127. self?.recordButton.backgroundColor = .darkGray
  128. }
  129. }
  130. }
  131. extension ViewController: StreamDelegate {
  132. func processAudio(_ data: Data) {
  133. self.audioData.append(data)
  134. // 100 ms chunk size
  135. let chunkSize: Int = Int(0.1 * Constants.sampleRate * 2)
  136. // When the audio data gets big enough
  137. if self.audioData.count > chunkSize {
  138. // Send to server
  139. self.speechService.stream(self.audioData) { [weak self] response in
  140. guard let self = self else { return }
  141. DispatchQueue.main.async {
  142. UIView.transition(
  143. with: self.textView,
  144. duration: 0.25,
  145. options: .transitionCrossDissolve,
  146. animations: {
  147. guard
  148. let results = response.results.first,
  149. let text = results.alternatives.first?.transcript else { return }
  150. if self.textView.text != text {
  151. self.textView.text = text
  152. }
  153. },
  154. completion: nil
  155. )
  156. }
  157. }
  158. }
  159. }
  160. }