ViewController.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 GRPC
  17. import UIKit
  18. import SnapKit
  19. import AVFoundation
  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 isRecording {
  43. startRecording()
  44. } else {
  45. 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(speechService: SpeechService(),
  60. audioStreamManager: AudioStreamManager.shared)
  61. }
  62. required init?(coder: NSCoder) {
  63. return nil
  64. }
  65. override func viewDidLoad() {
  66. super.viewDidLoad()
  67. view.backgroundColor = .black
  68. title = "gRPC Speech To Text"
  69. audioStreamManager.delegate = self
  70. let recordingSession = AVAudioSession.sharedInstance()
  71. recordingSession.requestRecordPermission { [weak self] allowed in
  72. DispatchQueue.main.async {
  73. if allowed {
  74. do {
  75. try self?.audioStreamManager.configure()
  76. self?.setupRecordingLayout()
  77. } catch {
  78. self?.setupConfigurationFailedLayout()
  79. }
  80. } else {
  81. self?.setupErrorLayout()
  82. }
  83. }
  84. }
  85. }
  86. func setupRecordingLayout() {
  87. view.addSubview(textView)
  88. view.addSubview(recordButton)
  89. textView.snp.makeConstraints { make in
  90. make.top.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
  91. make.left.right.equalToSuperview()
  92. make.bottom.equalTo(recordButton.snp.top)
  93. }
  94. recordButton.snp.makeConstraints { make in
  95. make.height.equalTo(50)
  96. make.left.equalTo(40)
  97. make.right.equalTo(-40)
  98. make.bottom.equalToSuperview().inset(100)
  99. make.centerX.equalToSuperview()
  100. }
  101. }
  102. func setupErrorLayout() {
  103. textView.text = "Microphone Permissions are required in order to use this App."
  104. recordButton.isEnabled = false
  105. }
  106. func setupConfigurationFailedLayout() {
  107. textView.text = "An error occured while configuring your device for audio streaming."
  108. recordButton.isEnabled = false
  109. }
  110. @objc
  111. func recordTapped() {
  112. isRecording.toggle()
  113. }
  114. func startRecording() {
  115. audioData = Data()
  116. audioStreamManager.start()
  117. UIView.animate(withDuration: 0.02) { [weak self] in
  118. self?.recordButton.backgroundColor = .red
  119. }
  120. }
  121. func stopRecording() {
  122. audioStreamManager.stop()
  123. speechService.stopStreaming()
  124. UIView.animate(withDuration: 0.02) { [weak self] in
  125. self?.recordButton.backgroundColor = .darkGray
  126. }
  127. }
  128. }
  129. extension ViewController: StreamDelegate {
  130. func processAudio(_ data: Data) {
  131. audioData.append(data)
  132. // 100 ms chunk size
  133. let chunkSize: Int = Int(0.1 * Constants.sampleRate * 2)
  134. // When the audio data gets big enough
  135. if audioData.count > chunkSize {
  136. // Send to server
  137. speechService.stream(audioData) { [weak self] response in
  138. guard let self = self else { return }
  139. DispatchQueue.main.async {
  140. UIView.transition(
  141. with: self.textView,
  142. duration: 0.25,
  143. options: .transitionCrossDissolve,
  144. animations: {
  145. guard
  146. let results = response.results.first,
  147. let text = results.alternatives.first?.transcript else { return }
  148. if self.textView.text != text {
  149. self.textView.text = text
  150. }
  151. },
  152. completion: nil
  153. )
  154. }
  155. }
  156. }
  157. }
  158. }