-
Notifications
You must be signed in to change notification settings - Fork 61
/
SpeechController.swift
executable file
路145 lines (124 loc) 路 4.72 KB
/
SpeechController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//
// SpeechController.swift
// InstantSearch
//
// Created by Robert Mogos on 23/05/2018.
//
import UIKit
import Speech
import AVFoundation
public typealias SpeechTextHandler = (String, Bool) -> Void
public typealias SpeechResultScreenHandler = (String) -> Void
public typealias SpeechErrorHandler = (Error?) -> Void
/// A controller object that manages the speech recognition to text
/// `SpeechController` is using the Speech framework, so it can only be used with iOS 10+
/// Simply initilise the controller with the desired `locale` or the device's `default` one
/// let speechController = SpeechController()
/// let speechController = SpeechController(locale: Locale(identifier: "fr_FR"))
/// Do not forget to add `NSSpeechRecognitionUsageDescription` and `NSMicrophoneUsageDescription` to your Info.plist
@available(iOS 10.0, *)
@objc public class SpeechController: NSObject, SFSpeechRecognizerDelegate {
private static let AUDIO_BUFFER_SIZE: UInt32 = 1024
private let speechRecognizer: SFSpeechRecognizer
private var speechRequest: SFSpeechAudioBufferRecognitionRequest?
private var speechTask: SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
/// Init with the device's default locale
override public convenience init() {
self.init(speechRecognizer: SFSpeechRecognizer())
}
/// Init with a locale
public convenience init(locale: Locale) {
self.init(speechRecognizer: SFSpeechRecognizer(locale: locale))
}
private init(speechRecognizer: SFSpeechRecognizer?) {
guard let speechRecognizer = speechRecognizer else {
fatalError("Locale not supported. Check SpeechController.supportedLocales() or SpeechController.localeSupported(locale: Locale)")
}
self.speechRecognizer = speechRecognizer
self.speechRecognizer.defaultTaskHint = .search
super.init()
}
/// Helper to get a list of supported locales
public class func supportedLocales() -> Set<Locale> {
return SFSpeechRecognizer.supportedLocales()
}
/// Helper to check if a locale is supported or not
public class func localeSupported(_ locale: Locale) -> Bool {
return SFSpeechRecognizer.supportedLocales().contains(locale)
}
/// Helper to request authorization for voice search
public func requestAuthorization(_ statusHandler: @escaping (Bool) -> Void) {
SFSpeechRecognizer.requestAuthorization { (authStatus) in
switch authStatus {
case .authorized:
statusHandler(true)
default:
statusHandler(false)
}
}
}
public func isRecording() -> Bool {
return audioEngine.isRunning
}
/// The method is going to give an infinite stream of speech-to-text until `stopRecording` is called or an error is encounter
public func startRecording(textHandler: @escaping SpeechTextHandler, errorHandler: @escaping SpeechErrorHandler) {
requestAuthorization {[unowned self] (authStatus) in
if authStatus {
if !self.audioEngine.isRunning {
self.record(textHandler: textHandler, errorHandler: errorHandler)
}
} else {
let errorMsg = "Speech recognizer needs to be authorized first"
errorHandler(NSError(domain:"com.algolia.speechcontroller", code:1, userInfo:[NSLocalizedDescriptionKey: errorMsg]))
}
}
}
private func record(textHandler: @escaping SpeechTextHandler, errorHandler: @escaping SpeechErrorHandler) {
do{
try AVAudioSession.sharedInstance().setCategory(.record, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
}
catch{
print(error.localizedDescription)
}
let node = audioEngine.inputNode
let recordingFormat = node.outputFormat(forBus: 0)
speechRequest = SFSpeechAudioBufferRecognitionRequest()
node.installTap(onBus: 0,
bufferSize: SpeechController.AUDIO_BUFFER_SIZE,
format: recordingFormat) { [weak self] (buffer, _) in
self?.speechRequest?.append(buffer)
}
audioEngine.prepare()
do {
try audioEngine.start()
} catch let err {
errorHandler(err)
return
}
speechTask = speechRecognizer.recognitionTask(with: speechRequest!) { (result, error) in
if let r = result {
let transcription = r.bestTranscription
let isFinal = r.isFinal
textHandler(transcription.formattedString, isFinal)
} else {
errorHandler(error)
}
}
}
/// Method which will stop the recording
public func stopRecording() {
if audioEngine.isRunning {
speechRequest?.endAudio()
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0)
speechTask?.cancel()
}
speechTask = nil
speechRequest = nil
}
deinit {
stopRecording()
}
}