@gmessier/nitro-speech 0.0.7 β†’ 0.1.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 (30) hide show
  1. package/README.md +29 -6
  2. package/android/src/main/AndroidManifest.xml +1 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HapticImpact.kt +66 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HybridRecognizer.kt +11 -0
  5. package/ios/HapticImpact.swift +23 -0
  6. package/ios/HybridRecognizer.swift +28 -10
  7. package/lib/commonjs/index.js +14 -2
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/module/index.js +13 -2
  10. package/lib/module/index.js.map +1 -1
  11. package/lib/tsconfig.tsbuildinfo +1 -1
  12. package/lib/typescript/index.d.ts +12 -1
  13. package/lib/typescript/index.d.ts.map +1 -1
  14. package/lib/typescript/specs/NitroSpeech.nitro.d.ts +13 -0
  15. package/lib/typescript/specs/NitroSpeech.nitro.d.ts.map +1 -1
  16. package/nitrogen/generated/android/c++/JHapticFeedbackStyle.hpp +62 -0
  17. package/nitrogen/generated/android/c++/JHybridRecognizerSpec.cpp +4 -0
  18. package/nitrogen/generated/android/c++/JSpeechToTextParams.hpp +11 -1
  19. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/HapticFeedbackStyle.kt +22 -0
  20. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/SpeechToTextParams.kt +8 -2
  21. package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Bridge.hpp +18 -0
  22. package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Umbrella.hpp +3 -0
  23. package/nitrogen/generated/ios/c++/HybridRecognizerSpecSwift.hpp +3 -0
  24. package/nitrogen/generated/ios/swift/HapticFeedbackStyle.swift +44 -0
  25. package/nitrogen/generated/ios/swift/SpeechToTextParams.swift +47 -1
  26. package/nitrogen/generated/shared/c++/HapticFeedbackStyle.hpp +80 -0
  27. package/nitrogen/generated/shared/c++/SpeechToTextParams.hpp +12 -2
  28. package/package.json +1 -1
  29. package/src/index.ts +14 -2
  30. package/src/specs/NitroSpeech.nitro.ts +14 -0
