@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.
- package/README.md +29 -6
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HapticImpact.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/nitrospeech/recognizer/HybridRecognizer.kt +11 -0
- package/ios/HapticImpact.swift +23 -0
- package/ios/HybridRecognizer.swift +28 -10
- package/lib/commonjs/index.js +14 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +13 -2
- package/lib/module/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/specs/NitroSpeech.nitro.d.ts +13 -0
- package/lib/typescript/specs/NitroSpeech.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHapticFeedbackStyle.hpp +62 -0
- package/nitrogen/generated/android/c++/JHybridRecognizerSpec.cpp +4 -0
- package/nitrogen/generated/android/c++/JSpeechToTextParams.hpp +11 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/HapticFeedbackStyle.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrospeech/SpeechToTextParams.kt +8 -2
- package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Bridge.hpp +18 -0
- package/nitrogen/generated/ios/NitroSpeech-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridRecognizerSpecSwift.hpp +3 -0
- package/nitrogen/generated/ios/swift/HapticFeedbackStyle.swift +44 -0
- package/nitrogen/generated/ios/swift/SpeechToTextParams.swift +47 -1
- package/nitrogen/generated/shared/c++/HapticFeedbackStyle.hpp +80 -0
- package/nitrogen/generated/shared/c++/SpeechToTextParams.hpp +12 -2
- package/package.json +1 -1
- package/src/index.ts +14 -2
- package/src/specs/NitroSpeech.nitro.ts +14 -0
package/README.md
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
[](https://github.com/NotGeorgeMessier/nitro-speech/blob/main/LICENSE)
|
|
5
5
|
[](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
|
|
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
|
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
105
|
+
private func startRecognition() {
|
|
97
106
|
isStopping = false
|
|
98
107
|
|
|
99
|
-
let locale = Locale(identifier:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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":";;;;;;
|
|
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":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -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,
|
package/lib/module/index.js.map
CHANGED
|
@@ -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,
|
|
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":[]}
|