@gmessier/nitro-speech 0.3.3 → 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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -148
  3. package/android/build.gradle +0 -1
  4. package/android/src/main/cpp/cpp-adapter.cpp +5 -1
  5. package/android/src/main/java/com/margelo/nitro/nitrospeech/HybridNitroSpeech.kt +2 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/AutoStopper.kt +82 -18
  7. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HybridRecognizer.kt +118 -30
  8. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/Logger.kt +16 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/RecognitionListenerSession.kt +35 -24
  10. package/ios/{BufferUtil.swift → Audio/AudioBufferConverter.swift} +3 -34
  11. package/ios/Audio/AudioLevelTracker.swift +60 -0
  12. package/ios/Coordinator.swift +105 -0
  13. package/ios/Engines/AnalyzerEngine.swift +241 -0
  14. package/ios/Engines/DictationRuntime.swift +67 -0
  15. package/ios/Engines/RecognizerEngine.swift +315 -0
  16. package/ios/Engines/SFSpeechEngine.swift +119 -0
  17. package/ios/Engines/SpeechRuntime.swift +58 -0
  18. package/ios/Engines/TranscriberRuntimeProtocol.swift +21 -0
  19. package/ios/HybridNitroSpeech.swift +1 -10
  20. package/ios/HybridRecognizer.swift +142 -191
  21. package/ios/LocaleManager.swift +73 -0
  22. package/ios/{AppStateObserver.swift → Shared/AppStateObserver.swift} +1 -2
  23. package/ios/Shared/AutoStopper.swift +147 -0
  24. package/ios/Shared/HapticImpact.swift +24 -0
  25. package/ios/Shared/Log.swift +41 -0
  26. package/ios/Shared/Permissions.swift +59 -0
  27. package/ios/Shared/Utils.swift +58 -0
  28. package/lib/NitroSpeech.d.ts +2 -0
  29. package/lib/NitroSpeech.js +2 -0
  30. package/lib/Recognizer/RecognizerRef.d.ts +7 -0
  31. package/lib/Recognizer/RecognizerRef.js +16 -0
  32. package/lib/Recognizer/SpeechRecognizer.d.ts +8 -0
  33. package/lib/Recognizer/SpeechRecognizer.js +9 -0
  34. package/lib/Recognizer/methods.d.ts +9 -0
  35. package/lib/Recognizer/methods.js +33 -0
  36. package/lib/Recognizer/types.d.ts +6 -0
  37. package/lib/Recognizer/types.js +1 -0
  38. package/lib/Recognizer/useRecognizer.d.ts +16 -0
  39. package/lib/Recognizer/useRecognizer.js +71 -0
  40. package/lib/Recognizer/useRecognizerIsActive.d.ts +25 -0
  41. package/lib/Recognizer/useRecognizerIsActive.js +40 -0
  42. package/lib/Recognizer/useVoiceInputVolume.d.ts +25 -0
  43. package/lib/Recognizer/useVoiceInputVolume.js +52 -0
  44. package/lib/index.d.ts +7 -0
  45. package/lib/index.js +7 -0
  46. package/lib/specs/NitroSpeech.nitro.d.ts +8 -0
  47. package/lib/specs/NitroSpeech.nitro.js +1 -0
  48. package/lib/specs/Recognizer.nitro.d.ts +97 -0
  49. package/lib/specs/Recognizer.nitro.js +1 -0
  50. package/lib/specs/SpeechRecognitionConfig.d.ts +162 -0
  51. package/lib/specs/SpeechRecognitionConfig.js +1 -0
  52. package/lib/specs/VolumeChangeEvent.d.ts +31 -0
  53. package/lib/specs/VolumeChangeEvent.js +1 -0
  54. package/nitro.json +0 -4
  55. package/nitrogen/generated/android/NitroSpeech+autolinking.cmake +2 -2
  56. package/nitrogen/generated/android/NitroSpeechOnLoad.cpp +4 -2
  57. package/nitrogen/generated/android/c++/JFunc_void_VolumeChangeEvent.hpp +78 -0
  58. package/nitrogen/generated/android/c++/JFunc_void_std__vector_std__string_.hpp +14 -14
  59. package/nitrogen/generated/android/c++/JHybridRecognizerSpec.cpp +73 -19
  60. package/nitrogen/generated/android/c++/JHybridRecognizerSpec.hpp +8 -4
  61. package/nitrogen/generated/android/c++/JIosPreset.hpp +58 -0
  62. package/nitrogen/generated/android/c++/JMutableSpeechRecognitionConfig.hpp +79 -0
  63. package/nitrogen/generated/android/c++/{JSpeechToTextParams.hpp → JSpeechRecognitionConfig.hpp} +48 -30
  64. package/nitrogen/generated/android/c++/JVolumeChangeEvent.hpp +65 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/Func_void_VolumeChangeEvent.kt +80 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/HybridRecognizerSpec.kt +22 -5
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/IosPreset.kt +23 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/MutableSpeechRecognitionConfig.kt +76 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/SpeechRecognitionConfig.kt +121 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/VolumeChangeEvent.kt +61 -0
  71. package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Bridge.cpp +46 -30
  72. package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Bridge.hpp +211 -69
  73. package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Umbrella.hpp +13 -3
  74. package/nitrogen/generated/ios/c++/HybridRecognizerSpecSwift.hpp +49 -9
  75. package/nitrogen/generated/ios/swift/Func_void_VolumeChangeEvent.swift +46 -0
  76. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  77. package/nitrogen/generated/ios/swift/HybridRecognizerSpec.swift +7 -3
  78. package/nitrogen/generated/ios/swift/HybridRecognizerSpec_cxx.swift +78 -18
  79. package/nitrogen/generated/ios/swift/IosPreset.swift +40 -0
  80. package/nitrogen/generated/ios/swift/MutableSpeechRecognitionConfig.swift +118 -0
  81. package/nitrogen/generated/ios/swift/{SpeechToTextParams.swift → SpeechRecognitionConfig.swift} +108 -43
  82. package/nitrogen/generated/ios/swift/VolumeChangeEvent.swift +52 -0
  83. package/nitrogen/generated/shared/c++/HybridRecognizerSpec.cpp +5 -1
  84. package/nitrogen/generated/shared/c++/HybridRecognizerSpec.hpp +18 -7
  85. package/nitrogen/generated/shared/c++/IosPreset.hpp +76 -0
  86. package/nitrogen/generated/shared/c++/MutableSpeechRecognitionConfig.hpp +105 -0
  87. package/nitrogen/generated/shared/c++/{SpeechToTextParams.hpp → SpeechRecognitionConfig.hpp} +39 -20
  88. package/nitrogen/generated/shared/c++/VolumeChangeEvent.hpp +91 -0
  89. package/package.json +15 -16
  90. package/src/NitroSpeech.ts +5 -0
  91. package/src/Recognizer/RecognizerRef.ts +27 -0
  92. package/src/Recognizer/SpeechRecognizer.ts +10 -0
  93. package/src/Recognizer/methods.ts +45 -0
  94. package/src/Recognizer/types.ts +34 -0
  95. package/src/Recognizer/useRecognizer.ts +87 -0
  96. package/src/Recognizer/useRecognizerIsActive.ts +49 -0
  97. package/src/Recognizer/useVoiceInputVolume.ts +65 -0
  98. package/src/index.ts +13 -182
  99. package/src/specs/NitroSpeech.nitro.ts +2 -163
  100. package/src/specs/Recognizer.nitro.ts +113 -0
  101. package/src/specs/SpeechRecognitionConfig.ts +167 -0
  102. package/src/specs/VolumeChangeEvent.ts +31 -0
  103. package/android/proguard-rules.pro +0 -1
  104. package/ios/AnylyzerTranscriber.swift +0 -331
  105. package/ios/AutoStopper.swift +0 -69
  106. package/ios/HapticImpact.swift +0 -32
  107. package/ios/LegacySpeechRecognizer.swift +0 -161
  108. package/lib/commonjs/index.js +0 -145
  109. package/lib/commonjs/index.js.map +0 -1
  110. package/lib/commonjs/package.json +0 -1
  111. package/lib/commonjs/specs/NitroSpeech.nitro.js +0 -6
  112. package/lib/commonjs/specs/NitroSpeech.nitro.js.map +0 -1
  113. package/lib/module/index.js +0 -138
  114. package/lib/module/index.js.map +0 -1
  115. package/lib/module/package.json +0 -1
  116. package/lib/module/specs/NitroSpeech.nitro.js +0 -4
  117. package/lib/module/specs/NitroSpeech.nitro.js.map +0 -1
  118. package/lib/tsconfig.tsbuildinfo +0 -1
  119. package/lib/typescript/index.d.ts +0 -50
  120. package/lib/typescript/index.d.ts.map +0 -1
  121. package/lib/typescript/specs/NitroSpeech.nitro.d.ts +0 -162
  122. package/lib/typescript/specs/NitroSpeech.nitro.d.ts.map +0 -1
  123. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/SpeechToTextParams.kt +0 -68
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Messier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -5,39 +5,42 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dm/@gmessier/nitro-speech.svg)](https://www.npmjs.com/package/@gmessier/nitro-speech)
6
6
 
7
7
 
8
- > If you hit an issue, please open a GitHub issue or reach out to me on Discord / Twitter (X) — response is guaranteed.
8
+ > If you hit an issue or want to request a feature, please open a GitHub issue or reach out to me on Discord / Twitter (X) — response is guaranteed.
9
9
  >
10
10
  > - GitHub Issues: [NotGeorgeMessier/nitro-speech/issues](https://github.com/NotGeorgeMessier/nitro-speech/issues)
11
11
  > - Discord: `gmessier`
12
12
  > - Twitter (X): `SufferingGeorge`
13
13
 
14
- React Native Real-Time Speech Recognition Library, powered by [Nitro Modules](https://github.com/mrousavy/nitro).
15
-
16
- #### Compatibility:
17
- ‼️ Newest versions of `@gmessier/nitro-speech` requires [react-native-nitro-modules 0.35.0 or higher](https://github.com/mrousavy/nitro/releases/tag/v0.35.0).
18
-
19
- | Compatibility | Supported versions |
20
- |---|---|
21
- | `react-native-nitro-modules <= 0.34.*` | `@gmessier/nitro-speech <= 0.2.*` |
22
- | `react-native-nitro-modules >= 0.35.*` | `@gmessier/nitro-speech >= 0.3.*` |
23
-
24
14
  #### Key Features:
25
15
 
26
- - Built on Nitro Modules for low-overhead native bridging
27
- - Uses newest advanced `SpeechAnalyzer` and `SpeechTranscriber` API for iOS 26+ (with fallback to legacy `SFSpeechRecognition` for older versions)
28
- - Configurable Timer for silence (default: 8 sec)
29
- - Callback `onAutoFinishProgress` for progress bars, etc...
30
- - Method `addAutoFinishTime` for single timer update
31
- - Method `updateAutoFinishTime` for constant timer update
32
- - Configurable Haptic Feedback on start and finish
33
- - Flexible `onVolumeChange` to display input volume in UI with built-in `useVoiceInputVolume` hook
34
- - Speech-quality configurations:
16
+ - Built with Nitro Modules for low-overhead native binding
17
+ - 🌎 Supports 60+ languages
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
+ - ⏱️ Timer for silence
20
+ - Configurable `autoFinishRecognitionMs` value (default: 8 sec)
21
+ - Callback `onAutoFinishProgress` fires periodically with interval
22
+ - Configurable interval `autoFinishProgressIntervalMs` value (default: 1 sec)
23
+ - Method `updateConfig` with `autoFinishRecognitionMs` and `autoFinishProgressIntervalMs`
24
+ allows changing the value on the fly
25
+ - Method `resetAutoFinishTime` resets the Timer to the threshold
26
+ - Method `addAutoFinishTime` adds ms once without changing threshold
27
+ - Configurable volume-based sensitivity `resetAutoFinishVoiceSensitivity` for the timer from 0 to 1
28
+ - 🎤 Rich user voice input management
29
+ - Hook `useVoiceInputVolume()` for `raw` or `smoothed` normalized volume level from 0 to 1 -> easy to use for UI animations;
30
+ And `db` as human-friendly value
31
+ - Flexible callback `onVolumeChange` for custom behavior
32
+ - Static method `getVoiceInputVolume()`
33
+ - 🧩 Lifecycle methods: `prewarm` | `updateConfig` | `getIsActive`
34
+ - 👆 Configurable Haptic Feedback on start and finish
35
+ - 🎚️ Speech-quality configurations:
35
36
  - Result is grouped by speech segments into Batches.
36
- - Param `disableRepeatingFilter` for consecutive duplicate-word filtering.
37
- - Param `androidDisableBatchHandling` for removing empty recognition result.
38
- - Embedded Permission handling
37
+ - Param `iosPreset` - `shortForm` or `general` enables best transcriber for your situation
38
+ - Param `disableRepeatingFilter` - filters out consecutive duplicate words.
39
+ - Param `androidDisableBatchHandling` - disables empty partial results
40
+ - Many more, see `SpeechRecognitionConfig`
41
+ - 🔓 Embedded Permission handling
39
42
  - Callback `onPermissionDenied` - if user denied the request
40
- - Everything else that could be found in Expo or other libraries
43
+ - 📦 Everything else that could be found in Expo or other libraries
41
44
 
42
45
  ## Table of Contents
43
46
 
@@ -48,10 +51,12 @@ React Native Real-Time Speech Recognition Library, powered by [Nitro Modules](ht
48
51
  - [Recommended: useRecognizer Hook](#recommended-userecognizer-hook)
49
52
  - [With React Navigation (important)](#with-react-navigation-important)
50
53
  - [Cross-component control: RecognizerRef](#cross-component-control-recognizerref)
54
+ - [Multithreading (react-native-worklets)](#multithreading-react-native-worklets)
51
55
  - [Voice input volume](#voice-input-volume)
52
- - [Unsafe: RecognizerSession](#unsafe-recognizersession)
53
- - [API Reference](#api-reference)
56
+ - [useRecognizerIsActive](#userecognizerisactive)
57
+ - [Unsafe: SpeechRecognizer](#unsafe-speechrecognizer)
54
58
  - [Requirements](#requirements)
59
+ - [Compatibility](#compatibility)
55
60
  - [Troubleshooting](#troubleshooting)
56
61
 
57
62
  ## Installation
@@ -88,6 +93,7 @@ No additional setup required.
88
93
 
89
94
  ### Android
90
95
 
96
+ No actions required.
91
97
  The library declares the required permission in its `AndroidManifest.xml` (merged automatically):
92
98
 
93
99
  ```xml
@@ -112,20 +118,31 @@ Both permissions are required for speech recognition to work on iOS.
112
118
 
113
119
  | Feature | Description | iOS | Android |
114
120
  |---------|-------------|-----|---------|
115
- | **Real-time transcription** | Get partial results as the user speaks, enabling live UI updates | ✅ | ✅ |
116
- | **Auto-stop on silence** | Automatically stops recognition after configurable inactivity period (default: 8s) | ✅ | ✅ |
117
- | **Auto-finish progress** | Progress callbacks showing countdown until auto-stop | ✅ | *(TODO)* |
118
- | **Haptic feedback** | Optional haptics on recording start/stop | ✅ | ✅ |
119
- | **Background handling** | Auto-stop when app loses focus/goes to background | ✅ | Not Safe *(TODO)* |
121
+ | **Real-time transcription** | Gets partial results as the user speaks | ✅ | ✅ |
122
+ | **Locale support** | 60+ Supported locales | ✅ | ✅ |
123
+ | **Auto-finish on silence** | Automatically stops recognition after configurable inactivity period | ✅ | |
124
+ | **Auto-finish progress** | Callback `onAutoFinishProgress` with countdown until auto-stop | ✅ | ✅ |
125
+ | **Add Auto-finish Time** | Adds time to the auto finish timer once without changing the timer threshold | ✅ | |
126
+ | **Reset Auto-finish Time** | Resets the Timer to the threshold | ✅ | ✅ |
127
+ | **Voice input volume** | `useVoiceInputVolume`, `getVoiceInputVolume()`, `onVolumeChange` | ✅ | ✅ |
128
+ | **Reset Auto-finish Sensitivity** | The voice detector sensitivity to reset the Auto-finish time | ✅ | ✅ |
129
+ | **Prewarm** | Prepares resources, downloads assets, confirms locale availability | ✅ | ✅ |
130
+ | **Update config** | Static method `updateConfig` allows updating the config on the fly | ✅ | ✅ |
131
+ | **Is Active** | Static method `getIsActive()` | ✅ | ✅ |
132
+ | **Haptic feedback** | Haptic feedback on recording start/stop | ✅ | ✅ |
120
133
  | **Permission handling** | Dedicated `onPermissionDenied` callback | ✅ | ✅ |
121
- | **Voice input volume** | Normalized voice input level for UI meters (`useVoiceInputVolume`) | ✅ | ✅ |
134
+ | **Background handling** | Stop when app loses focus/goes to background | ✅ | ✅ |
122
135
  | **Repeating word filter** | Removes consecutive duplicate words from artifacts | ✅ | ✅ |
123
- | **Locale support** | Configure speech recognizer for different languages | | ✅ |
136
+ | **Offensive word masking** | Control whether offensive words are masked with * | iOS 26+ | ✅ |
124
137
  | **Contextual strings** | Domain-specific vocabulary for improved accuracy | ✅ | ✅ |
125
- | **Automatic punctuation** | Adds punctuation to transcription (iOS 16+) | ✅ | Auto |
126
138
  | **Language model selection** | Choose between web search vs free-form models | Auto | ✅ |
127
- | **Offensive word masking** | Control whether offensive words are masked | Auto | ✅ |
139
+ | **Batch handling** | Filters out empty or repeated results | Auto | ✅ |
128
140
  | **Formatting quality** | Prefer quality vs speed in formatting | Auto | ✅ |
141
+ | **Transcription preset** | `iosPreset` adapts for short phrases (`shortForm`) or `general` conversation | ✅ | Auto |
142
+ | **Automatic punctuation** | Adds punctuation to transcription (iOS 16+) | ✅ | Auto |
143
+ | **Atypical speech hint** | Hint iOS that speech may include accent, lisp, or other confounding traits | ✅ | Auto |
144
+ | **getSupportedLocalesIOS** | Supported locales for iOS (No available API for Android) | ✅ | X |
145
+
129
146
 
130
147
  ## Usage
131
148
 
@@ -141,8 +158,12 @@ function MyComponent() {
141
158
  const {
142
159
  startListening,
143
160
  stopListening,
161
+ resetAutoFinishTime,
144
162
  addAutoFinishTime,
145
- updateAutoFinishTime
163
+ updateConfig,
164
+ getSupportedLocalesIOS,
165
+ getIsActive,
166
+ getVoiceInputVolume,
146
167
  } = useRecognizer({
147
168
  onReadyForSpeech: () => {
148
169
  console.log('Listening...');
@@ -167,18 +188,22 @@ function MyComponent() {
167
188
  return (
168
189
  <View>
169
190
  <TouchableOpacity onPress={() => startListening({
170
- locale: 'en-US',
171
- disableRepeatingFilter: false,
172
- autoFinishRecognitionMs: 8000,
173
-
191
+ // Universal
192
+ locale: "en-US",
174
193
  contextualStrings: ['custom', 'words'],
175
- // Haptics (both platforms)
194
+ maskOffensiveWords: false,
195
+ // Mutable properties
196
+ autoFinishRecognitionMs: 12000,
197
+ autoFinishProgressIntervalMs: 1000,
198
+ resetAutoFinishVoiceSensitivity: 0.4,
199
+ disableRepeatingFilter: false,
176
200
  startHapticFeedbackStyle: 'medium',
177
201
  stopHapticFeedbackStyle: 'light',
178
- // iOS specific
202
+ // iOS specific, non-mutable
179
203
  iosAddPunctuation: true,
180
- // Android specific
181
- maskOffensiveWords: false,
204
+ iosPreset: 'general',
205
+ iosAtypicalSpeech: false,
206
+ // Android specific, non-mutable
182
207
  androidFormattingPreferQuality: false,
183
208
  androidUseWebSearchModel: false,
184
209
  androidDisableBatchHandling: false,
@@ -191,22 +216,31 @@ function MyComponent() {
191
216
  <TouchableOpacity onPress={() => addAutoFinishTime(5000)}>
192
217
  <Text>Add 5s to Timer</Text>
193
218
  </TouchableOpacity>
194
- <TouchableOpacity onPress={() => updateAutoFinishTime(10000)}>
195
- <Text>Update Timer to 10s</Text>
219
+ <TouchableOpacity onPress={() => resetAutoFinishTime()}>
220
+ <Text>Reset Timer</Text>
221
+ </TouchableOpacity>
222
+ <TouchableOpacity onPress={() => updateConfig(
223
+ {
224
+ autoFinishRecognitionMs: 12000,
225
+ autoFinishProgressIntervalMs: 500,
226
+ resetAutoFinishVoiceSensitivity: 0.65,
227
+ },
228
+ true
229
+ )>
230
+ <Text>Update Timer to 12s, 500ms interval, 0.65 sensitivity, with reset</Text>
196
231
  </TouchableOpacity>
197
232
  </View>
198
233
  );
199
234
  }
200
235
  ```
201
236
 
202
- Use the handlers returned by this single hook instance inside that owner component.
203
- For other components, avoid creating another `useRecognizer` instance for the same session.
237
+ On iOS 26+, the recognizer prefers the newer `SpeechTranscriber` path for general cases. Setting `iosPreset: 'shortForm'`, `iosAddPunctuation: false`, or `iosAtypicalSpeech: true` switches priority to `DictationTranscriber` that is better suited for short utterances or non-standard speech patterns.
204
238
 
205
239
  ### With React Navigation (important)
206
240
 
207
241
  React Navigation **doesn’t unmount screens** when you navigate — the screen can stay mounted in the background and come back without remounting. See: [Navigation lifecycle (React Navigation)](https://reactnavigation.org/docs/8.x/navigation-lifecycle/#summary).
208
242
 
209
- Because of that, prefer tying recognition cleanup to **focus state**, not just component unmount. A simple approach is `useIsFocused()` and passing it into `useRecognizer`’s `destroyDeps` so recognition stops when the screen blurs. See: `[useIsFocused` (React Navigation)](https://reactnavigation.org/docs/8.x/use-is-focused).
243
+ Because of that, prefer tying recognition cleanup to **focus state**, not just component unmount. A simple approach is `useIsFocused()` and passing it into `useRecognizer`’s `destroyDeps` so recognition stops when the screen blurs. See: [`useIsFocused` (React Navigation)](https://reactnavigation.org/docs/8.x/use-is-focused).
210
244
 
211
245
  ```typescript
212
246
  const isFocused = useIsFocused();
@@ -229,26 +263,59 @@ import { RecognizerRef } from '@gmessier/nitro-speech';
229
263
 
230
264
  RecognizerRef.startListening({ locale: 'en-US' });
231
265
  RecognizerRef.addAutoFinishTime(5000);
232
- RecognizerRef.updateAutoFinishTime(10000, true);
266
+ RecognizerRef.resetAutoFinishTime();
267
+ RecognizerRef.updateConfig(
268
+ {
269
+ autoFinishRecognitionMs: 12000,
270
+ autoFinishProgressIntervalMs: 500,
271
+ resetAutoFinishVoiceSensitivity: 0.65,
272
+ },
273
+ true
274
+ );
233
275
  RecognizerRef.getIsActive();
276
+ RecognizerRef.getVoiceInputVolume();
234
277
  RecognizerRef.stopListening();
278
+ // iOS only
279
+ RecognizerRef.getSupportedLocalesIOS();
235
280
  ```
236
281
 
237
282
  `RecognizerRef` exposes only method handlers and is safe for cross-component method access.
238
283
 
284
+ ### Multithreading (react-native-worklets)
285
+ All methods are thread-safe and can be called from UI thread or custom worklets
286
+ ```typescript
287
+ import { createWorkletRuntime, scheduleOnRuntime } from 'react-native-worklets';
288
+ const workletRuntime = createWorkletRuntime({ name: 'background' });
289
+
290
+ onPress={() => {
291
+ scheduleOnRuntime(workletRuntime, () => {
292
+ // or SpeechRecognizer
293
+ // or just updateConfig from useRecognizer
294
+ RecognizerRef.updateConfig({
295
+ autoFinishRecognitionMs: 10000,
296
+ autoFinishProgressIntervalMs: 200,
297
+ resetAutoFinishVoiceSensitivity: 0.10,
298
+ });
299
+ });
300
+ }}
301
+ ```
302
+
239
303
  ### Voice input volume
240
304
 
241
305
  #### useVoiceInputVolume
242
306
 
243
- By default you have access to `useVoiceInputVolume` to read normalized voice input level (`0..1`) for UI meters.
244
- ⚠️ **Technical limitation**: this approach re-renders component a lot.
307
+ ⚠️ **Technical limitation**: this hook re-renders component a lot.
245
308
 
246
309
  ```typescript
247
310
  import { useVoiceInputVolume } from '@gmessier/nitro-speech';
248
311
 
249
312
  function VoiceMeter() {
250
- const volume = useVoiceInputVolume();
251
- return <Text>{volume.toFixed(2)}</Text>;
313
+ const volumeEvent = useVoiceInputVolume();
314
+ return <>
315
+ <Text>{volumeEvent.smoothedVolume.toFixed(2)}</Text>
316
+ <Text>{volumeEvent.rawVolume.toFixed(2)}</Text>
317
+ <Text>{volumeEvent.db}</Text>
318
+ </>;
252
319
  }
253
320
  ```
254
321
 
@@ -265,9 +332,9 @@ function VoiceMeter() {
265
332
  } = useRecognizer(
266
333
  {
267
334
  // ...
268
- onVolumeChange: (normVolume) => {
335
+ onVolumeChange: (volumeEvent) => {
269
336
  "worklet";
270
- sharedVolume.value = normValue
337
+ sharedVolume.value = volumeEvent.smoothedVolume
271
338
  },
272
339
  // ...
273
340
  }
@@ -275,155 +342,116 @@ function VoiceMeter() {
275
342
  }
276
343
  ```
277
344
 
345
+ ### useRecognizerIsActive
278
346
 
279
- ### Unsafe: RecognizerSession
347
+ ```typescript
348
+ import { useRecognizerIsActive } from '@gmessier/nitro-speech';
280
349
 
281
- `RecognizerSession` is the hybrid object. It gives direct access to callbacks and control methods, but it is unsafe to orchestrate the full session directly from it.
350
+ function MyComponent() {
351
+ const isActive = useRecognizerIsActive();
352
+ return <Text>{isActive ? 'Listening...' : 'Not listening'}</Text>;
353
+ }
354
+ ```
355
+
356
+ ### Unsafe: SpeechRecognizer
357
+
358
+ `SpeechRecognizer` is the hybrid object. It gives direct access to callbacks and control methods, but it is unsafe to orchestrate the full session directly from it.
282
359
 
283
360
  ```typescript
284
- import { RecognizerSession, unsafe_onVolumeChange } from '@gmessier/nitro-speech';
361
+ import { SpeechRecognizer, speechRecognizerVolumeChangeHandler } from '@gmessier/nitro-speech';
285
362
 
286
363
  // Set up callbacks
287
- RecognizerSession.onReadyForSpeech = () => {
364
+ SpeechRecognizer.onReadyForSpeech = () => {
288
365
  console.log('Listening...');
289
366
  };
290
367
 
291
- RecognizerSession.onResult = (textBatches) => {
368
+ SpeechRecognizer.onResult = (textBatches) => {
292
369
  console.log('Result:', textBatches.join('\n'));
293
370
  };
294
371
 
295
- RecognizerSession.onRecordingStopped = () => {
372
+ SpeechRecognizer.onRecordingStopped = () => {
296
373
  console.log('Stopped');
297
374
  };
298
375
 
299
- RecognizerSession.onAutoFinishProgress = (timeLeftMs) => {
376
+ SpeechRecognizer.onAutoFinishProgress = (timeLeftMs) => {
300
377
  console.log('Auto-stop in:', timeLeftMs, 'ms');
301
378
  };
302
379
 
303
- RecognizerSession.onError = (error) => {
380
+ SpeechRecognizer.onError = (error) => {
304
381
  console.log('Error:', error);
305
382
  };
306
383
 
307
- RecognizerSession.onPermissionDenied = () => {
384
+ SpeechRecognizer.onPermissionDenied = () => {
308
385
  console.log('Permission denied');
309
386
  };
310
387
 
311
- RecognizerSession.onVolumeChange = (volume) => {
388
+ SpeechRecognizer.onVolumeChange = (volume) => {
312
389
  console.log('new volume: ', volume);
313
390
  };
314
- // OR use unsafe_onVolumeChange to enable useVoiceInputVolume hook manually
315
- RecognizerSession.onVolumeChange = unsafe_onVolumeChange
391
+ // OR use speechRecognizerVolumeChangeHandler to enable useVoiceInputVolume hook manually
392
+ SpeechRecognizer.onVolumeChange = speechRecognizerVolumeChangeHandler
316
393
 
317
394
 
318
395
  // Start listening
319
- RecognizerSession.startListening({
396
+ SpeechRecognizer.startListening({
320
397
  locale: 'en-US',
321
398
  });
322
399
 
323
400
  // Stop listening
324
- RecognizerSession.stopListening();
401
+ SpeechRecognizer.stopListening();
325
402
 
326
403
  // Manually add time to auto finish timer
327
- RecognizerSession.addAutoFinishTime(5000); // Add 5 seconds
328
- RecognizerSession.addAutoFinishTime(); // Reset to original time
329
-
330
- // Update auto finish time
331
- RecognizerSession.updateAutoFinishTime(10000); // Set to 10 seconds
332
- RecognizerSession.updateAutoFinishTime(10000, true); // Set to 10 seconds and refresh progress
404
+ SpeechRecognizer.addAutoFinishTime(5000); // Add 5 seconds
405
+ SpeechRecognizer.addAutoFinishTime(); // Reset to original time
406
+
407
+ // Update config
408
+ SpeechRecognizer.updateConfig({
409
+ autoFinishRecognitionMs: 10000,
410
+ autoFinishProgressIntervalMs: 200,
411
+ resetAutoFinishVoiceSensitivity: 0.10,
412
+ }, true); // Set to 10 seconds, 200ms interval, 0.10 sensitivity, with reset
333
413
  ```
334
414
 
335
415
  ### ⚠️ About dispose()
336
416
 
337
- The `RecognizerSession.dispose()` method is **NOT SAFE** and should rarely be used. Hybrid Objects in Nitro are typically managed by the JS garbage collector automatically. Only call `dispose()` in performance-critical scenarios where you need to eagerly destroy objects.
417
+ The `SpeechRecognizer.dispose()` method is **NOT SAFE** and should rarely be used. Hybrid Objects in Nitro are typically managed by the JS garbage collector automatically. Only call `dispose()` in performance-critical scenarios where you need to eagerly destroy objects.
338
418
 
339
419
  **See:** [Nitro dispose() documentation](https://nitro.margelo.com/docs/hybrid-objects#dispose)
340
420
 
341
- ## API Reference
342
-
343
- ### `useRecognizer(callbacks, destroyDeps?)`
344
-
345
- #### Usage notes
346
-
347
- - Use `useRecognizer` once per session/screen as the session setup owner.
348
- - Cleanup stops recognition, so mounting multiple instances can unexpectedly end an active session.
349
- - For method access in non-owner components, use `RecognizerRef`.
350
-
351
- #### Parameters
352
-
353
- - `callbacks` (object):
354
- - `onReadyForSpeech?: () => void` - Called when speech recognition starts
355
- - `onResult?: (textBatches: string[]) => void` - Called every time when partial result is ready (array of text batches)
356
- - `onRecordingStopped?: () => void` - Called when recording stops
357
- - `onAutoFinishProgress?: (timeLeftMs: number) => void` - Called each second during auto-finish countdown
358
- - `onError?: (message: string) => void` - Called when an error occurs
359
- - `onPermissionDenied?: () => void` - Called if microphone permission is denied
360
- - `destroyDeps` (array, optional) - Additional dependencies for the cleanup effect. When any of these change (or the component unmounts), recognition is stopped.
361
-
362
- #### Returns
363
-
364
- - `startListening(params: SpeechToTextParams)` - Start speech recognition with the given parameters
365
- - `stopListening()` - Stop speech recognition
366
- - `addAutoFinishTime(additionalTimeMs?: number)` - Add time to the auto-finish timer (or reset to original if no parameter)
367
- - `updateAutoFinishTime(newTimeMs: number, withRefresh?: boolean)` - Update the auto-finish timer
368
- - `getIsActive()` - Returns true if the speech recognition is active
369
-
370
- ### `RecognizerRef`
371
-
372
- - `startListening(params: SpeechToTextParams)`
373
- - `stopListening()`
374
- - `addAutoFinishTime(additionalTimeMs?: number)`
375
- - `updateAutoFinishTime(newTimeMs: number, withRefresh?: boolean)`
376
- - `getIsActive()`
377
-
378
- ### `useVoiceInputVolume`
379
-
380
- - `useVoiceInputVolume(): number`
381
-
382
- ### `RecognizerSession`
383
-
384
- - Exposes callbacks (`onReadyForSpeech`, `onResult`, etc.) and control methods.
385
- - Prefer `useRecognizer` (single owner) + `RecognizerRef` for app-level usage.
386
-
387
- ### `SpeechToTextParams`
388
-
389
- Configuration object for speech recognition.
390
-
391
- #### Common Parameters
392
-
393
- - `locale?: string` - Language locale (default: `"en-US"`)
394
- - `autoFinishRecognitionMs?: number` - Auto-stop timeout in milliseconds (default: `8000`)
395
- - `contextualStrings?: string[]` - Array of domain-specific words for better recognition
396
- - `disableRepeatingFilter?: boolean` - Disable filter that removes consecutive duplicate words (default: `false`)
397
- - `startHapticFeedbackStyle?: 'light' | 'medium' | 'heavy' | 'none'` - Haptic feedback style when microphone starts recording (default: `"medium"`)
398
- - `stopHapticFeedbackStyle?: 'light' | 'medium' | 'heavy' | 'none'` - Haptic feedback style when microphone stops recording (default: `"medium"`)
399
- - `maskOffensiveWords?: boolean` - Mask offensive words with asterisks. (Android 13+, iOS 26+, default: `false`. iOS <26: always `false`)
400
-
401
- #### iOS-Specific Parameters
402
-
403
- - `iosAddPunctuation?: boolean` - Add punctuation to results (iOS 16+, default: `true`)
404
-
405
- #### Android-Specific Parameters
406
-
407
- - `androidFormattingPreferQuality?: boolean` - Prefer quality over latency (Android 13+, default: `false`)
408
- - `androidUseWebSearchModel?: boolean` - Use web search language model instead of free-form (default: `false`)
409
- - `androidDisableBatchHandling?: boolean` - Disable default batch handling (may add many empty batches, default: `false`)
410
-
411
421
  ## Requirements
412
422
 
413
423
  - React Native >= 0.76
414
424
  - New Arch Only
415
425
  - react-native-nitro-modules
416
426
 
427
+ ## Compatibility
428
+
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).
430
+
431
+
432
+ | Compatibility | Supported versions |
433
+ | -------------------------------------- | --------------------------------- |
434
+ | `react-native-nitro-modules <= 0.34.*` | `@gmessier/nitro-speech <= 0.2.*` |
435
+ | `react-native-nitro-modules >= 0.35.*` | `@gmessier/nitro-speech >= 0.3.*` |
436
+
417
437
  ## Troubleshooting
418
438
 
419
439
  ### Android Gradle sync issues
420
440
 
421
- If you're having issues with Android Gradle sync, try running the prebuild for the core Nitro library:
441
+ If you're having issues with Android Gradle sync, try running the prebuild for the library that causes the issue:
442
+
443
+ e.g. failed in `react-native-nitro-modules`:
422
444
 
423
445
  ```bash
424
446
  cd android && ./gradlew :react-native-nitro-modules:preBuild
425
447
  ```
426
448
 
449
+ e.g. failed in `react-native-worklets`:
450
+
451
+ ```bash
452
+ cd android && ./gradlew :react-native-worklets:preBuild
453
+ ```
454
+
427
455
  ## License
428
456
 
429
457
  MIT
@@ -63,7 +63,6 @@ android {
63
63
  }
64
64
  }
65
65
 
66
- consumerProguardFiles 'proguard-rules.pro'
67
66
  }
68
67
 
69
68
  externalNativeBuild {
@@ -1,6 +1,10 @@
1
1
  #include <jni.h>
2
+ #include <fbjni/fbjni.h>
2
3
  #include "NitroSpeechOnLoad.hpp"
3
4
 
4
5
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
- return margelo::nitro::nitrospeech::initialize(vm);
6
+ return facebook::jni::initialize(vm, []() {
7
+ margelo::nitro::nitrospeech::registerAllNatives();
8
+ });
6
9
  }
10
+
@@ -4,6 +4,8 @@ import androidx.annotation.Keep
4
4
  import com.facebook.proguard.annotations.DoNotStrip
5
5
  import com.margelo.nitro.nitrospeech.recognizer.HybridRecognizer
6
6
 
7
+ @DoNotStrip
8
+ @Keep
7
9
  class HybridNitroSpeech: HybridNitroSpeechSpec() {
8
10
 
9
11
  @DoNotStrip