package/README.md CHANGED
@@ -4,8 +4,6 @@
4
4
  [![license](https://img.shields.io/npm/l/@gmessier/nitro-speech.svg)](https://github.com/NotGeorgeMessier/nitro-speech/blob/main/LICENSE)
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
- > πŸ› [Report Bug](https://github.com/NotGeorgeMessier/nitro-speech/issues) | πŸ’‘ [Request Feature](https://github.com/NotGeorgeMessier/nitro-speech/issues)
8
-
9
7
  > **⚠️ Work in Progress**
10
8
  >
11
9
  > This library is under active development. (Last version is stable)
@@ -19,12 +17,11 @@ Speech recognition for React Native, powered by [Nitro Modules](https://github.c
19
17
  - [Features](#features)
20
18
  - [Usage](#usage)
21
19
  - [Recommended: useRecognizer Hook](#recommended-userecognizer-hook)
20
+ - [With React Navigation (important)](#with-react-navigation-important)
22
21
  - [Alternative: Static Recognizer](#alternative-static-recognizer-not-safe)
23
22
  - [API Reference](#api-reference)
24
23
  - [Requirements](#requirements)
25
24
  - [Troubleshooting](#troubleshooting)
26
- - [License](#license)
27
- - [TODO](#todo)
28
25
 
29
26
  ## Installation
30
27
 
@@ -64,6 +61,7 @@ The library declares the required permission in its `AndroidManifest.xml` (merge
64
61
 
65
62
  ```xml
66
63
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
64
+ <uses-permission android:name="android.permission.VIBRATE" />
67
65
  ```
68
66
 
69
67
  ### iOS
@@ -91,6 +89,7 @@ Both permissions are required for speech recognition to work on iOS.
91
89
  | **Contextual strings** | Domain-specific vocabulary for improved accuracy | βœ… | βœ… |
92
90
  | **Repeating word filter** | Removes consecutive duplicate words from artifacts | βœ… | βœ… |
93
91
  | **Permission handling** | Dedicated `onPermissionDenied` callback | βœ… | βœ… |
92
+ | **Haptic feedback** | Optional haptics on recording start/stop | βœ… | βœ… |
94
93
  | **Automatic punctuation** | Adds punctuation to transcription (iOS 16+) | βœ… | Auto |
95
94
  | **Language model selection** | Choose between web search vs free-form models | Auto | βœ… |
96
95
  | **Offensive word masking** | Control whether offensive words are masked | Auto | βœ… |
@@ -136,6 +135,9 @@ function MyComponent() {
136
135
  locale: 'en-US',
137
136
  autoFinishRecognitionMs: 8000,
138
137
  contextualStrings: ['custom', 'words'],
138
+ // Haptics (both platforms)
139
+ startHapticFeedbackStyle: 'medium',
140
+ stopHapticFeedbackStyle: 'light',
139
141
  // iOS specific
140
142
  iosAddPunctuation: true,
141
143
  // Android specific
@@ -159,6 +161,24 @@ function MyComponent() {
159
161
  }
160
162
  ```
161
163
 
164
+ ### With React Navigation (important)
165
+
166
+ 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).
167
+
168
+ 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).
169
+
170
+ ```typescript
171
+ const isFocused = useIsFocused();
172
+ const {
173
+ // ...
174
+ } = useRecognizer(
175
+ {
176
+ // ...
177
+ },
178
+ [isFocused]
179
+ );
180
+ ```
181
+
162
182
  ### Alternative: Static Recognizer (Not Safe)
163
183
 
164
184
  ```typescript
@@ -214,7 +234,7 @@ The `Recognizer.dispose()` method is **NOT SAFE** and should rarely be used. Hyb
214
234
 
215
235
  ## API Reference
216
236
 
217
- ### `useRecognizer(callbacks)`
237
+ ### `useRecognizer(callbacks, destroyDeps?)`
218
238
 
219
239
  A React hook that provides lifecycle-aware access to the speech recognizer.
220
240
 
@@ -222,11 +242,12 @@ A React hook that provides lifecycle-aware access to the speech recognizer.
222
242
 
223
243
  - `callbacks` (object):
224
244
  - `onReadyForSpeech?: () => void` - Called when speech recognition starts
225
- - `onResult?: (textBatches: string[]) => void` - Called with transcription results (array of text batches)
245
+ - `onResult?: (textBatches: string[]) => void` - Called every time when partial result is ready (array of text batches)
226
246
  - `onRecordingStopped?: () => void` - Called when recording stops
227
247
  - `onAutoFinishProgress?: (timeLeftMs: number) => void` - Called each second during auto-finish countdown
228
248
  - `onError?: (message: string) => void` - Called when an error occurs
229
249
  - `onPermissionDenied?: () => void` - Called if microphone permission is denied
250
+ - `destroyDeps` (array, optional) - Additional dependencies for the cleanup effect. When any of these change (or the component unmounts), recognition is stopped.
230
251
 
231
252
  #### Returns
232
253
 
@@ -245,6 +266,8 @@ Configuration object for speech recognition.
245
266
  - `autoFinishRecognitionMs?: number` - Auto-stop timeout in milliseconds (default: `8000`)
246
267
  - `contextualStrings?: string[]` - Array of domain-specific words for better recognition
247
268
  - `disableRepeatingFilter?: boolean` - Disable filter that removes consecutive duplicate words (default: `false`)
269
+ - `startHapticFeedbackStyle?: 'light' | 'medium' | 'heavy'` - Haptic feedback style when microphone starts recording (default: `null` / disabled)
270
+ - `stopHapticFeedbackStyle?: 'light' | 'medium' | 'heavy'` - Haptic feedback style when microphone stops recording (default: `null` / disabled)
248
271
 
249
272
  #### iOS-Specific Parameters
250
273
 
@@ -1,3 +1,4 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
2
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
3
+ <uses-permission android:name="android.permission.VIBRATE" />
3
4
  </manifest>
@@ -0,0 +1,66 @@
1
+ package com.margelo.nitro.nitrospeech.recognizer
2
+
3
+ import android.content.Context
4
+ import android.os.Build
5
+ import android.os.VibrationEffect
6
+ import android.os.Vibrator
7
+ import android.os.VibratorManager
8
+ import com.margelo.nitro.nitrospeech.HapticFeedbackStyle
9
+
10
+ class HapticImpact(
11
+ private val style: HapticFeedbackStyle = HapticFeedbackStyle.MEDIUM,
12
+ ) {
13
+ private data class LegacyOneShot(
14
+ val durationMs: Long,
15
+ val amplitude: Int,
16
+ )
17
+
18
+ fun trigger(context: Context) {
19
+ val vibrator = getVibrator(context) ?: return
20
+ if (!vibrator.hasVibrator()) return
21
+
22
+ try {
23
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
24
+ val effect = when (style) {
25
+ HapticFeedbackStyle.LIGHT -> VibrationEffect.EFFECT_TICK
26
+ HapticFeedbackStyle.MEDIUM -> VibrationEffect.EFFECT_CLICK
27
+ HapticFeedbackStyle.HEAVY -> VibrationEffect.EFFECT_HEAVY_CLICK
28
+ }
29
+ vibrator.vibrate(VibrationEffect.createPredefined(effect))
30
+ return
31
+ }
32
+
33
+ val legacyOneShot = when (style) {
34
+ HapticFeedbackStyle.LIGHT -> LegacyOneShot(durationMs = 12L, amplitude = 50)
35
+ HapticFeedbackStyle.MEDIUM -> LegacyOneShot(durationMs = 18L, amplitude = 100)
36
+ HapticFeedbackStyle.HEAVY -> LegacyOneShot(durationMs = 28L, amplitude = 180)
37
+ }
38
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
39
+ vibrator.vibrate(
40
+ VibrationEffect.createOneShot(
41
+ legacyOneShot.durationMs,
42
+ legacyOneShot.amplitude
43
+ )
44
+ )
45
+ } else {
46
+ @Suppress("DEPRECATION")
47
+ vibrator.vibrate(legacyOneShot.durationMs)
48
+ }
49
+ } catch (_: SecurityException) {
50
+ // Missing android.permission.VIBRATE or disallowed by device policy.
51
+ } catch (_: Throwable) {
52
+ // Never crash the recognition flow because of haptics.
53
+ }
54
+ }
55
+
56
+ private fun getVibrator(context: Context): Vibrator? {
57
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
58
+ val manager =
59
+ context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as? VibratorManager
60
+ manager?.defaultVibrator
61
+ } else {
62
+ @Suppress("DEPRECATION")
63
+ context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
64
+ }
65
+ }
66
+ }
@@ -84,6 +84,11 @@ class HybridRecognizer: HybridRecognizerSpec() {
84
84
  if (!isActive) return
85
85
  onFinishRecognition(null, null, true)
86
86
  mainHandler.postDelayed({
87
+ val context = NitroModules.applicationContext
88
+ val hapticImpact = config?.stopHapticFeedbackStyle
89
+ if (hapticImpact != null && context != null) {
90
+ HapticImpact(hapticImpact).trigger(context)
91
+ }
87
92
  cleanup()
88
93
  }, POST_RECOGNITION_DELAY)
89
94
  }
@@ -156,6 +161,12 @@ class HybridRecognizer: HybridRecognizerSpec() {
156
161
 
157
162
  speechRecognizer?.startListening(intent)
158
163
  isActive = true
164
+
165
+ val hapticImpact = config?.startHapticFeedbackStyle
166
+ if (hapticImpact != null) {
167
+ HapticImpact(hapticImpact).trigger(context)
168
+ }
169
+
159
170
  mainHandler.postDelayed({
160
171
  if (isActive) {
161
172
  onReadyForSpeech?.invoke()
@@ -0,0 +1,23 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ class HapticImpact {
5
+ private let impactGenerator: UIImpactFeedbackGenerator
6
+
7
+ init(style: HapticFeedbackStyle) {
8
+ let hapticStyle = switch style {
9
+ case .light:
10
+ UIImpactFeedbackGenerator.FeedbackStyle.light
11
+ case .medium:
12
+ UIImpactFeedbackGenerator.FeedbackStyle.medium
13
+ case .heavy:
14
+ UIImpactFeedbackGenerator.FeedbackStyle.heavy
15
+ }
16
+ self.impactGenerator = UIImpactFeedbackGenerator(style: hapticStyle)
17
+ }
18
+
19
+ func trigger() {
20
+ impactGenerator.prepare()
21
+ impactGenerator.impactOccurred()
22
+ }
23
+ }
@@ -1,8 +1,10 @@
1
1
  import Foundation
2
2
  import Speech
3
3
  import NitroModules
4
+ import os.log
4
5
 
5
6
  class HybridRecognizer: HybridRecognizerSpec {
7
+ private let logger = Logger(subsystem: "com.margelo.nitro.nitrospeech", category: "AutoStopper")
6
8
  private static let defaultAutoFinishRecognitionMs = 8000.0
7
9
 
8
10
  var onReadyForSpeech: (() -> Void)?
@@ -19,10 +21,11 @@ class HybridRecognizer: HybridRecognizerSpec {
19
21
  private var appStateObserver: AppStateObserver?
20
22
  private var isActive: Bool = false
21
23
  private var isStopping: Bool = false
24
+ private var config: SpeechToTextParams?
22
25
 
23
26
  func startListening(params: SpeechToTextParams) {
24
27
  if isActive {
25
- onError?("Previous recognition session is still active")
28
+ // Previous recognition session is still active
26
29
  return
27
30
  }
28
31
 
@@ -30,9 +33,11 @@ class HybridRecognizer: HybridRecognizerSpec {
30
33
  DispatchQueue.main.async {
31
34
  guard let self = self else { return }
32
35
 
36
+ self.config = params
37
+
33
38
  switch authStatus {
34
39
  case .authorized:
35
- self.requestMicrophonePermission(params: params)
40
+ self.requestMicrophonePermission()
36
41
  case .denied, .restricted:
37
42
  self.onPermissionDenied?()
38
43
  case .notDetermined:
@@ -48,6 +53,10 @@ class HybridRecognizer: HybridRecognizerSpec {
48
53
  guard isActive, !isStopping else { return }
49
54
  isStopping = true
50
55
 
56
+ if let hapticStyle = config?.stopHapticFeedbackStyle {
57
+ HapticImpact(style: hapticStyle).trigger()
58
+ }
59
+
51
60
  // Signal end of audio and request graceful finish
52
61
  recognitionRequest?.endAudio()
53
62
  recognitionTask?.finish()
@@ -79,13 +88,13 @@ class HybridRecognizer: HybridRecognizerSpec {
79
88
  stopListening()
80
89
  }
81
90
 
82
- private func requestMicrophonePermission(params: SpeechToTextParams) {
91
+ private func requestMicrophonePermission() {
83
92
  AVAudioSession.sharedInstance().requestRecordPermission { [weak self] granted in
84
93
  DispatchQueue.main.async {
85
94
  guard let self = self else { return }
86
95
 
87
96
  if granted {
88
- self.startRecognition(params: params)
97
+ self.startRecognition()
89
98
  } else {
90
99
  self.onPermissionDenied?()
91
100
  }
@@ -93,17 +102,17 @@ class HybridRecognizer: HybridRecognizerSpec {
93
102
  }
94
103
  }
95
104
 
96
- private func startRecognition(params: SpeechToTextParams) {
105
+ private func startRecognition() {
97
106
  isStopping = false
98
107
 
99
- let locale = Locale(identifier: params.locale ?? "en-US")
108
+ let locale = Locale(identifier: config?.locale ?? "en-US")
100
109
  guard let speechRecognizer = SFSpeechRecognizer(locale: locale), speechRecognizer.isAvailable else {
101
110
  onError?("Speech recognizer not available")
102
111
  return
103
112
  }
104
113
 
105
114
  autoStopper = AutoStopper(
106
- silenceThresholdMs: params.autoFinishRecognitionMs ?? Self.defaultAutoFinishRecognitionMs,
115
+ silenceThresholdMs: config?.autoFinishRecognitionMs ?? Self.defaultAutoFinishRecognitionMs,
107
116
  onProgress: { [weak self] timeLeftMs in
108
117
  self?.onAutoFinishProgress?(timeLeftMs)
109
118
  },
@@ -115,6 +124,10 @@ class HybridRecognizer: HybridRecognizerSpec {
115
124
  do {
116
125
  let audioSession = AVAudioSession.sharedInstance()
117
126
  try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
127
+ if #available(iOS 13.0, *) {
128
+ // Without this, iOS may suppress haptics while recording.
129
+ try audioSession.setAllowHapticsAndSystemSoundsDuringRecording(true)
130
+ }
118
131
  try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
119
132
  } catch {
120
133
  onError?("Failed to set up audio session: \(error.localizedDescription)")
@@ -131,19 +144,19 @@ class HybridRecognizer: HybridRecognizerSpec {
131
144
 
132
145
  recognitionRequest.shouldReportPartialResults = true
133
146
 
134
- if let contextualStrings = params.contextualStrings, !contextualStrings.isEmpty {
147
+ if let contextualStrings = config?.contextualStrings, !contextualStrings.isEmpty {
135
148
  recognitionRequest.contextualStrings = contextualStrings
136
149
  }
137
150
 
138
151
  if #available(iOS 16, *) {
139
- if let addPunctiation = params.iosAddPunctuation, addPunctiation == false {
152
+ if let addPunctiation = config?.iosAddPunctuation, addPunctiation == false {
140
153
  recognitionRequest.addsPunctuation = false
141
154
  } else {
142
155
  recognitionRequest.addsPunctuation = true
143
156
  }
144
157
  }
145
158
 
146
- let disableRepeatingFilter = params.disableRepeatingFilter ?? false
159
+ let disableRepeatingFilter = config?.disableRepeatingFilter ?? false
147
160
 
148
161
  recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
149
162
  guard let self = self else { return }
@@ -197,6 +210,11 @@ class HybridRecognizer: HybridRecognizerSpec {
197
210
  audioEngine.prepare()
198
211
  try audioEngine.start()
199
212
  isActive = true
213
+
214
+ if let hapticStyle = config?.startHapticFeedbackStyle {
215
+ HapticImpact(style: hapticStyle).trigger()
216
+ }
217
+
200
218
  autoStopper?.indicateRecordingActivity(
201
219
  from: "startListening",
202
220
  addMsToThreshold: nil
@@ -7,6 +7,8 @@ exports.useRecognizer = exports.Recognizer = void 0;
7
7
  var _react = _interopRequireDefault(require("react"));
8
8
  var _reactNativeNitroModules = require("react-native-nitro-modules");
9
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ /* eslint-disable react-hooks/exhaustive-deps */
11
+
10
12
  const NitroSpeech = _reactNativeNitroModules.NitroModules.createHybridObject('NitroSpeech');
11
13
 
12
14
  /**
@@ -28,8 +30,18 @@ const recognizerUpdateAutoFinishTime = (newTimeMs, withRefresh) => {
28
30
 
29
31
  /**
30
32
  * Safe, lifecycle-aware hook to use the recognizer.
33
+ *
34
+ * @param callbacks - The callbacks to use for the recognizer.
35
+ * @param destroyDeps - The additional dependencies to use for the cleanup effect.
36
+ *
37
+ * Example: To cleanup when the screen is unfocused.
38
+ *
39
+ * ```typescript
40
+ * const isFocused = useIsFocused()
41
+ * useRecognizer({ ... }, [isFocused])
42
+ * ```
31
43
  */
32
- const useRecognizer = callbacks => {
44
+ const useRecognizer = (callbacks, destroyDeps = []) => {
33
45
  _react.default.useEffect(() => {
34
46
  Recognizer.onReadyForSpeech = () => {
35
47
  callbacks.onReadyForSpeech?.();
@@ -62,7 +74,7 @@ const useRecognizer = callbacks => {
62
74
  return () => {
63
75
  Recognizer.stopListening();
64
76
  };
65
- }, []);
77
+ }, [...destroyDeps]);
66
78
  return {
67
79
  startListening: recognizerStartListening,
68
80
  stopListening: recognizerStopListening,
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireDefault","require","_reactNativeNitroModules","e","__esModule","default","NitroSpeech","NitroModules","createHybridObject","Recognizer","exports","recognizer","recognizerStartListening","params","startListening","recognizerStopListening","stopListening","recognizerAddAutoFinishTime","additionalTimeMs","addAutoFinishTime","recognizerUpdateAutoFinishTime","newTimeMs","withRefresh","updateAutoFinishTime","useRecognizer","callbacks","React","useEffect","onReadyForSpeech","onRecordingStopped","onResult","resultBatches","onAutoFinishProgress","timeLeftMs","onError","message","onPermissionDenied","undefined"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,wBAAA,GAAAD,OAAA;AAAyD,SAAAD,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAOzD,MAAMG,WAAW,GACfC,qCAAY,CAACC,kBAAkB,CAAkB,aAAa,CAAC;;AAEjE;AACA;AACA;AACO,MAAMC,UAAU,GAAAC,OAAA,CAAAD,UAAA,GAAGH,WAAW,CAACK,UAAU;AAoBhD,MAAMC,wBAAwB,GAAIC,MAA0B,IAAK;EAC/DJ,UAAU,CAACK,cAAc,CAACD,MAAM,CAAC;AACnC,CAAC;AAED,MAAME,uBAAuB,GAAGA,CAAA,KAAM;EACpCN,UAAU,CAACO,aAAa,CAAC,CAAC;AAC5B,CAAC;AAED,MAAMC,2BAA2B,GAAIC,gBAAyB,IAAK;EACjET,UAAU,CAACU,iBAAiB,CAACD,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAME,8BAA8B,GAAGA,CACrCC,SAAiB,EACjBC,WAAqB,KAClB;EACHb,UAAU,CAACc,oBAAoB,CAACF,SAAS,EAAEC,WAAW,CAAC;AACzD,CAAC;;AAED;AACA;AACA;AACO,MAAME,aAAa,GACxBC,SAA8B,IACP;EACvBC,cAAK,CAACC,SAAS,CAAC,MAAM;IACpBlB,UAAU,CAACmB,gBAAgB,GAAG,MAAM;MAClCH,SAAS,CAACG,gBAAgB,GAAG,CAAC;IAChC,CAAC;IACDnB,UAAU,CAACoB,kBAAkB,GAAG,MAAM;MACpCJ,SAAS,CAACI,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACDpB,UAAU,CAACqB,QAAQ,GAAIC,aAAuB,IAAK;MACjDN,SAAS,CAACK,QAAQ,GAAGC,aAAa,CAAC;IACrC,CAAC;IACDtB,UAAU,CAACuB,oBAAoB,GAAIC,UAAkB,IAAK;MACxDR,SAAS,CAACO,oBAAoB,GAAGC,UAAU,CAAC;IAC9C,CAAC;IACDxB,UAAU,CAACyB,OAAO,GAAIC,OAAe,IAAK;MACxCV,SAAS,CAACS,OAAO,GAAGC,OAAO,CAAC;IAC9B,CAAC;IACD1B,UAAU,CAAC2B,kBAAkB,GAAG,MAAM;MACpCX,SAAS,CAACW,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACD,OAAO,MAAM;MACX3B,UAAU,CAACmB,gBAAgB,GAAGS,SAAS;MACvC5B,UAAU,CAACoB,kBAAkB,GAAGQ,SAAS;MACzC5B,UAAU,CAACqB,QAAQ,GAAGO,SAAS;MAC/B5B,UAAU,CAACuB,oBAAoB,GAAGK,SAAS;MAC3C5B,UAAU,CAACyB,OAAO,GAAGG,SAAS;MAC9B5B,UAAU,CAAC2B,kBAAkB,GAAGC,SAAS;IAC3C,CAAC;EACH,CAAC,EAAE,CAACZ,SAAS,CAAC,CAAC;EAEfC,cAAK,CAACC,SAAS,CAAC,MAAM;IACpB,OAAO,MAAM;MACXlB,UAAU,CAACO,aAAa,CAAC,CAAC;IAC5B,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,OAAO;IACLF,cAAc,EAAEF,wBAAwB;IACxCI,aAAa,EAAED,uBAAuB;IACtCI,iBAAiB,EAAEF,2BAA2B;IAC9CM,oBAAoB,EAAEH;EACxB,CAAC;AACH,CAAC;AAAAV,OAAA,CAAAc,aAAA,GAAAA,aAAA","ignoreList":[]}
1
+ {"version":3,"names":["_react","_interopRequireDefault","require","_reactNativeNitroModules","e","__esModule","default","NitroSpeech","NitroModules","createHybridObject","Recognizer","exports","recognizer","recognizerStartListening","params","startListening","recognizerStopListening","stopListening","recognizerAddAutoFinishTime","additionalTimeMs","addAutoFinishTime","recognizerUpdateAutoFinishTime","newTimeMs","withRefresh","updateAutoFinishTime","useRecognizer","callbacks","destroyDeps","React","useEffect","onReadyForSpeech","onRecordingStopped","onResult","resultBatches","onAutoFinishProgress","timeLeftMs","onError","message","onPermissionDenied","undefined"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AACA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,wBAAA,GAAAD,OAAA;AAAyD,SAAAD,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAFzD;;AASA,MAAMG,WAAW,GACfC,qCAAY,CAACC,kBAAkB,CAAkB,aAAa,CAAC;;AAEjE;AACA;AACA;AACO,MAAMC,UAAU,GAAAC,OAAA,CAAAD,UAAA,GAAGH,WAAW,CAACK,UAAU;AAoBhD,MAAMC,wBAAwB,GAAIC,MAA0B,IAAK;EAC/DJ,UAAU,CAACK,cAAc,CAACD,MAAM,CAAC;AACnC,CAAC;AAED,MAAME,uBAAuB,GAAGA,CAAA,KAAM;EACpCN,UAAU,CAACO,aAAa,CAAC,CAAC;AAC5B,CAAC;AAED,MAAMC,2BAA2B,GAAIC,gBAAyB,IAAK;EACjET,UAAU,CAACU,iBAAiB,CAACD,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAME,8BAA8B,GAAGA,CACrCC,SAAiB,EACjBC,WAAqB,KAClB;EACHb,UAAU,CAACc,oBAAoB,CAACF,SAAS,EAAEC,WAAW,CAAC;AACzD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAME,aAAa,GAAGA,CAC3BC,SAA8B,EAC9BC,WAAiC,GAAG,EAAE,KACf;EACvBC,cAAK,CAACC,SAAS,CAAC,MAAM;IACpBnB,UAAU,CAACoB,gBAAgB,GAAG,MAAM;MAClCJ,SAAS,CAACI,gBAAgB,GAAG,CAAC;IAChC,CAAC;IACDpB,UAAU,CAACqB,kBAAkB,GAAG,MAAM;MACpCL,SAAS,CAACK,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACDrB,UAAU,CAACsB,QAAQ,GAAIC,aAAuB,IAAK;MACjDP,SAAS,CAACM,QAAQ,GAAGC,aAAa,CAAC;IACrC,CAAC;IACDvB,UAAU,CAACwB,oBAAoB,GAAIC,UAAkB,IAAK;MACxDT,SAAS,CAACQ,oBAAoB,GAAGC,UAAU,CAAC;IAC9C,CAAC;IACDzB,UAAU,CAAC0B,OAAO,GAAIC,OAAe,IAAK;MACxCX,SAAS,CAACU,OAAO,GAAGC,OAAO,CAAC;IAC9B,CAAC;IACD3B,UAAU,CAAC4B,kBAAkB,GAAG,MAAM;MACpCZ,SAAS,CAACY,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACD,OAAO,MAAM;MACX5B,UAAU,CAACoB,gBAAgB,GAAGS,SAAS;MACvC7B,UAAU,CAACqB,kBAAkB,GAAGQ,SAAS;MACzC7B,UAAU,CAACsB,QAAQ,GAAGO,SAAS;MAC/B7B,UAAU,CAACwB,oBAAoB,GAAGK,SAAS;MAC3C7B,UAAU,CAAC0B,OAAO,GAAGG,SAAS;MAC9B7B,UAAU,CAAC4B,kBAAkB,GAAGC,SAAS;IAC3C,CAAC;EACH,CAAC,EAAE,CAACb,SAAS,CAAC,CAAC;EAEfE,cAAK,CAACC,SAAS,CAAC,MAAM;IACpB,OAAO,MAAM;MACXnB,UAAU,CAACO,aAAa,CAAC,CAAC;IAC5B,CAAC;EACH,CAAC,EAAE,CAAC,GAAGU,WAAW,CAAC,CAAC;EAEpB,OAAO;IACLZ,cAAc,EAAEF,wBAAwB;IACxCI,aAAa,EAAED,uBAAuB;IACtCI,iBAAiB,EAAEF,2BAA2B;IAC9CM,oBAAoB,EAAEH;EACxB,CAAC;AACH,CAAC;AAAAV,OAAA,CAAAc,aAAA,GAAAA,aAAA","ignoreList":[]}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ /* eslint-disable react-hooks/exhaustive-deps */
3
4
  import React from 'react';
4
5
  import { NitroModules } from 'react-native-nitro-modules';
5
6
  const NitroSpeech = NitroModules.createHybridObject('NitroSpeech');
@@ -23,8 +24,18 @@ const recognizerUpdateAutoFinishTime = (newTimeMs, withRefresh) => {
23
24
 
24
25
  /**
25
26
  * Safe, lifecycle-aware hook to use the recognizer.
27
+ *
28
+ * @param callbacks - The callbacks to use for the recognizer.
29
+ * @param destroyDeps - The additional dependencies to use for the cleanup effect.
30
+ *
31
+ * Example: To cleanup when the screen is unfocused.
32
+ *
33
+ * ```typescript
34
+ * const isFocused = useIsFocused()
35
+ * useRecognizer({ ... }, [isFocused])
36
+ * ```
26
37
  */
27
- export const useRecognizer = callbacks => {
38
+ export const useRecognizer = (callbacks, destroyDeps = []) => {
28
39
  React.useEffect(() => {
29
40
  Recognizer.onReadyForSpeech = () => {
30
41
  callbacks.onReadyForSpeech?.();
@@ -57,7 +68,7 @@ export const useRecognizer = callbacks => {
57
68
  return () => {
58
69
  Recognizer.stopListening();
59
70
  };
60
- }, []);
71
+ }, [...destroyDeps]);
61
72
  return {
62
73
  startListening: recognizerStartListening,
63
74
  stopListening: recognizerStopListening,
@@ -1 +1 @@
1
- {"version":3,"names":["React","NitroModules","NitroSpeech","createHybridObject","Recognizer","recognizer","recognizerStartListening","params","startListening","recognizerStopListening","stopListening","recognizerAddAutoFinishTime","additionalTimeMs","addAutoFinishTime","recognizerUpdateAutoFinishTime","newTimeMs","withRefresh","updateAutoFinishTime","useRecognizer","callbacks","useEffect","onReadyForSpeech","onRecordingStopped","onResult","resultBatches","onAutoFinishProgress","timeLeftMs","onError","message","onPermissionDenied","undefined"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,YAAY,QAAQ,4BAA4B;AAOzD,MAAMC,WAAW,GACfD,YAAY,CAACE,kBAAkB,CAAkB,aAAa,CAAC;;AAEjE;AACA;AACA;AACA,OAAO,MAAMC,UAAU,GAAGF,WAAW,CAACG,UAAU;AAoBhD,MAAMC,wBAAwB,GAAIC,MAA0B,IAAK;EAC/DH,UAAU,CAACI,cAAc,CAACD,MAAM,CAAC;AACnC,CAAC;AAED,MAAME,uBAAuB,GAAGA,CAAA,KAAM;EACpCL,UAAU,CAACM,aAAa,CAAC,CAAC;AAC5B,CAAC;AAED,MAAMC,2BAA2B,GAAIC,gBAAyB,IAAK;EACjER,UAAU,CAACS,iBAAiB,CAACD,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAME,8BAA8B,GAAGA,CACrCC,SAAiB,EACjBC,WAAqB,KAClB;EACHZ,UAAU,CAACa,oBAAoB,CAACF,SAAS,EAAEC,WAAW,CAAC;AACzD,CAAC;;AAED;AACA;AACA;AACA,OAAO,MAAME,aAAa,GACxBC,SAA8B,IACP;EACvBnB,KAAK,CAACoB,SAAS,CAAC,MAAM;IACpBhB,UAAU,CAACiB,gBAAgB,GAAG,MAAM;MAClCF,SAAS,CAACE,gBAAgB,GAAG,CAAC;IAChC,CAAC;IACDjB,UAAU,CAACkB,kBAAkB,GAAG,MAAM;MACpCH,SAAS,CAACG,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACDlB,UAAU,CAACmB,QAAQ,GAAIC,aAAuB,IAAK;MACjDL,SAAS,CAACI,QAAQ,GAAGC,aAAa,CAAC;IACrC,CAAC;IACDpB,UAAU,CAACqB,oBAAoB,GAAIC,UAAkB,IAAK;MACxDP,SAAS,CAACM,oBAAoB,GAAGC,UAAU,CAAC;IAC9C,CAAC;IACDtB,UAAU,CAACuB,OAAO,GAAIC,OAAe,IAAK;MACxCT,SAAS,CAACQ,OAAO,GAAGC,OAAO,CAAC;IAC9B,CAAC;IACDxB,UAAU,CAACyB,kBAAkB,GAAG,MAAM;MACpCV,SAAS,CAACU,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACD,OAAO,MAAM;MACXzB,UAAU,CAACiB,gBAAgB,GAAGS,SAAS;MACvC1B,UAAU,CAACkB,kBAAkB,GAAGQ,SAAS;MACzC1B,UAAU,CAACmB,QAAQ,GAAGO,SAAS;MAC/B1B,UAAU,CAACqB,oBAAoB,GAAGK,SAAS;MAC3C1B,UAAU,CAACuB,OAAO,GAAGG,SAAS;MAC9B1B,UAAU,CAACyB,kBAAkB,GAAGC,SAAS;IAC3C,CAAC;EACH,CAAC,EAAE,CAACX,SAAS,CAAC,CAAC;EAEfnB,KAAK,CAACoB,SAAS,CAAC,MAAM;IACpB,OAAO,MAAM;MACXhB,UAAU,CAACM,aAAa,CAAC,CAAC;IAC5B,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,OAAO;IACLF,cAAc,EAAEF,wBAAwB;IACxCI,aAAa,EAAED,uBAAuB;IACtCI,iBAAiB,EAAEF,2BAA2B;IAC9CM,oBAAoB,EAAEH;EACxB,CAAC;AACH,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","NitroModules","NitroSpeech","createHybridObject","Recognizer","recognizer","recognizerStartListening","params","startListening","recognizerStopListening","stopListening","recognizerAddAutoFinishTime","additionalTimeMs","addAutoFinishTime","recognizerUpdateAutoFinishTime","newTimeMs","withRefresh","updateAutoFinishTime","useRecognizer","callbacks","destroyDeps","useEffect","onReadyForSpeech","onRecordingStopped","onResult","resultBatches","onAutoFinishProgress","timeLeftMs","onError","message","onPermissionDenied","undefined"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA;AACA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,YAAY,QAAQ,4BAA4B;AAOzD,MAAMC,WAAW,GACfD,YAAY,CAACE,kBAAkB,CAAkB,aAAa,CAAC;;AAEjE;AACA;AACA;AACA,OAAO,MAAMC,UAAU,GAAGF,WAAW,CAACG,UAAU;AAoBhD,MAAMC,wBAAwB,GAAIC,MAA0B,IAAK;EAC/DH,UAAU,CAACI,cAAc,CAACD,MAAM,CAAC;AACnC,CAAC;AAED,MAAME,uBAAuB,GAAGA,CAAA,KAAM;EACpCL,UAAU,CAACM,aAAa,CAAC,CAAC;AAC5B,CAAC;AAED,MAAMC,2BAA2B,GAAIC,gBAAyB,IAAK;EACjER,UAAU,CAACS,iBAAiB,CAACD,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAME,8BAA8B,GAAGA,CACrCC,SAAiB,EACjBC,WAAqB,KAClB;EACHZ,UAAU,CAACa,oBAAoB,CAACF,SAAS,EAAEC,WAAW,CAAC;AACzD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,aAAa,GAAGA,CAC3BC,SAA8B,EAC9BC,WAAiC,GAAG,EAAE,KACf;EACvBpB,KAAK,CAACqB,SAAS,CAAC,MAAM;IACpBjB,UAAU,CAACkB,gBAAgB,GAAG,MAAM;MAClCH,SAAS,CAACG,gBAAgB,GAAG,CAAC;IAChC,CAAC;IACDlB,UAAU,CAACmB,kBAAkB,GAAG,MAAM;MACpCJ,SAAS,CAACI,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACDnB,UAAU,CAACoB,QAAQ,GAAIC,aAAuB,IAAK;MACjDN,SAAS,CAACK,QAAQ,GAAGC,aAAa,CAAC;IACrC,CAAC;IACDrB,UAAU,CAACsB,oBAAoB,GAAIC,UAAkB,IAAK;MACxDR,SAAS,CAACO,oBAAoB,GAAGC,UAAU,CAAC;IAC9C,CAAC;IACDvB,UAAU,CAACwB,OAAO,GAAIC,OAAe,IAAK;MACxCV,SAAS,CAACS,OAAO,GAAGC,OAAO,CAAC;IAC9B,CAAC;IACDzB,UAAU,CAAC0B,kBAAkB,GAAG,MAAM;MACpCX,SAAS,CAACW,kBAAkB,GAAG,CAAC;IAClC,CAAC;IACD,OAAO,MAAM;MACX1B,UAAU,CAACkB,gBAAgB,GAAGS,SAAS;MACvC3B,UAAU,CAACmB,kBAAkB,GAAGQ,SAAS;MACzC3B,UAAU,CAACoB,QAAQ,GAAGO,SAAS;MAC/B3B,UAAU,CAACsB,oBAAoB,GAAGK,SAAS;MAC3C3B,UAAU,CAACwB,OAAO,GAAGG,SAAS;MAC9B3B,UAAU,CAAC0B,kBAAkB,GAAGC,SAAS;IAC3C,CAAC;EACH,CAAC,EAAE,CAACZ,SAAS,CAAC,CAAC;EAEfnB,KAAK,CAACqB,SAAS,CAAC,MAAM;IACpB,OAAO,MAAM;MACXjB,UAAU,CAACM,aAAa,CAAC,CAAC;IAC5B,CAAC;EACH,CAAC,EAAE,CAAC,GAAGU,WAAW,CAAC,CAAC;EAEpB,OAAO;IACLZ,cAAc,EAAEF,wBAAwB;IACxCI,aAAa,EAAED,uBAAuB;IACtCI,iBAAiB,EAAEF,2BAA2B;IAC9CM,oBAAoB,EAAEH;EACxB,CAAC;AACH,CAAC","ignoreList":[]}