@capgo/capacitor-speech-recognition 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorSpeechRecognition.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +360 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/app/capgo/speechrecognition/Constants.java +17 -0
- package/android/src/main/java/app/capgo/speechrecognition/Receiver.java +49 -0
- package/android/src/main/java/app/capgo/speechrecognition/SpeechRecognitionPlugin.java +406 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +438 -0
- package/dist/esm/definitions.d.ts +130 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +11 -0
- package/dist/esm/web.js +25 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +39 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +42 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/SpeechRecognitionPlugin/SpeechRecognitionPlugin.swift +282 -0
- package/ios/Tests/SpeechRecognitionPluginTests/SpeechRecognitionPluginTests.swift +7 -0
- package/package.json +87 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const SpeechRecognition = core.registerPlugin('SpeechRecognition', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SpeechRecognitionWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class SpeechRecognitionWeb extends core.WebPlugin {
|
|
10
|
+
available() {
|
|
11
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
12
|
+
}
|
|
13
|
+
start(_options) {
|
|
14
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
15
|
+
}
|
|
16
|
+
stop() {
|
|
17
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
18
|
+
}
|
|
19
|
+
getSupportedLanguages() {
|
|
20
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
21
|
+
}
|
|
22
|
+
isListening() {
|
|
23
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
24
|
+
}
|
|
25
|
+
checkPermissions() {
|
|
26
|
+
throw this.unimplemented('Speech recognition permissions are not handled on the web.');
|
|
27
|
+
}
|
|
28
|
+
requestPermissions() {
|
|
29
|
+
throw this.unimplemented('Speech recognition permissions are not handled on the web.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
34
|
+
__proto__: null,
|
|
35
|
+
SpeechRecognitionWeb: SpeechRecognitionWeb
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
exports.SpeechRecognition = SpeechRecognition;
|
|
39
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SpeechRecognition = registerPlugin('SpeechRecognition', {\n web: () => import('./web').then((m) => new m.SpeechRecognitionWeb()),\n});\nexport * from './definitions';\nexport { SpeechRecognition };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SpeechRecognitionWeb extends WebPlugin {\n available() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n start(_options) {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n stop() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n getSupportedLanguages() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n isListening() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n checkPermissions() {\n throw this.unimplemented('Speech recognition permissions are not handled on the web.');\n }\n requestPermissions() {\n throw this.unimplemented('Speech recognition permissions are not handled on the web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;AAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;AACxE,CAAC;;ACFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;AACpD,IAAI,SAAS,GAAG;AAChB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;AACnF,IAAI;AACJ,IAAI,KAAK,CAAC,QAAQ,EAAE;AACpB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;AACnF,IAAI;AACJ,IAAI,IAAI,GAAG;AACX,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;AACnF,IAAI;AACJ,IAAI,qBAAqB,GAAG;AAC5B,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;AACnF,IAAI;AACJ,IAAI,WAAW,GAAG;AAClB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;AACnF,IAAI;AACJ,IAAI,gBAAgB,GAAG;AACvB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;AAC9F,IAAI;AACJ,IAAI,kBAAkB,GAAG;AACzB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;AAC9F,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var capacitorCapacitorSpeechRecognition = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const SpeechRecognition = core.registerPlugin('SpeechRecognition', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SpeechRecognitionWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
class SpeechRecognitionWeb extends core.WebPlugin {
|
|
9
|
+
available() {
|
|
10
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
11
|
+
}
|
|
12
|
+
start(_options) {
|
|
13
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
14
|
+
}
|
|
15
|
+
stop() {
|
|
16
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
17
|
+
}
|
|
18
|
+
getSupportedLanguages() {
|
|
19
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
20
|
+
}
|
|
21
|
+
isListening() {
|
|
22
|
+
throw this.unimplemented('Speech recognition is not available on the web.');
|
|
23
|
+
}
|
|
24
|
+
checkPermissions() {
|
|
25
|
+
throw this.unimplemented('Speech recognition permissions are not handled on the web.');
|
|
26
|
+
}
|
|
27
|
+
requestPermissions() {
|
|
28
|
+
throw this.unimplemented('Speech recognition permissions are not handled on the web.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
33
|
+
__proto__: null,
|
|
34
|
+
SpeechRecognitionWeb: SpeechRecognitionWeb
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
exports.SpeechRecognition = SpeechRecognition;
|
|
38
|
+
|
|
39
|
+
return exports;
|
|
40
|
+
|
|
41
|
+
})({}, capacitorExports);
|
|
42
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SpeechRecognition = registerPlugin('SpeechRecognition', {\n web: () => import('./web').then((m) => new m.SpeechRecognitionWeb()),\n});\nexport * from './definitions';\nexport { SpeechRecognition };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SpeechRecognitionWeb extends WebPlugin {\n available() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n start(_options) {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n stop() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n getSupportedLanguages() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n isListening() {\n throw this.unimplemented('Speech recognition is not available on the web.');\n }\n checkPermissions() {\n throw this.unimplemented('Speech recognition permissions are not handled on the web.');\n }\n requestPermissions() {\n throw this.unimplemented('Speech recognition permissions are not handled on the web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,iBAAiB,GAAGA,mBAAc,CAAC,mBAAmB,EAAE;IAC9D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,oBAAoB,EAAE,CAAC;IACxE,CAAC;;ICFM,MAAM,oBAAoB,SAASC,cAAS,CAAC;IACpD,IAAI,SAAS,GAAG;IAChB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;IACnF,IAAI;IACJ,IAAI,KAAK,CAAC,QAAQ,EAAE;IACpB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;IACnF,IAAI;IACJ,IAAI,IAAI,GAAG;IACX,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;IACnF,IAAI;IACJ,IAAI,qBAAqB,GAAG;IAC5B,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;IACnF,IAAI;IACJ,IAAI,WAAW,GAAG;IAClB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,iDAAiD,CAAC;IACnF,IAAI;IACJ,IAAI,gBAAgB,GAAG;IACvB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ,IAAI,kBAAkB,GAAG;IACzB,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Foundation
|
|
4
|
+
import Speech
|
|
5
|
+
|
|
6
|
+
private enum PermissionState: String {
|
|
7
|
+
case granted
|
|
8
|
+
case denied
|
|
9
|
+
case prompt
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@objc(SpeechRecognitionPlugin)
|
|
13
|
+
public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
14
|
+
public let identifier = "SpeechRecognitionPlugin"
|
|
15
|
+
public let jsName = "SpeechRecognition"
|
|
16
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
17
|
+
CAPPluginMethod(name: "available", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "getSupportedLanguages", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "isListening", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise)
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
private let audioEngine = AVAudioEngine()
|
|
27
|
+
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
|
|
28
|
+
private var recognitionTask: SFSpeechRecognitionTask?
|
|
29
|
+
private var speechRecognizer: SFSpeechRecognizer?
|
|
30
|
+
private var activeCall: CAPPluginCall?
|
|
31
|
+
private var currentOptions: RecognitionOptions?
|
|
32
|
+
private var hasInstalledTap = false
|
|
33
|
+
|
|
34
|
+
private let maxDefaultResults = 5
|
|
35
|
+
|
|
36
|
+
@objc func available(_ call: CAPPluginCall) {
|
|
37
|
+
let locale = Locale(identifier: call.getString("language") ?? Locale.current.identifier)
|
|
38
|
+
let recognizer = SFSpeechRecognizer(locale: locale)
|
|
39
|
+
call.resolve(["available": recognizer?.isAvailable ?? false])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@objc func start(_ call: CAPPluginCall) {
|
|
43
|
+
if self.audioEngine.isRunning || recognitionTask != nil {
|
|
44
|
+
call.reject("Speech recognition is already running.")
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
guard isSpeechPermissionGranted else {
|
|
49
|
+
call.reject("Missing speech recognition permission.")
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let options = RecognitionOptions(
|
|
54
|
+
language: call.getString("language") ?? Locale.current.identifier,
|
|
55
|
+
maxResults: call.getInt("maxResults") ?? maxDefaultResults,
|
|
56
|
+
partialResults: call.getBool("partialResults") ?? false,
|
|
57
|
+
addPunctuation: call.getBool("addPunctuation") ?? false
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.activeCall = call
|
|
61
|
+
self.currentOptions = options
|
|
62
|
+
|
|
63
|
+
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
|
64
|
+
guard granted else {
|
|
65
|
+
DispatchQueue.main.async {
|
|
66
|
+
call.reject("User denied microphone access.")
|
|
67
|
+
self.cleanupRecognition(notifyStop: false)
|
|
68
|
+
}
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
DispatchQueue.main.async {
|
|
73
|
+
self.beginRecognition(call: call, options: options)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@objc func stop(_ call: CAPPluginCall) {
|
|
79
|
+
cleanupRecognition(notifyStop: true)
|
|
80
|
+
call.resolve()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@objc func isListening(_ call: CAPPluginCall) {
|
|
84
|
+
call.resolve(["listening": audioEngine.isRunning])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@objc func getSupportedLanguages(_ call: CAPPluginCall) {
|
|
88
|
+
let identifiers = SFSpeechRecognizer
|
|
89
|
+
.supportedLocales()
|
|
90
|
+
.map { $0.identifier }
|
|
91
|
+
.sorted()
|
|
92
|
+
call.resolve(["languages": identifiers])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
96
|
+
call.resolve(["speechRecognition": permissionState.rawValue])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
100
|
+
SFSpeechRecognizer.requestAuthorization { status in
|
|
101
|
+
switch status {
|
|
102
|
+
case .authorized:
|
|
103
|
+
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
|
104
|
+
DispatchQueue.main.async {
|
|
105
|
+
let result: PermissionState = granted ? .granted : .denied
|
|
106
|
+
call.resolve(["speechRecognition": result.rawValue])
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
case .denied, .restricted:
|
|
110
|
+
DispatchQueue.main.async {
|
|
111
|
+
call.resolve(["speechRecognition": PermissionState.denied.rawValue])
|
|
112
|
+
}
|
|
113
|
+
case .notDetermined:
|
|
114
|
+
DispatchQueue.main.async {
|
|
115
|
+
call.resolve(["speechRecognition": PermissionState.prompt.rawValue])
|
|
116
|
+
}
|
|
117
|
+
@unknown default:
|
|
118
|
+
DispatchQueue.main.async {
|
|
119
|
+
call.resolve(["speechRecognition": PermissionState.prompt.rawValue])
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private func beginRecognition(call: CAPPluginCall, options: RecognitionOptions) {
|
|
126
|
+
guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: options.language)) else {
|
|
127
|
+
call.reject("Unsupported locale: \(options.language)")
|
|
128
|
+
cleanupRecognition(notifyStop: false)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
guard recognizer.isAvailable else {
|
|
133
|
+
call.reject("Speech recognizer is currently unavailable.")
|
|
134
|
+
cleanupRecognition(notifyStop: false)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
speechRecognizer = recognizer
|
|
139
|
+
|
|
140
|
+
do {
|
|
141
|
+
try configureAudioSession()
|
|
142
|
+
} catch {
|
|
143
|
+
call.reject("Failed to configure audio session: \(error.localizedDescription)")
|
|
144
|
+
cleanupRecognition(notifyStop: false)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
|
|
149
|
+
recognitionRequest.shouldReportPartialResults = options.partialResults
|
|
150
|
+
if #available(iOS 16.0, *) {
|
|
151
|
+
recognitionRequest.addsPunctuation = options.addPunctuation
|
|
152
|
+
}
|
|
153
|
+
self.recognitionRequest = recognitionRequest
|
|
154
|
+
|
|
155
|
+
let inputNode = audioEngine.inputNode
|
|
156
|
+
let recordingFormat = inputNode.outputFormat(forBus: 0)
|
|
157
|
+
inputNode.removeTap(onBus: 0)
|
|
158
|
+
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
|
|
159
|
+
self?.recognitionRequest?.append(buffer)
|
|
160
|
+
}
|
|
161
|
+
hasInstalledTap = true
|
|
162
|
+
|
|
163
|
+
audioEngine.prepare()
|
|
164
|
+
|
|
165
|
+
do {
|
|
166
|
+
try audioEngine.start()
|
|
167
|
+
notifyListeners("listeningState", data: ["status": "started"])
|
|
168
|
+
} catch {
|
|
169
|
+
call.reject("Unable to start audio engine: \(error.localizedDescription)")
|
|
170
|
+
cleanupRecognition(notifyStop: false)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if options.partialResults {
|
|
175
|
+
call.resolve()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
recognitionTask = recognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
|
|
179
|
+
guard let self else { return }
|
|
180
|
+
if let result {
|
|
181
|
+
let matches = self.buildMatches(from: result, maxResults: options.maxResults)
|
|
182
|
+
if options.partialResults {
|
|
183
|
+
DispatchQueue.main.async {
|
|
184
|
+
self.notifyListeners("partialResults", data: ["matches": matches])
|
|
185
|
+
}
|
|
186
|
+
} else if result.isFinal {
|
|
187
|
+
DispatchQueue.main.async {
|
|
188
|
+
self.activeCall?.resolve(["matches": matches])
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if result.isFinal {
|
|
193
|
+
self.cleanupRecognition(notifyStop: true)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if let error {
|
|
198
|
+
self.handleRecognitionError(error)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private func configureAudioSession() throws {
|
|
204
|
+
let session = AVAudioSession.sharedInstance()
|
|
205
|
+
try session.setCategory(.playAndRecord, options: [.defaultToSpeaker, .duckOthers])
|
|
206
|
+
try session.setMode(.measurement)
|
|
207
|
+
try session.setActive(true, options: .notifyOthersOnDeactivation)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private func cleanupRecognition(notifyStop: Bool) {
|
|
211
|
+
DispatchQueue.main.async {
|
|
212
|
+
if self.audioEngine.isRunning {
|
|
213
|
+
self.audioEngine.stop()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if self.hasInstalledTap {
|
|
217
|
+
self.audioEngine.inputNode.removeTap(onBus: 0)
|
|
218
|
+
self.hasInstalledTap = false
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
self.recognitionRequest?.endAudio()
|
|
222
|
+
self.recognitionRequest = nil
|
|
223
|
+
self.recognitionTask?.cancel()
|
|
224
|
+
self.recognitionTask = nil
|
|
225
|
+
self.speechRecognizer = nil
|
|
226
|
+
self.currentOptions = nil
|
|
227
|
+
self.activeCall = nil
|
|
228
|
+
|
|
229
|
+
if notifyStop {
|
|
230
|
+
self.notifyListeners("listeningState", data: ["status": "stopped"])
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private func handleRecognitionError(_ error: Error) {
|
|
236
|
+
DispatchQueue.main.async {
|
|
237
|
+
self.cleanupRecognition(notifyStop: true)
|
|
238
|
+
self.activeCall?.reject(error.localizedDescription)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private func buildMatches(from result: SFSpeechRecognitionResult, maxResults: Int) -> [String] {
|
|
243
|
+
var matches: [String] = []
|
|
244
|
+
for transcription in result.transcriptions where matches.count < maxResults {
|
|
245
|
+
matches.append(transcription.formattedString)
|
|
246
|
+
}
|
|
247
|
+
return matches
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private var isSpeechPermissionGranted: Bool {
|
|
251
|
+
switch SFSpeechRecognizer.authorizationStatus() {
|
|
252
|
+
case .authorized:
|
|
253
|
+
return true
|
|
254
|
+
case .notDetermined, .denied, .restricted:
|
|
255
|
+
return false
|
|
256
|
+
@unknown default:
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private var permissionState: PermissionState {
|
|
262
|
+
let speechStatus = SFSpeechRecognizer.authorizationStatus()
|
|
263
|
+
let micStatus = AVAudioSession.sharedInstance().recordPermission
|
|
264
|
+
|
|
265
|
+
if speechStatus == .denied || speechStatus == .restricted || micStatus == .denied {
|
|
266
|
+
return .denied
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if speechStatus == .notDetermined || micStatus == .undetermined {
|
|
270
|
+
return .prompt
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return .granted
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private struct RecognitionOptions {
|
|
278
|
+
let language: String
|
|
279
|
+
let maxResults: Int
|
|
280
|
+
let partialResults: Bool
|
|
281
|
+
let addPunctuation: Bool
|
|
282
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-speech-recognition",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Modern Capacitor plugin for on-device and cloud speech recognition with live partial results.",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorSpeechRecognition.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-speech-recognition.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-speech-recognition/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"speech",
|
|
31
|
+
"recognition",
|
|
32
|
+
"voice",
|
|
33
|
+
"transcription"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
37
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorSpeechRecognition -destination generic/platform=iOS",
|
|
38
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
39
|
+
"verify:web": "npm run build",
|
|
40
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
41
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
42
|
+
"eslint": "eslint . --ext .ts",
|
|
43
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java,kt}\" --plugin=prettier-plugin-java",
|
|
44
|
+
"swiftlint": "node-swiftlint",
|
|
45
|
+
"docgen": "docgen --api SpeechRecognitionPlugin --output-readme README.md --output-json dist/docs.json",
|
|
46
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
47
|
+
"clean": "rimraf ./dist",
|
|
48
|
+
"watch": "tsc --watch",
|
|
49
|
+
"prepublishOnly": "npm run build"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@capacitor/android": "^7.0.0",
|
|
53
|
+
"@capacitor/cli": "^7.0.0",
|
|
54
|
+
"@capacitor/core": "^7.0.0",
|
|
55
|
+
"@capacitor/docgen": "^0.3.0",
|
|
56
|
+
"@capacitor/ios": "^7.0.0",
|
|
57
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
58
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
59
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
60
|
+
"@types/node": "^22.13.1",
|
|
61
|
+
"eslint": "^8.57.0",
|
|
62
|
+
"eslint-plugin-import": "^2.31.0",
|
|
63
|
+
"husky": "^9.1.7",
|
|
64
|
+
"prettier": "^3.4.2",
|
|
65
|
+
"prettier-plugin-java": "^2.6.7",
|
|
66
|
+
"rimraf": "^6.0.1",
|
|
67
|
+
"rollup": "^4.34.6",
|
|
68
|
+
"swiftlint": "^2.0.0",
|
|
69
|
+
"typescript": "^5.7.3"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@capacitor/core": ">=7.0.0"
|
|
73
|
+
},
|
|
74
|
+
"eslintConfig": {
|
|
75
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
76
|
+
},
|
|
77
|
+
"prettier": "@ionic/prettier-config",
|
|
78
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
79
|
+
"capacitor": {
|
|
80
|
+
"ios": {
|
|
81
|
+
"src": "ios"
|
|
82
|
+
},
|
|
83
|
+
"android": {
|
|
84
|
+
"src": "android"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|