@gmessier/nitro-speech 0.4.0 → 0.4.1
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/README.md +23 -12
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/AutoStopper.kt +7 -7
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HybridRecognizer.kt +29 -14
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/Logger.kt +16 -0
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/RecognitionListenerSession.kt +11 -12
- package/ios/Audio/AudioLevelTracker.swift +16 -22
- package/ios/Engines/RecognizerEngine.swift +16 -13
- package/ios/HybridRecognizer.swift +8 -0
- package/ios/Shared/AutoStopper.swift +1 -1
- package/lib/Recognizer/RecognizerRef.d.ts +2 -0
- package/lib/Recognizer/RecognizerRef.js +4 -1
- package/lib/Recognizer/methods.d.ts +1 -0
- package/lib/Recognizer/methods.js +4 -0
- package/lib/Recognizer/types.d.ts +1 -1
- package/lib/Recognizer/useRecognizer.js +9 -9
- package/lib/Recognizer/useRecognizerIsActive.d.ts +25 -0
- package/lib/Recognizer/useRecognizerIsActive.js +40 -0
- package/lib/Recognizer/useVoiceInputVolume.d.ts +1 -1
- package/lib/index.d.ts +6 -5
- package/lib/index.js +6 -5
- package/lib/specs/Recognizer.nitro.d.ts +7 -5
- package/nitrogen/generated/android/c++/JHybridRecognizerSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JHybridRecognizerSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/HybridRecognizerSpec.kt +4 -0
- package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Bridge.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridRecognizerSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/swift/HybridRecognizerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridRecognizerSpec_cxx.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridRecognizerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridRecognizerSpec.hpp +1 -0
- package/package.json +1 -1
- package/src/Recognizer/RecognizerRef.ts +4 -0
- package/src/Recognizer/methods.ts +5 -0
- package/src/Recognizer/types.ts +1 -0
- package/src/Recognizer/useRecognizer.ts +9 -7
- package/src/Recognizer/useRecognizerIsActive.ts +49 -0
- package/src/Recognizer/useVoiceInputVolume.ts +1 -1
- package/src/index.ts +12 -5
- package/src/specs/Recognizer.nitro.ts +8 -5
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
#### Key Features:
|
|
15
15
|
|
|
16
|
-
- ⚡ Built
|
|
16
|
+
- ⚡ Built with Nitro Modules for low-overhead native binding
|
|
17
17
|
- 🌎 Supports 60+ languages
|
|
18
18
|
- 🍎 The only library that uses new `SpeechAnalyzer` with `SpeechTranscriber` or `DictationTranscriber` API for iOS 26+ (with fallback to legacy `SFSpeechRecognition` for older versions)
|
|
19
19
|
- ⏱️ Timer for silence
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
- Callback `onAutoFinishProgress` fires periodically with interval
|
|
22
22
|
- Configurable interval `autoFinishProgressIntervalMs` value (default: 1 sec)
|
|
23
23
|
- Method `updateConfig` with `autoFinishRecognitionMs` and `autoFinishProgressIntervalMs`
|
|
24
|
-
allows
|
|
24
|
+
allows changing the value on the fly
|
|
25
25
|
- Method `resetAutoFinishTime` resets the Timer to the threshold
|
|
26
26
|
- Method `addAutoFinishTime` adds ms once without changing threshold
|
|
27
27
|
- Configurable volume-based sensitivity `resetAutoFinishVoiceSensitivity` for the timer from 0 to 1
|
|
28
28
|
- 🎤 Rich user voice input management
|
|
29
|
-
- Hook `useVoiceInputVolume()` for `raw` or `smoothed` normalized
|
|
30
|
-
volume level from 0 to 1 -> easy to use for UI animations;
|
|
29
|
+
- Hook `useVoiceInputVolume()` for `raw` or `smoothed` normalized volume level from 0 to 1 -> easy to use for UI animations;
|
|
31
30
|
And `db` as human-friendly value
|
|
32
31
|
- Flexible callback `onVolumeChange` for custom behavior
|
|
33
|
-
-
|
|
32
|
+
- Static method `getVoiceInputVolume()`
|
|
33
|
+
- 🧩 Lifecycle methods: `prewarm` | `updateConfig` | `getIsActive`
|
|
34
34
|
- 👆 Configurable Haptic Feedback on start and finish
|
|
35
35
|
- 🎚️ Speech-quality configurations:
|
|
36
36
|
- Result is grouped by speech segments into Batches.
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
- [Cross-component control: RecognizerRef](#cross-component-control-recognizerref)
|
|
54
54
|
- [Multithreading (react-native-worklets)](#multithreading-react-native-worklets)
|
|
55
55
|
- [Voice input volume](#voice-input-volume)
|
|
56
|
+
- [useRecognizerIsActive](#userecognizerisactive)
|
|
56
57
|
- [Unsafe: SpeechRecognizer](#unsafe-speechrecognizer)
|
|
57
58
|
- [Requirements](#requirements)
|
|
58
59
|
- [Compatibility](#compatibility)
|
|
@@ -123,11 +124,11 @@ Both permissions are required for speech recognition to work on iOS.
|
|
|
123
124
|
| **Auto-finish progress** | Callback `onAutoFinishProgress` with countdown until auto-stop | ✅ | ✅ |
|
|
124
125
|
| **Add Auto-finish Time** | Adds time to the auto finish timer once without changing the timer threshold | ✅ | ✅ |
|
|
125
126
|
| **Reset Auto-finish Time** | Resets the Timer to the threshold | ✅ | ✅ |
|
|
126
|
-
| **Voice input volume** |
|
|
127
|
+
| **Voice input volume** | `useVoiceInputVolume`, `getVoiceInputVolume()`, `onVolumeChange` | ✅ | ✅ |
|
|
127
128
|
| **Reset Auto-finish Sensitivity** | The voice detector sensitivity to reset the Auto-finish time | ✅ | ✅ |
|
|
128
129
|
| **Prewarm** | Prepares resources, downloads assets, confirms locale availability | ✅ | ✅ |
|
|
129
|
-
| **Update config** | Static method `updateConfig` allows
|
|
130
|
-
| **
|
|
130
|
+
| **Update config** | Static method `updateConfig` allows updating the config on the fly | ✅ | ✅ |
|
|
131
|
+
| **Is Active** | Static method `getIsActive()` | ✅ | ✅ |
|
|
131
132
|
| **Haptic feedback** | Haptic feedback on recording start/stop | ✅ | ✅ |
|
|
132
133
|
| **Permission handling** | Dedicated `onPermissionDenied` callback | ✅ | ✅ |
|
|
133
134
|
| **Background handling** | Stop when app loses focus/goes to background | ✅ | ✅ |
|
|
@@ -162,6 +163,7 @@ function MyComponent() {
|
|
|
162
163
|
updateConfig,
|
|
163
164
|
getSupportedLocalesIOS,
|
|
164
165
|
getIsActive,
|
|
166
|
+
getVoiceInputVolume,
|
|
165
167
|
} = useRecognizer({
|
|
166
168
|
onReadyForSpeech: () => {
|
|
167
169
|
console.log('Listening...');
|
|
@@ -271,6 +273,7 @@ RecognizerRef.updateConfig(
|
|
|
271
273
|
true
|
|
272
274
|
);
|
|
273
275
|
RecognizerRef.getIsActive();
|
|
276
|
+
RecognizerRef.getVoiceInputVolume();
|
|
274
277
|
RecognizerRef.stopListening();
|
|
275
278
|
// iOS only
|
|
276
279
|
RecognizerRef.getSupportedLocalesIOS();
|
|
@@ -321,8 +324,6 @@ function VoiceMeter() {
|
|
|
321
324
|
As a better alternative you can control volume via SharedValue and apply it only on UI thread with Reanimated.
|
|
322
325
|
This way you will avoid re-renders since the volume will be stored on UI thread
|
|
323
326
|
|
|
324
|
-
Warning: this approach will disable the built-in `useVoiceInputVolume` hook.
|
|
325
|
-
|
|
326
327
|
```typescript
|
|
327
328
|
function VoiceMeter() {
|
|
328
329
|
const sharedVolume = useSharedValue(0)
|
|
@@ -341,6 +342,16 @@ function VoiceMeter() {
|
|
|
341
342
|
}
|
|
342
343
|
```
|
|
343
344
|
|
|
345
|
+
### useRecognizerIsActive
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { useRecognizerIsActive } from '@gmessier/nitro-speech';
|
|
349
|
+
|
|
350
|
+
function MyComponent() {
|
|
351
|
+
const isActive = useRecognizerIsActive();
|
|
352
|
+
return <Text>{isActive ? 'Listening...' : 'Not listening'}</Text>;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
344
355
|
|
|
345
356
|
### Unsafe: SpeechRecognizer
|
|
346
357
|
|
|
@@ -415,7 +426,7 @@ The `SpeechRecognizer.dispose()` method is **NOT SAFE** and should rarely be use
|
|
|
415
426
|
|
|
416
427
|
## Compatibility
|
|
417
428
|
|
|
418
|
-
Latest versions of `@gmessier/nitro-speech`
|
|
429
|
+
Latest versions of `@gmessier/nitro-speech` require [react-native-nitro-modules 0.35.0 or higher](https://github.com/mrousavy/nitro/releases/tag/v0.35.0).
|
|
419
430
|
|
|
420
431
|
|
|
421
432
|
| Compatibility | Supported versions |
|
|
@@ -427,7 +438,7 @@ Latest versions of `@gmessier/nitro-speech` requires [react-native-nitro-modules
|
|
|
427
438
|
|
|
428
439
|
### Android Gradle sync issues
|
|
429
440
|
|
|
430
|
-
If you're having issues with Android Gradle sync, try running the prebuild for the library
|
|
441
|
+
If you're having issues with Android Gradle sync, try running the prebuild for the library that causes the issue:
|
|
431
442
|
|
|
432
443
|
e.g. failed in `react-native-nitro-modules`:
|
|
433
444
|
|
|
@@ -2,7 +2,6 @@ package com.margelo.nitro.nitrospeech.recognizer
|
|
|
2
2
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.Looper
|
|
5
|
-
import android.util.Log
|
|
6
5
|
import kotlin.math.max
|
|
7
6
|
|
|
8
7
|
class AutoStopper(
|
|
@@ -12,12 +11,13 @@ class AutoStopper(
|
|
|
12
11
|
val onTimeout: () -> Unit,
|
|
13
12
|
) {
|
|
14
13
|
companion object {
|
|
15
|
-
private const val TAG = "HybridRecognizer"
|
|
16
14
|
private const val DEFAULT_SILENCE_THRESHOLD_MS = 8000.0
|
|
17
15
|
private const val DEFAULT_PROGRESS_INTERVAL_MS = 1000.0
|
|
18
16
|
private const val MIN_PROGRESS_INTERVAL_MS = 50.0
|
|
19
17
|
}
|
|
20
18
|
|
|
19
|
+
private val logger = Logger(disable = false)
|
|
20
|
+
|
|
21
21
|
private var silenceThresholdMs: Double = clampMs(silenceThresholdMs ?: DEFAULT_SILENCE_THRESHOLD_MS)
|
|
22
22
|
private var progressIntervalMs: Double = clampMs(progressIntervalMs ?: DEFAULT_PROGRESS_INTERVAL_MS)
|
|
23
23
|
|
|
@@ -31,7 +31,7 @@ class AutoStopper(
|
|
|
31
31
|
private val tickRunnable = Runnable { tick() }
|
|
32
32
|
|
|
33
33
|
fun resetTimer() {
|
|
34
|
-
|
|
34
|
+
logger.log("resetTimer | isStopped: $isStopped | ms: ${System.currentTimeMillis()}")
|
|
35
35
|
handler.removeCallbacks(tickRunnable)
|
|
36
36
|
isTimerScheduled = false
|
|
37
37
|
if (isStopped) return
|
|
@@ -55,7 +55,7 @@ class AutoStopper(
|
|
|
55
55
|
|
|
56
56
|
fun addMsOnce(extraMs: Double) {
|
|
57
57
|
if (isStopped || !extraMs.isFinite()) return
|
|
58
|
-
|
|
58
|
+
logger.log("addMsOnce | extraMs: $extraMs")
|
|
59
59
|
timeLeftMs += extraMs
|
|
60
60
|
didTimeout = false
|
|
61
61
|
if (timeLeftMs > 0 && isTimerScheduled) {
|
|
@@ -65,7 +65,7 @@ class AutoStopper(
|
|
|
65
65
|
|
|
66
66
|
fun updateProgressInterval(newIntervalMs: Double) {
|
|
67
67
|
if (isStopped) return
|
|
68
|
-
|
|
68
|
+
logger.log("updateProgressInterval | newIntervalMs: $newIntervalMs")
|
|
69
69
|
progressIntervalMs = clampMs(newIntervalMs)
|
|
70
70
|
if (isTimerScheduled) {
|
|
71
71
|
scheduleNextTickLocked()
|
|
@@ -83,7 +83,7 @@ class AutoStopper(
|
|
|
83
83
|
if (isStopped || didTimeout) return
|
|
84
84
|
timeLeftMs -= progressIntervalMs
|
|
85
85
|
if (timeLeftMs > 0) {
|
|
86
|
-
|
|
86
|
+
logger.log("onProgress | timeLeftMs: $timeLeftMs")
|
|
87
87
|
onProgress(timeLeftMs)
|
|
88
88
|
scheduleNextTickLocked()
|
|
89
89
|
return
|
|
@@ -92,7 +92,7 @@ class AutoStopper(
|
|
|
92
92
|
didTimeout = true
|
|
93
93
|
handler.removeCallbacks(tickRunnable)
|
|
94
94
|
isTimerScheduled = false
|
|
95
|
-
|
|
95
|
+
logger.log("onTimeout | ms: ${System.currentTimeMillis()}")
|
|
96
96
|
onTimeout()
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -7,7 +7,6 @@ import android.os.Handler
|
|
|
7
7
|
import android.os.Looper
|
|
8
8
|
import android.speech.RecognizerIntent
|
|
9
9
|
import android.speech.SpeechRecognizer
|
|
10
|
-
import android.util.Log
|
|
11
10
|
import androidx.annotation.Keep
|
|
12
11
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
13
12
|
import com.margelo.nitro.NitroModules
|
|
@@ -21,12 +20,14 @@ import com.margelo.nitro.nitrospeech.VolumeChangeEvent
|
|
|
21
20
|
@Keep
|
|
22
21
|
class HybridRecognizer: HybridRecognizerSpec() {
|
|
23
22
|
companion object {
|
|
24
|
-
private const val TAG = "HybridRecognizer"
|
|
25
23
|
private const val POST_RECOGNITION_DELAY = 250L
|
|
26
24
|
}
|
|
27
25
|
|
|
26
|
+
private val logger = Logger(disable = false)
|
|
27
|
+
|
|
28
28
|
private var isActive: Boolean = false
|
|
29
29
|
private var config: SpeechRecognitionConfig? = null
|
|
30
|
+
private var volumeChangeEvent: VolumeChangeEvent = VolumeChangeEvent(0.0,0.0,null)
|
|
30
31
|
private var autoStopper: AutoStopper? = null
|
|
31
32
|
private var speechRecognizer: SpeechRecognizer? = null
|
|
32
33
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
@@ -51,7 +52,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
51
52
|
@DoNotStrip
|
|
52
53
|
@Keep
|
|
53
54
|
override fun startListening(params: SpeechRecognitionConfig?) {
|
|
54
|
-
|
|
55
|
+
logger.log("startListening: $params")
|
|
55
56
|
if (isActive) {
|
|
56
57
|
onFinishRecognition(
|
|
57
58
|
null,
|
|
@@ -94,7 +95,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
94
95
|
@DoNotStrip
|
|
95
96
|
@Keep
|
|
96
97
|
override fun stopListening() {
|
|
97
|
-
|
|
98
|
+
logger.log("stopListening called")
|
|
98
99
|
if (!isActive) return
|
|
99
100
|
onFinishRecognition(null, null, true)
|
|
100
101
|
mainHandler.postDelayed({
|
|
@@ -117,7 +118,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
117
118
|
@DoNotStrip
|
|
118
119
|
@Keep
|
|
119
120
|
override fun addAutoFinishTime(additionalTimeMs: Double?) {
|
|
120
|
-
|
|
121
|
+
logger.log("addAutoFinishTime")
|
|
121
122
|
if (!isActive) return
|
|
122
123
|
|
|
123
124
|
if (additionalTimeMs != null) {
|
|
@@ -134,7 +135,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
134
135
|
newConfig: MutableSpeechRecognitionConfig?,
|
|
135
136
|
resetAutoFinishTime: Boolean?
|
|
136
137
|
) {
|
|
137
|
-
|
|
138
|
+
logger.log("updateConfig $newConfig",)
|
|
138
139
|
if (!isActive) return
|
|
139
140
|
|
|
140
141
|
val newTimeMs = if (newConfig?.autoFinishRecognitionMs != null) newConfig.autoFinishRecognitionMs else config?.autoFinishRecognitionMs
|
|
@@ -177,6 +178,12 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
177
178
|
return isActive
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
@DoNotStrip
|
|
182
|
+
@Keep
|
|
183
|
+
override fun getVoiceInputVolume(): VolumeChangeEvent {
|
|
184
|
+
return volumeChangeEvent
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
@DoNotStrip
|
|
181
188
|
@Keep
|
|
182
189
|
override fun getSupportedLocalesIOS(): Array<String> {
|
|
@@ -204,12 +211,14 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
204
211
|
}
|
|
205
212
|
)
|
|
206
213
|
val recognitionListenerSession = RecognitionListenerSession(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
autoStopper,
|
|
215
|
+
config,
|
|
216
|
+
fireVolumeChangeEvent = { event -> fireVolumeChangeEvent(event) },
|
|
217
|
+
onFinishRecognition = { result, errorMessage, recordingStopped ->
|
|
218
|
+
onFinishRecognition(result, errorMessage, recordingStopped)
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
213
222
|
speechRecognizer?.setRecognitionListener(recognitionListenerSession.createRecognitionListener())
|
|
214
223
|
|
|
215
224
|
val languageModel = if (config?.androidUseWebSearchModel == true) RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH else RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
|
@@ -262,7 +271,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
262
271
|
|
|
263
272
|
private fun cleanup() {
|
|
264
273
|
try {
|
|
265
|
-
|
|
274
|
+
logger.log("cleanup called")
|
|
266
275
|
autoStopper?.stop()
|
|
267
276
|
autoStopper = null
|
|
268
277
|
speechRecognizer?.stopListening()
|
|
@@ -270,7 +279,7 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
270
279
|
speechRecognizer = null
|
|
271
280
|
isActive = false
|
|
272
281
|
// Reset voice meter in JS consumers after stop/error cleanup.
|
|
273
|
-
|
|
282
|
+
fireVolumeChangeEvent(VolumeChangeEvent(0.0,0.0,null))
|
|
274
283
|
} catch (e: Exception) {
|
|
275
284
|
onFinishRecognition(
|
|
276
285
|
null,
|
|
@@ -291,4 +300,10 @@ class HybridRecognizer: HybridRecognizerSpec() {
|
|
|
291
300
|
onResult?.invoke(result.toTypedArray())
|
|
292
301
|
}
|
|
293
302
|
}
|
|
303
|
+
|
|
304
|
+
private fun fireVolumeChangeEvent(event: VolumeChangeEvent) {
|
|
305
|
+
logger.log("fireVolumeChangeEvent ${event}")
|
|
306
|
+
volumeChangeEvent = event
|
|
307
|
+
onVolumeChange?.invoke(event)
|
|
308
|
+
}
|
|
294
309
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrospeech.recognizer
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
|
|
5
|
+
class Logger (
|
|
6
|
+
private val disable: Boolean
|
|
7
|
+
) {
|
|
8
|
+
private val isLogging = false
|
|
9
|
+
companion object {
|
|
10
|
+
private const val TAG = "HybridRecognizer"
|
|
11
|
+
}
|
|
12
|
+
fun log(message: String) {
|
|
13
|
+
if (disable || !isLogging) return
|
|
14
|
+
Log.d(TAG, message)
|
|
15
|
+
}
|
|
16
|
+
}
|
package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/RecognitionListenerSession.kt
CHANGED
|
@@ -3,7 +3,6 @@ package com.margelo.nitro.nitrospeech.recognizer
|
|
|
3
3
|
import android.os.Bundle
|
|
4
4
|
import android.speech.RecognitionListener
|
|
5
5
|
import android.speech.SpeechRecognizer
|
|
6
|
-
import android.util.Log
|
|
7
6
|
import com.margelo.nitro.nitrospeech.SpeechRecognitionConfig
|
|
8
7
|
import com.margelo.nitro.nitrospeech.VolumeChangeEvent
|
|
9
8
|
import kotlin.math.max
|
|
@@ -12,11 +11,11 @@ import kotlin.math.roundToInt
|
|
|
12
11
|
class RecognitionListenerSession (
|
|
13
12
|
private val autoStopper: AutoStopper?,
|
|
14
13
|
private val config: SpeechRecognitionConfig?,
|
|
15
|
-
private val
|
|
14
|
+
private val fireVolumeChangeEvent: (event: VolumeChangeEvent) -> Unit,
|
|
16
15
|
private val onFinishRecognition: (result: ArrayList<String>?, errorMessage: String?, recordingStopped: Boolean) -> Unit,
|
|
17
16
|
) {
|
|
17
|
+
private val logger = Logger(disable = false)
|
|
18
18
|
companion object {
|
|
19
|
-
private const val TAG = "HybridRecognizer"
|
|
20
19
|
private const val SPEECH_LEVEL_THRESHOLD = 0.35
|
|
21
20
|
private const val FLOOR_RISE_ALPHA = 0.01f
|
|
22
21
|
private const val FLOOR_FALL_ALPHA = 0.20f
|
|
@@ -40,11 +39,11 @@ class RecognitionListenerSession (
|
|
|
40
39
|
override fun onBeginningOfSpeech() {}
|
|
41
40
|
override fun onRmsChanged(rmsdB: Float) {
|
|
42
41
|
val volumeEvent = getVolume(rmsdB)
|
|
43
|
-
|
|
42
|
+
fireVolumeChangeEvent(volumeEvent)
|
|
44
43
|
val threshold =
|
|
45
44
|
config?.resetAutoFinishVoiceSensitivity?.coerceIn(0.0, 1.0)
|
|
46
45
|
?: SPEECH_LEVEL_THRESHOLD.toDouble()
|
|
47
|
-
|
|
46
|
+
// logger.log("onRmsChanged: ${volumeEvent}")
|
|
48
47
|
if (volumeEvent.rawVolume > threshold) {
|
|
49
48
|
autoStopper?.resetTimer()
|
|
50
49
|
}
|
|
@@ -75,7 +74,7 @@ class RecognitionListenerSession (
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
override fun onResults(results: Bundle?) {
|
|
78
|
-
|
|
77
|
+
logger.log("onResults: $resultBatches")
|
|
79
78
|
onFinishRecognition(resultBatches, null, true)
|
|
80
79
|
autoStopper?.stop()
|
|
81
80
|
autoStopper?.onTimeout()
|
|
@@ -85,26 +84,26 @@ class RecognitionListenerSession (
|
|
|
85
84
|
val matches = partialResults?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
|
|
86
85
|
|
|
87
86
|
if (matches.isNullOrEmpty() || matches[0] == "") {
|
|
88
|
-
|
|
87
|
+
logger.log("onPartialResults[0], skip, NO RECOGNIZE")
|
|
89
88
|
return
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
autoStopper?.resetTimer()
|
|
93
|
-
|
|
92
|
+
logger.log("onPartialResults[0], add ${matches[0]}")
|
|
94
93
|
var currentBatches = resultBatches
|
|
95
94
|
if (currentBatches.isNullOrEmpty()) {
|
|
96
|
-
|
|
95
|
+
logger.log("onPartialResults[1], NO BATCHES YET | add first")
|
|
97
96
|
currentBatches = arrayListOf(matches[0])
|
|
98
97
|
} else {
|
|
99
|
-
|
|
98
|
+
logger.log("onPartialResults[1], current batches $currentBatches")
|
|
100
99
|
val prevBatchLength = currentBatches[currentBatches.lastIndex].length
|
|
101
100
|
val match = if (config?.disableRepeatingFilter == true) matches[0] else repeatingFilter(matches[0])
|
|
102
101
|
val matchLength = match.length
|
|
103
102
|
if (config?.androidDisableBatchHandling == true || matchLength + 3 < prevBatchLength) {
|
|
104
|
-
|
|
103
|
+
logger.log("onPartialResults[2], append new batch")
|
|
105
104
|
currentBatches.add(match)
|
|
106
105
|
} else {
|
|
107
|
-
|
|
106
|
+
logger.log("onPartialResults[2], update batch, replace #${currentBatches.lastIndex}")
|
|
108
107
|
currentBatches[currentBatches.lastIndex] = match
|
|
109
108
|
}
|
|
110
109
|
}
|
|
@@ -16,33 +16,18 @@ final class AudioLevelTracker {
|
|
|
16
16
|
private static let meterRelease: Float = 0.08
|
|
17
17
|
private static let defaultAutoStopResetThreshold: Double = 0.4
|
|
18
18
|
|
|
19
|
-
private var autoStopResetThreshold: Double
|
|
20
19
|
private var smoothedLevel: Float = 0
|
|
20
|
+
|
|
21
|
+
var currentSample: AudioLevelSample?
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
if let resetAutoFinishVoiceSensitivity {
|
|
24
|
-
// Clamp value between 0 and 1
|
|
25
|
-
self.autoStopResetThreshold = max(0, min(1, resetAutoFinishVoiceSensitivity))
|
|
26
|
-
} else {
|
|
27
|
-
self.autoStopResetThreshold = Self.defaultAutoStopResetThreshold
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
func updateResetAutoFinishVoiceSensitivity(newValue: Double?) {
|
|
32
|
-
if let newValue {
|
|
33
|
-
// Clamp value between 0 and 1
|
|
34
|
-
self.autoStopResetThreshold = max(0, min(1, newValue))
|
|
35
|
-
} else {
|
|
36
|
-
self.autoStopResetThreshold = Self.defaultAutoStopResetThreshold
|
|
37
|
-
}
|
|
38
|
-
}
|
|
23
|
+
private let lg = Lg(prefix: "RecognizerEngine")
|
|
39
24
|
|
|
40
25
|
func reset() {
|
|
41
26
|
smoothedLevel = 0
|
|
42
|
-
|
|
27
|
+
currentSample = nil
|
|
43
28
|
}
|
|
44
29
|
|
|
45
|
-
func process(_ buffer: AVAudioPCMBuffer) -> AudioLevelSample? {
|
|
30
|
+
func process(_ buffer: AVAudioPCMBuffer,_ autoStopResetThreshold: Double? = nil) -> AudioLevelSample? {
|
|
46
31
|
guard let samples = buffer.floatChannelData?[0] else { return nil }
|
|
47
32
|
|
|
48
33
|
let frameCount = Int(buffer.frameLength)
|
|
@@ -56,11 +41,20 @@ final class AudioLevelTracker {
|
|
|
56
41
|
let coeff = normalized > smoothedLevel ? Self.meterAttack : Self.meterRelease
|
|
57
42
|
smoothedLevel += coeff * (normalized - smoothedLevel)
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
var threshold = Self.defaultAutoStopResetThreshold
|
|
45
|
+
if let autoStopResetThreshold {
|
|
46
|
+
threshold = max(0, min(1, autoStopResetThreshold))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
currentSample = AudioLevelSample(
|
|
60
50
|
smoothed: Double(smoothedLevel * 1_000_000).rounded() / 1_000_000,
|
|
61
51
|
raw: Double(normalized * 1_000_000).rounded() / 1_000_000,
|
|
62
52
|
db: Double(db * 1_000).rounded() / 1_000,
|
|
63
|
-
resetTimer: Double(normalized) >=
|
|
53
|
+
resetTimer: Double(normalized) >= threshold
|
|
64
54
|
)
|
|
55
|
+
|
|
56
|
+
lg.log("[AudioLevelTracker.process] autoStopResetThreshold: \(threshold)")
|
|
57
|
+
|
|
58
|
+
return currentSample
|
|
65
59
|
}
|
|
66
60
|
}
|
|
@@ -18,7 +18,7 @@ class RecognizerEngine {
|
|
|
18
18
|
var hardwareFormat: AVAudioFormat?
|
|
19
19
|
weak var recognizerDelegate: RecognizerDelegate?
|
|
20
20
|
|
|
21
|
-
private let audioLevelTracker
|
|
21
|
+
private let audioLevelTracker = AudioLevelTracker()
|
|
22
22
|
private var appStateObserver: AppStateObserver?
|
|
23
23
|
private var audioEngine: AVAudioEngine?
|
|
24
24
|
private var autoStopper: AutoStopper?
|
|
@@ -29,9 +29,6 @@ class RecognizerEngine {
|
|
|
29
29
|
init(locale: Locale, delegate: RecognizerDelegate) {
|
|
30
30
|
self.locale = locale
|
|
31
31
|
self.recognizerDelegate = delegate
|
|
32
|
-
self.audioLevelTracker = AudioLevelTracker(
|
|
33
|
-
resetAutoFinishVoiceSensitivity: delegate.config?.resetAutoFinishVoiceSensitivity
|
|
34
|
-
)
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
// MARK: - Recognizer Methods
|
|
@@ -84,7 +81,10 @@ class RecognizerEngine {
|
|
|
84
81
|
format: hardwareFormat
|
|
85
82
|
) { [weak self] buffer, _ in
|
|
86
83
|
guard let self, let recognizerDelegate = self.recognizerDelegate else { return }
|
|
87
|
-
if let sample = self.audioLevelTracker.process(
|
|
84
|
+
if let sample = self.audioLevelTracker.process(
|
|
85
|
+
buffer,
|
|
86
|
+
recognizerDelegate.config?.resetAutoFinishVoiceSensitivity
|
|
87
|
+
) {
|
|
88
88
|
// Send buffer volume data
|
|
89
89
|
recognizerDelegate.volumeChange(
|
|
90
90
|
event:
|
|
@@ -148,13 +148,7 @@ class RecognizerEngine {
|
|
|
148
148
|
from: "updateSession"
|
|
149
149
|
)
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
if let newSensitivity = newConfig?.resetAutoFinishVoiceSensitivity,
|
|
153
|
-
newSensitivity != currentConfig?.resetAutoFinishVoiceSensitivity {
|
|
154
|
-
audioLevelTracker.updateResetAutoFinishVoiceSensitivity(
|
|
155
|
-
newValue: newSensitivity
|
|
156
|
-
)
|
|
157
|
-
}
|
|
151
|
+
|
|
158
152
|
if let addMsToTimer {
|
|
159
153
|
// Add time to the timer once
|
|
160
154
|
autoStopper?.addMsOnce(
|
|
@@ -168,7 +162,16 @@ class RecognizerEngine {
|
|
|
168
162
|
// Only update new non-nil values in the config
|
|
169
163
|
recognizerDelegate.softlyUpdateConfig(newConfig: newConfig)
|
|
170
164
|
}
|
|
171
|
-
|
|
165
|
+
|
|
166
|
+
func getVoiceInputVolume() -> VolumeChangeEvent? {
|
|
167
|
+
guard let currentSample = audioLevelTracker.currentSample else { return nil }
|
|
168
|
+
return VolumeChangeEvent(
|
|
169
|
+
smoothedVolume: currentSample.smoothed,
|
|
170
|
+
rawVolume: currentSample.raw,
|
|
171
|
+
db: currentSample.db
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
172
175
|
func cleanup(from: String) {
|
|
173
176
|
lg.log("[cleanup]: \(from)")
|
|
174
177
|
let wasActive = isActive
|
|
@@ -68,6 +68,14 @@ class HybridRecognizer: HybridRecognizerSpec {
|
|
|
68
68
|
func getIsActive() -> Bool {
|
|
69
69
|
engine?.isActive ?? false
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
func getVoiceInputVolume() -> VolumeChangeEvent {
|
|
73
|
+
return engine?.getVoiceInputVolume() ?? VolumeChangeEvent(
|
|
74
|
+
smoothedVolume: 0,
|
|
75
|
+
rawVolume: 0,
|
|
76
|
+
db: nil
|
|
77
|
+
)
|
|
78
|
+
}
|
|
71
79
|
|
|
72
80
|
func getSupportedLocalesIOS() -> [String] {
|
|
73
81
|
return self.coordinator.getSupportedLocales()
|
|
@@ -5,7 +5,7 @@ final class AutoStopper {
|
|
|
5
5
|
private static let defaultProgressIntervalMs = 1000.0
|
|
6
6
|
private static let minProgressIntervalMs = 50.0
|
|
7
7
|
|
|
8
|
-
private let lg = Lg(prefix: "AutoStopper", disable:
|
|
8
|
+
private let lg = Lg(prefix: "AutoStopper", disable: false)
|
|
9
9
|
|
|
10
10
|
private let queue = DispatchQueue(label: "com.margelo.nitrospeech.autostopper")
|
|
11
11
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { recognizerAddAutoFinishTime, recognizerGetSupportedLocalesIOS, recognizerGetIsActive, recognizerResetAutoFinishTime, recognizerStartListening, recognizerStopListening, recognizerUpdateConfig, } from './methods';
|
|
1
|
+
import { recognizerAddAutoFinishTime, recognizerGetSupportedLocalesIOS, recognizerGetIsActive, recognizerResetAutoFinishTime, recognizerStartListening, recognizerStopListening, recognizerUpdateConfig, recognizerGetVoiceInputVolume, } from './methods';
|
|
2
2
|
/**
|
|
3
3
|
* Safe cross-component reference to the Speech Recognizer methods.
|
|
4
|
+
*
|
|
5
|
+
* All methods support worklets and UI thread calls
|
|
4
6
|
*/
|
|
5
7
|
export const RecognizerRef = {
|
|
6
8
|
startListening: recognizerStartListening,
|
|
@@ -9,5 +11,6 @@ export const RecognizerRef = {
|
|
|
9
11
|
addAutoFinishTime: recognizerAddAutoFinishTime,
|
|
10
12
|
updateConfig: recognizerUpdateConfig,
|
|
11
13
|
getIsActive: recognizerGetIsActive,
|
|
14
|
+
getVoiceInputVolume: recognizerGetVoiceInputVolume,
|
|
12
15
|
getSupportedLocalesIOS: recognizerGetSupportedLocalesIOS,
|
|
13
16
|
};
|
|
@@ -5,4 +5,5 @@ export declare const recognizerResetAutoFinishTime: () => void;
|
|
|
5
5
|
export declare const recognizerAddAutoFinishTime: (additionalTimeMs?: number) => void;
|
|
6
6
|
export declare const recognizerUpdateConfig: (newConfig: SpeechRecognitionConfig, resetAutoFinishTime?: boolean) => void;
|
|
7
7
|
export declare const recognizerGetIsActive: () => boolean;
|
|
8
|
+
export declare const recognizerGetVoiceInputVolume: () => import("./types").VolumeChangeEvent;
|
|
8
9
|
export declare const recognizerGetSupportedLocalesIOS: () => string[];
|
|
@@ -23,6 +23,10 @@ export const recognizerGetIsActive = () => {
|
|
|
23
23
|
'worklet';
|
|
24
24
|
return SpeechRecognizer.getIsActive();
|
|
25
25
|
};
|
|
26
|
+
export const recognizerGetVoiceInputVolume = () => {
|
|
27
|
+
'worklet';
|
|
28
|
+
return SpeechRecognizer.getVoiceInputVolume();
|
|
29
|
+
};
|
|
26
30
|
export const recognizerGetSupportedLocalesIOS = () => {
|
|
27
31
|
'worklet';
|
|
28
32
|
return SpeechRecognizer.getSupportedLocalesIOS().sort();
|
|
@@ -2,5 +2,5 @@ import type { Recognizer as RecognizerSpec } from '../specs/Recognizer.nitro';
|
|
|
2
2
|
import type { SpeechRecognitionConfig } from '../specs/SpeechRecognitionConfig';
|
|
3
3
|
import type { VolumeChangeEvent } from '../specs/VolumeChangeEvent';
|
|
4
4
|
type RecognizerCallbacks = Pick<RecognizerSpec, 'onReadyForSpeech' | 'onRecordingStopped' | 'onResult' | 'onAutoFinishProgress' | 'onError' | 'onPermissionDenied' | 'onVolumeChange'>;
|
|
5
|
-
type RecognizerMethods = Pick<RecognizerSpec, 'startListening' | 'stopListening' | 'resetAutoFinishTime' | 'addAutoFinishTime' | 'updateConfig' | 'getIsActive' | 'getSupportedLocalesIOS'>;
|
|
5
|
+
type RecognizerMethods = Pick<RecognizerSpec, 'startListening' | 'stopListening' | 'resetAutoFinishTime' | 'addAutoFinishTime' | 'updateConfig' | 'getIsActive' | 'getVoiceInputVolume' | 'getSupportedLocalesIOS'>;
|
|
6
6
|
export type { RecognizerSpec, SpeechRecognitionConfig, VolumeChangeEvent, RecognizerCallbacks, RecognizerMethods, };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { recognizerResetAutoFinishTime, recognizerAddAutoFinishTime, recognizerUpdateConfig, recognizerGetIsActive, recognizerGetSupportedLocalesIOS, recognizerStartListening, recognizerStopListening, } from './methods';
|
|
2
|
+
import { recognizerResetAutoFinishTime, recognizerAddAutoFinishTime, recognizerUpdateConfig, recognizerGetIsActive, recognizerGetSupportedLocalesIOS, recognizerStartListening, recognizerStopListening, recognizerGetVoiceInputVolume, } from './methods';
|
|
3
3
|
import { SpeechRecognizer } from './SpeechRecognizer';
|
|
4
4
|
import { speechRecognizerVolumeChangeHandler } from './useVoiceInputVolume';
|
|
5
|
+
import { speechRecognizerActiveStateHandler } from './useRecognizerIsActive';
|
|
5
6
|
/**
|
|
6
7
|
* Safe, lifecycle-aware hook to use the recognizer.
|
|
7
8
|
*
|
|
@@ -17,18 +18,12 @@ import { speechRecognizerVolumeChangeHandler } from './useVoiceInputVolume';
|
|
|
17
18
|
*/
|
|
18
19
|
export const useRecognizer = (callbacks, destroyDeps = []) => {
|
|
19
20
|
useEffect(() => {
|
|
20
|
-
if (callbacks.onVolumeChange) {
|
|
21
|
-
SpeechRecognizer.onVolumeChange = (event) => {
|
|
22
|
-
callbacks.onVolumeChange?.(event);
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
SpeechRecognizer.onVolumeChange = speechRecognizerVolumeChangeHandler;
|
|
27
|
-
}
|
|
28
21
|
SpeechRecognizer.onReadyForSpeech = () => {
|
|
22
|
+
speechRecognizerActiveStateHandler(true);
|
|
29
23
|
callbacks.onReadyForSpeech?.();
|
|
30
24
|
};
|
|
31
25
|
SpeechRecognizer.onRecordingStopped = () => {
|
|
26
|
+
speechRecognizerActiveStateHandler(false);
|
|
32
27
|
callbacks.onRecordingStopped?.();
|
|
33
28
|
};
|
|
34
29
|
SpeechRecognizer.onResult = (resultBatches) => {
|
|
@@ -43,6 +38,10 @@ export const useRecognizer = (callbacks, destroyDeps = []) => {
|
|
|
43
38
|
SpeechRecognizer.onPermissionDenied = () => {
|
|
44
39
|
callbacks.onPermissionDenied?.();
|
|
45
40
|
};
|
|
41
|
+
SpeechRecognizer.onVolumeChange = (event) => {
|
|
42
|
+
speechRecognizerVolumeChangeHandler(event);
|
|
43
|
+
callbacks.onVolumeChange?.(event);
|
|
44
|
+
};
|
|
46
45
|
return () => {
|
|
47
46
|
SpeechRecognizer.onReadyForSpeech = undefined;
|
|
48
47
|
SpeechRecognizer.onRecordingStopped = undefined;
|
|
@@ -66,6 +65,7 @@ export const useRecognizer = (callbacks, destroyDeps = []) => {
|
|
|
66
65
|
addAutoFinishTime: recognizerAddAutoFinishTime,
|
|
67
66
|
updateConfig: recognizerUpdateConfig,
|
|
68
67
|
getIsActive: recognizerGetIsActive,
|
|
68
|
+
getVoiceInputVolume: recognizerGetVoiceInputVolume,
|
|
69
69
|
getSupportedLocalesIOS: recognizerGetSupportedLocalesIOS,
|
|
70
70
|
};
|
|
71
71
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type OnActiveStateChange = (isActive: boolean) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Returns true if the speech recognition session is active.
|
|
4
|
+
*/
|
|
5
|
+
export declare const useRecognizerIsActive: () => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Direct access to default Speech Recognizer isActive state change handler.
|
|
8
|
+
*
|
|
9
|
+
* In case you use static Speech Recognizer:
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { speechRecognizerActiveStateHandler } from '@gmessier/nitro-speech'
|
|
13
|
+
*
|
|
14
|
+
* SpeechRecognizer.onReadyForSpeech = () => {
|
|
15
|
+
* speechRecognizerActiveStateHandler(true)
|
|
16
|
+
* }
|
|
17
|
+
* SpeechRecognizer.onRecordingStopped = () => {
|
|
18
|
+
* speechRecognizerActiveStateHandler(false)
|
|
19
|
+
* }
|
|
20
|
+
* ... // setup everything else
|
|
21
|
+
* SpeechRecognizer.startListening({ locale: 'en-US' })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const speechRecognizerActiveStateHandler: OnActiveStateChange;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
const subscribers = new Set();
|
|
3
|
+
let recognizerIsActive = false;
|
|
4
|
+
const getSnapshot = () => {
|
|
5
|
+
return recognizerIsActive;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the speech recognition session is active.
|
|
9
|
+
*/
|
|
10
|
+
export const useRecognizerIsActive = () => {
|
|
11
|
+
return useSyncExternalStore((subscriber) => {
|
|
12
|
+
subscribers.add(subscriber);
|
|
13
|
+
return () => subscribers.delete(subscriber);
|
|
14
|
+
}, getSnapshot);
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Direct access to default Speech Recognizer isActive state change handler.
|
|
18
|
+
*
|
|
19
|
+
* In case you use static Speech Recognizer:
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { speechRecognizerActiveStateHandler } from '@gmessier/nitro-speech'
|
|
23
|
+
*
|
|
24
|
+
* SpeechRecognizer.onReadyForSpeech = () => {
|
|
25
|
+
* speechRecognizerActiveStateHandler(true)
|
|
26
|
+
* }
|
|
27
|
+
* SpeechRecognizer.onRecordingStopped = () => {
|
|
28
|
+
* speechRecognizerActiveStateHandler(false)
|
|
29
|
+
* }
|
|
30
|
+
* ... // setup everything else
|
|
31
|
+
* SpeechRecognizer.startListening({ locale: 'en-US' })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const speechRecognizerActiveStateHandler = (isActive) => {
|
|
35
|
+
if (isActive === recognizerIsActive) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
recognizerIsActive = isActive;
|
|
39
|
+
subscribers.forEach((subscriber) => subscriber?.(isActive));
|
|
40
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export * from './Recognizer/useRecognizer';
|
|
2
|
-
export * from './Recognizer/useVoiceInputVolume';
|
|
3
|
-
export * from './Recognizer/SpeechRecognizer';
|
|
4
|
-
export * from './Recognizer/RecognizerRef';
|
|
5
1
|
export * from './Recognizer/types';
|
|
6
|
-
export
|
|
2
|
+
export { useRecognizer } from './Recognizer/useRecognizer';
|
|
3
|
+
export { useVoiceInputVolume, speechRecognizerVolumeChangeHandler, } from './Recognizer/useVoiceInputVolume';
|
|
4
|
+
export { useRecognizerIsActive, speechRecognizerActiveStateHandler, } from './Recognizer/useRecognizerIsActive';
|
|
5
|
+
export { SpeechRecognizer } from './Recognizer/SpeechRecognizer';
|
|
6
|
+
export { RecognizerRef } from './Recognizer/RecognizerRef';
|
|
7
|
+
export { NitroSpeech } from './NitroSpeech';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export * from './Recognizer/useRecognizer';
|
|
2
|
-
export * from './Recognizer/useVoiceInputVolume';
|
|
3
|
-
export * from './Recognizer/SpeechRecognizer';
|
|
4
|
-
export * from './Recognizer/RecognizerRef';
|
|
5
1
|
export * from './Recognizer/types';
|
|
6
|
-
export
|
|
2
|
+
export { useRecognizer } from './Recognizer/useRecognizer';
|
|
3
|
+
export { useVoiceInputVolume, speechRecognizerVolumeChangeHandler, } from './Recognizer/useVoiceInputVolume';
|
|
4
|
+
export { useRecognizerIsActive, speechRecognizerActiveStateHandler, } from './Recognizer/useRecognizerIsActive';
|
|
5
|
+
export { SpeechRecognizer } from './Recognizer/SpeechRecognizer';
|
|
6
|
+
export { RecognizerRef } from './Recognizer/RecognizerRef';
|
|
7
|
+
export { NitroSpeech } from './NitroSpeech';
|
|
@@ -52,6 +52,10 @@ export interface Recognizer extends HybridObject<{
|
|
|
52
52
|
* Returns true if the speech recognition is active.
|
|
53
53
|
*/
|
|
54
54
|
getIsActive(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the current voice input volume.
|
|
57
|
+
*/
|
|
58
|
+
getVoiceInputVolume(): VolumeChangeEvent;
|
|
55
59
|
/**
|
|
56
60
|
* Returns a list of supported locales.
|
|
57
61
|
*
|
|
@@ -67,11 +71,11 @@ export interface Recognizer extends HybridObject<{
|
|
|
67
71
|
*/
|
|
68
72
|
onRecordingStopped?: () => void;
|
|
69
73
|
/**
|
|
70
|
-
*
|
|
74
|
+
* Fires each time either a new batch has been added or the last batch has been updated.
|
|
71
75
|
*/
|
|
72
76
|
onResult?: (resultBatches: string[]) => void;
|
|
73
77
|
/**
|
|
74
|
-
*
|
|
78
|
+
* Fires every {@linkcode SpeechRecognitionConfig.autoFinishProgressIntervalMs} or 1000ms
|
|
75
79
|
*
|
|
76
80
|
* Time left in milliseconds until the timer stops.
|
|
77
81
|
*
|
|
@@ -87,9 +91,7 @@ export interface Recognizer extends HybridObject<{
|
|
|
87
91
|
*/
|
|
88
92
|
onPermissionDenied?: () => void;
|
|
89
93
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @warning overriding it will disable the built-in `useVoiceInputVolume` hook.
|
|
94
|
+
* Fires with high and arbitrary frequency (many times per second) while audio recording is active.
|
|
93
95
|
*/
|
|
94
96
|
onVolumeChange?: (event: VolumeChangeEvent) => void;
|
|
95
97
|
}
|
|
@@ -233,6 +233,11 @@ namespace margelo::nitro::nitrospeech {
|
|
|
233
233
|
auto __result = method(_javaPart);
|
|
234
234
|
return static_cast<bool>(__result);
|
|
235
235
|
}
|
|
236
|
+
VolumeChangeEvent JHybridRecognizerSpec::getVoiceInputVolume() {
|
|
237
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JVolumeChangeEvent>()>("getVoiceInputVolume");
|
|
238
|
+
auto __result = method(_javaPart);
|
|
239
|
+
return __result->toCpp();
|
|
240
|
+
}
|
|
236
241
|
std::vector<std::string> JHybridRecognizerSpec::getSupportedLocalesIOS() {
|
|
237
242
|
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JArrayClass<jni::JString>>()>("getSupportedLocalesIOS");
|
|
238
243
|
auto __result = method(_javaPart);
|
|
@@ -74,6 +74,7 @@ namespace margelo::nitro::nitrospeech {
|
|
|
74
74
|
void addAutoFinishTime(std::optional<double> additionalTimeMs) override;
|
|
75
75
|
void updateConfig(const std::optional<MutableSpeechRecognitionConfig>& newConfig, std::optional<bool> resetAutoFinishTime) override;
|
|
76
76
|
bool getIsActive() override;
|
|
77
|
+
VolumeChangeEvent getVoiceInputVolume() override;
|
|
77
78
|
std::vector<std::string> getSupportedLocalesIOS() override;
|
|
78
79
|
|
|
79
80
|
private:
|
package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/HybridRecognizerSpec.kt
CHANGED
|
@@ -153,6 +153,10 @@ abstract class HybridRecognizerSpec: HybridObject() {
|
|
|
153
153
|
@Keep
|
|
154
154
|
abstract fun getIsActive(): Boolean
|
|
155
155
|
|
|
156
|
+
@DoNotStrip
|
|
157
|
+
@Keep
|
|
158
|
+
abstract fun getVoiceInputVolume(): VolumeChangeEvent
|
|
159
|
+
|
|
156
160
|
@DoNotStrip
|
|
157
161
|
@Keep
|
|
158
162
|
abstract fun getSupportedLocalesIOS(): Array<String>
|
|
@@ -454,6 +454,15 @@ namespace margelo::nitro::nitrospeech::bridge::swift {
|
|
|
454
454
|
return Result<bool>::withError(error);
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
// pragma MARK: Result<VolumeChangeEvent>
|
|
458
|
+
using Result_VolumeChangeEvent_ = Result<VolumeChangeEvent>;
|
|
459
|
+
inline Result_VolumeChangeEvent_ create_Result_VolumeChangeEvent_(const VolumeChangeEvent& value) noexcept {
|
|
460
|
+
return Result<VolumeChangeEvent>::withValue(value);
|
|
461
|
+
}
|
|
462
|
+
inline Result_VolumeChangeEvent_ create_Result_VolumeChangeEvent_(const std::exception_ptr& error) noexcept {
|
|
463
|
+
return Result<VolumeChangeEvent>::withError(error);
|
|
464
|
+
}
|
|
465
|
+
|
|
457
466
|
// pragma MARK: Result<std::vector<std::string>>
|
|
458
467
|
using Result_std__vector_std__string__ = Result<std::vector<std::string>>;
|
|
459
468
|
inline Result_std__vector_std__string__ create_Result_std__vector_std__string__(const std::vector<std::string>& value) noexcept {
|
|
@@ -178,6 +178,14 @@ namespace margelo::nitro::nitrospeech {
|
|
|
178
178
|
auto __value = std::move(__result.value());
|
|
179
179
|
return __value;
|
|
180
180
|
}
|
|
181
|
+
inline VolumeChangeEvent getVoiceInputVolume() override {
|
|
182
|
+
auto __result = _swiftPart.getVoiceInputVolume();
|
|
183
|
+
if (__result.hasError()) [[unlikely]] {
|
|
184
|
+
std::rethrow_exception(__result.error());
|
|
185
|
+
}
|
|
186
|
+
auto __value = std::move(__result.value());
|
|
187
|
+
return __value;
|
|
188
|
+
}
|
|
181
189
|
inline std::vector<std::string> getSupportedLocalesIOS() override {
|
|
182
190
|
auto __result = _swiftPart.getSupportedLocalesIOS();
|
|
183
191
|
if (__result.hasError()) [[unlikely]] {
|
|
@@ -26,6 +26,7 @@ public protocol HybridRecognizerSpec_protocol: HybridObject {
|
|
|
26
26
|
func addAutoFinishTime(additionalTimeMs: Double?) throws -> Void
|
|
27
27
|
func updateConfig(newConfig: MutableSpeechRecognitionConfig?, resetAutoFinishTime: Bool?) throws -> Void
|
|
28
28
|
func getIsActive() throws -> Bool
|
|
29
|
+
func getVoiceInputVolume() throws -> VolumeChangeEvent
|
|
29
30
|
func getSupportedLocalesIOS() throws -> [String]
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -452,6 +452,18 @@ open class HybridRecognizerSpec_cxx {
|
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
@inline(__always)
|
|
456
|
+
public final func getVoiceInputVolume() -> bridge.Result_VolumeChangeEvent_ {
|
|
457
|
+
do {
|
|
458
|
+
let __result = try self.__implementation.getVoiceInputVolume()
|
|
459
|
+
let __resultCpp = __result
|
|
460
|
+
return bridge.create_Result_VolumeChangeEvent_(__resultCpp)
|
|
461
|
+
} catch (let __error) {
|
|
462
|
+
let __exceptionPtr = __error.toCpp()
|
|
463
|
+
return bridge.create_Result_VolumeChangeEvent_(__exceptionPtr)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
455
467
|
@inline(__always)
|
|
456
468
|
public final func getSupportedLocalesIOS() -> bridge.Result_std__vector_std__string__ {
|
|
457
469
|
do {
|
|
@@ -35,6 +35,7 @@ namespace margelo::nitro::nitrospeech {
|
|
|
35
35
|
prototype.registerHybridMethod("addAutoFinishTime", &HybridRecognizerSpec::addAutoFinishTime);
|
|
36
36
|
prototype.registerHybridMethod("updateConfig", &HybridRecognizerSpec::updateConfig);
|
|
37
37
|
prototype.registerHybridMethod("getIsActive", &HybridRecognizerSpec::getIsActive);
|
|
38
|
+
prototype.registerHybridMethod("getVoiceInputVolume", &HybridRecognizerSpec::getVoiceInputVolume);
|
|
38
39
|
prototype.registerHybridMethod("getSupportedLocalesIOS", &HybridRecognizerSpec::getSupportedLocalesIOS);
|
|
39
40
|
});
|
|
40
41
|
}
|
|
@@ -80,6 +80,7 @@ namespace margelo::nitro::nitrospeech {
|
|
|
80
80
|
virtual void addAutoFinishTime(std::optional<double> additionalTimeMs) = 0;
|
|
81
81
|
virtual void updateConfig(const std::optional<MutableSpeechRecognitionConfig>& newConfig, std::optional<bool> resetAutoFinishTime) = 0;
|
|
82
82
|
virtual bool getIsActive() = 0;
|
|
83
|
+
virtual VolumeChangeEvent getVoiceInputVolume() = 0;
|
|
83
84
|
virtual std::vector<std::string> getSupportedLocalesIOS() = 0;
|
|
84
85
|
|
|
85
86
|
protected:
|
package/package.json
CHANGED
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
recognizerStartListening,
|
|
8
8
|
recognizerStopListening,
|
|
9
9
|
recognizerUpdateConfig,
|
|
10
|
+
recognizerGetVoiceInputVolume,
|
|
10
11
|
} from './methods'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Safe cross-component reference to the Speech Recognizer methods.
|
|
15
|
+
*
|
|
16
|
+
* All methods support worklets and UI thread calls
|
|
14
17
|
*/
|
|
15
18
|
export const RecognizerRef: RecognizerMethods = {
|
|
16
19
|
startListening: recognizerStartListening,
|
|
@@ -19,5 +22,6 @@ export const RecognizerRef: RecognizerMethods = {
|
|
|
19
22
|
addAutoFinishTime: recognizerAddAutoFinishTime,
|
|
20
23
|
updateConfig: recognizerUpdateConfig,
|
|
21
24
|
getIsActive: recognizerGetIsActive,
|
|
25
|
+
getVoiceInputVolume: recognizerGetVoiceInputVolume,
|
|
22
26
|
getSupportedLocalesIOS: recognizerGetSupportedLocalesIOS,
|
|
23
27
|
}
|
|
@@ -34,6 +34,11 @@ export const recognizerGetIsActive = () => {
|
|
|
34
34
|
return SpeechRecognizer.getIsActive()
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export const recognizerGetVoiceInputVolume = () => {
|
|
38
|
+
'worklet'
|
|
39
|
+
return SpeechRecognizer.getVoiceInputVolume()
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
export const recognizerGetSupportedLocalesIOS = () => {
|
|
38
43
|
'worklet'
|
|
39
44
|
return SpeechRecognizer.getSupportedLocalesIOS().sort()
|
package/src/Recognizer/types.ts
CHANGED
|
@@ -7,10 +7,12 @@ import {
|
|
|
7
7
|
recognizerGetSupportedLocalesIOS,
|
|
8
8
|
recognizerStartListening,
|
|
9
9
|
recognizerStopListening,
|
|
10
|
+
recognizerGetVoiceInputVolume,
|
|
10
11
|
} from './methods'
|
|
11
12
|
import type { RecognizerCallbacks, RecognizerMethods } from './types'
|
|
12
13
|
import { SpeechRecognizer } from './SpeechRecognizer'
|
|
13
14
|
import { speechRecognizerVolumeChangeHandler } from './useVoiceInputVolume'
|
|
15
|
+
import { speechRecognizerActiveStateHandler } from './useRecognizerIsActive'
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Safe, lifecycle-aware hook to use the recognizer.
|
|
@@ -30,17 +32,12 @@ export const useRecognizer = (
|
|
|
30
32
|
destroyDeps: DependencyList = []
|
|
31
33
|
): RecognizerMethods => {
|
|
32
34
|
useEffect(() => {
|
|
33
|
-
if (callbacks.onVolumeChange) {
|
|
34
|
-
SpeechRecognizer.onVolumeChange = (event) => {
|
|
35
|
-
callbacks.onVolumeChange?.(event)
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
SpeechRecognizer.onVolumeChange = speechRecognizerVolumeChangeHandler
|
|
39
|
-
}
|
|
40
35
|
SpeechRecognizer.onReadyForSpeech = () => {
|
|
36
|
+
speechRecognizerActiveStateHandler(true)
|
|
41
37
|
callbacks.onReadyForSpeech?.()
|
|
42
38
|
}
|
|
43
39
|
SpeechRecognizer.onRecordingStopped = () => {
|
|
40
|
+
speechRecognizerActiveStateHandler(false)
|
|
44
41
|
callbacks.onRecordingStopped?.()
|
|
45
42
|
}
|
|
46
43
|
SpeechRecognizer.onResult = (resultBatches: string[]) => {
|
|
@@ -55,6 +52,10 @@ export const useRecognizer = (
|
|
|
55
52
|
SpeechRecognizer.onPermissionDenied = () => {
|
|
56
53
|
callbacks.onPermissionDenied?.()
|
|
57
54
|
}
|
|
55
|
+
SpeechRecognizer.onVolumeChange = (event) => {
|
|
56
|
+
speechRecognizerVolumeChangeHandler(event)
|
|
57
|
+
callbacks.onVolumeChange?.(event)
|
|
58
|
+
}
|
|
58
59
|
return () => {
|
|
59
60
|
SpeechRecognizer.onReadyForSpeech = undefined
|
|
60
61
|
SpeechRecognizer.onRecordingStopped = undefined
|
|
@@ -80,6 +81,7 @@ export const useRecognizer = (
|
|
|
80
81
|
addAutoFinishTime: recognizerAddAutoFinishTime,
|
|
81
82
|
updateConfig: recognizerUpdateConfig,
|
|
82
83
|
getIsActive: recognizerGetIsActive,
|
|
84
|
+
getVoiceInputVolume: recognizerGetVoiceInputVolume,
|
|
83
85
|
getSupportedLocalesIOS: recognizerGetSupportedLocalesIOS,
|
|
84
86
|
}
|
|
85
87
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react'
|
|
2
|
+
|
|
3
|
+
type OnActiveStateChange = (isActive: boolean) => void
|
|
4
|
+
|
|
5
|
+
const subscribers = new Set<OnActiveStateChange>()
|
|
6
|
+
|
|
7
|
+
let recognizerIsActive = false
|
|
8
|
+
|
|
9
|
+
const getSnapshot = () => {
|
|
10
|
+
return recognizerIsActive
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the speech recognition session is active.
|
|
15
|
+
*/
|
|
16
|
+
export const useRecognizerIsActive = () => {
|
|
17
|
+
return useSyncExternalStore((subscriber) => {
|
|
18
|
+
subscribers.add(subscriber)
|
|
19
|
+
return () => subscribers.delete(subscriber)
|
|
20
|
+
}, getSnapshot)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Direct access to default Speech Recognizer isActive state change handler.
|
|
25
|
+
*
|
|
26
|
+
* In case you use static Speech Recognizer:
|
|
27
|
+
*
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { speechRecognizerActiveStateHandler } from '@gmessier/nitro-speech'
|
|
30
|
+
*
|
|
31
|
+
* SpeechRecognizer.onReadyForSpeech = () => {
|
|
32
|
+
* speechRecognizerActiveStateHandler(true)
|
|
33
|
+
* }
|
|
34
|
+
* SpeechRecognizer.onRecordingStopped = () => {
|
|
35
|
+
* speechRecognizerActiveStateHandler(false)
|
|
36
|
+
* }
|
|
37
|
+
* ... // setup everything else
|
|
38
|
+
* SpeechRecognizer.startListening({ locale: 'en-US' })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const speechRecognizerActiveStateHandler: OnActiveStateChange = (
|
|
42
|
+
isActive
|
|
43
|
+
) => {
|
|
44
|
+
if (isActive === recognizerIsActive) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
recognizerIsActive = isActive
|
|
48
|
+
subscribers.forEach((subscriber) => subscriber?.(isActive))
|
|
49
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react'
|
|
2
2
|
import type { RecognizerSpec, VolumeChangeEvent } from './types'
|
|
3
3
|
|
|
4
|
-
type OnVolumeChange = RecognizerSpec['onVolumeChange']
|
|
4
|
+
type OnVolumeChange = Exclude<RecognizerSpec['onVolumeChange'], undefined>
|
|
5
5
|
|
|
6
6
|
const subscribers = new Set<OnVolumeChange>()
|
|
7
7
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
export * from './Recognizer/useRecognizer'
|
|
2
|
-
export * from './Recognizer/useVoiceInputVolume'
|
|
3
|
-
export * from './Recognizer/SpeechRecognizer'
|
|
4
|
-
export * from './Recognizer/RecognizerRef'
|
|
5
1
|
export * from './Recognizer/types'
|
|
6
|
-
export
|
|
2
|
+
export { useRecognizer } from './Recognizer/useRecognizer'
|
|
3
|
+
export {
|
|
4
|
+
useVoiceInputVolume,
|
|
5
|
+
speechRecognizerVolumeChangeHandler,
|
|
6
|
+
} from './Recognizer/useVoiceInputVolume'
|
|
7
|
+
export {
|
|
8
|
+
useRecognizerIsActive,
|
|
9
|
+
speechRecognizerActiveStateHandler,
|
|
10
|
+
} from './Recognizer/useRecognizerIsActive'
|
|
11
|
+
export { SpeechRecognizer } from './Recognizer/SpeechRecognizer'
|
|
12
|
+
export { RecognizerRef } from './Recognizer/RecognizerRef'
|
|
13
|
+
export { NitroSpeech } from './NitroSpeech'
|
|
@@ -66,6 +66,11 @@ export interface Recognizer extends HybridObject<{
|
|
|
66
66
|
*/
|
|
67
67
|
getIsActive(): boolean
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Returns the current voice input volume.
|
|
71
|
+
*/
|
|
72
|
+
getVoiceInputVolume(): VolumeChangeEvent
|
|
73
|
+
|
|
69
74
|
/**
|
|
70
75
|
* Returns a list of supported locales.
|
|
71
76
|
*
|
|
@@ -82,11 +87,11 @@ export interface Recognizer extends HybridObject<{
|
|
|
82
87
|
*/
|
|
83
88
|
onRecordingStopped?: () => void
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
90
|
+
* Fires each time either a new batch has been added or the last batch has been updated.
|
|
86
91
|
*/
|
|
87
92
|
onResult?: (resultBatches: string[]) => void
|
|
88
93
|
/**
|
|
89
|
-
*
|
|
94
|
+
* Fires every {@linkcode SpeechRecognitionConfig.autoFinishProgressIntervalMs} or 1000ms
|
|
90
95
|
*
|
|
91
96
|
* Time left in milliseconds until the timer stops.
|
|
92
97
|
*
|
|
@@ -102,9 +107,7 @@ export interface Recognizer extends HybridObject<{
|
|
|
102
107
|
*/
|
|
103
108
|
onPermissionDenied?: () => void
|
|
104
109
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* @warning overriding it will disable the built-in `useVoiceInputVolume` hook.
|
|
110
|
+
* Fires with high and arbitrary frequency (many times per second) while audio recording is active.
|
|
108
111
|
*/
|
|
109
112
|
onVolumeChange?: (event: VolumeChangeEvent) => void
|
|
110
113
|
}
|