@gmessier/nitro-speech 0.1.0 → 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 +28 -1
  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
@@ -17,6 +17,7 @@ Speech recognition for React Native, powered by [Nitro Modules](https://github.c
17
17
  - [Features](#features)
18
18
  - [Usage](#usage)
19
19
  - [Recommended: useRecognizer Hook](#recommended-userecognizer-hook)
20
+ - [With React Navigation (important)](#with-react-navigation-important)
20
21
  - [Alternative: Static Recognizer](#alternative-static-recognizer-not-safe)
21
22
  - [API Reference](#api-reference)
22
23
  - [Requirements](#requirements)
@@ -60,6 +61,7 @@ The library declares the required permission in its `AndroidManifest.xml` (merge
60
61
 
61
62
  ```xml
62
63
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
64
+ <uses-permission android:name="android.permission.VIBRATE" />
63
65
  ```
64
66
 
65
67
  ### iOS
@@ -87,6 +89,7 @@ Both permissions are required for speech recognition to work on iOS.
87
89
  | **Contextual strings** | Domain-specific vocabulary for improved accuracy | ✅ | ✅ |
88
90
  | **Repeating word filter** | Removes consecutive duplicate words from artifacts | ✅ | ✅ |
89
91
  | **Permission handling** | Dedicated `onPermissionDenied` callback | ✅ | ✅ |
92
+ | **Haptic feedback** | Optional haptics on recording start/stop | ✅ | ✅ |
90
93
  | **Automatic punctuation** | Adds punctuation to transcription (iOS 16+) | ✅ | Auto |
91
94
  | **Language model selection** | Choose between web search vs free-form models | Auto | ✅ |
92
95
  | **Offensive word masking** | Control whether offensive words are masked | Auto | ✅ |
@@ -132,6 +135,9 @@ function MyComponent() {
132
135
  locale: 'en-US',
133
136
  autoFinishRecognitionMs: 8000,
134
137
  contextualStrings: ['custom', 'words'],
138
+ // Haptics (both platforms)
139
+ startHapticFeedbackStyle: 'medium',
140
+ stopHapticFeedbackStyle: 'light',
135
141
  // iOS specific
136
142
  iosAddPunctuation: true,
137
143
  // Android specific
@@ -155,6 +161,24 @@ function MyComponent() {
155
161
  }
156
162
  ```
157
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
+
158
182
  ### Alternative: Static Recognizer (Not Safe)
159
183
 
160
184
  ```typescript
@@ -210,7 +234,7 @@ The `Recognizer.dispose()` method is **NOT SAFE** and should rarely be used. Hyb
210
234
 
211
235
  ## API Reference
212
236
 
213
- ### `useRecognizer(callbacks)`
237
+ ### `useRecognizer(callbacks, destroyDeps?)`
214
238
 
215
239
  A React hook that provides lifecycle-aware access to the speech recognizer.
216
240
 
@@ -223,6 +247,7 @@ A React hook that provides lifecycle-aware access to the speech recognizer.
223
247
  - `onAutoFinishProgress?: (timeLeftMs: number) => void` - Called each second during auto-finish countdown
224
248
  - `onError?: (message: string) => void` - Called when an error occurs
225
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.
226
251
 
227
252
  #### Returns
228
253
 
@@ -241,6 +266,8 @@ Configuration object for speech recognition.
241
266
  - `autoFinishRecognitionMs?: number` - Auto-stop timeout in milliseconds (default: `8000`)
242
267
  - `contextualStrings?: string[]` - Array of domain-specific words for better recognition
243
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)
244
271
 
245
272
  #### iOS-Specific Parameters
246
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":[]}