react-native-srschat 0.1.59 → 0.1.61
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/lib/commonjs/components/input.js +28 -1
- package/lib/commonjs/components/input.js.map +1 -1
- package/lib/commonjs/components/voice.js +28 -11
- package/lib/commonjs/components/voice.js.map +1 -1
- package/lib/commonjs/components/welcomeInput.js +29 -2
- package/lib/commonjs/components/welcomeInput.js.map +1 -1
- package/lib/commonjs/utils/audioRecorder.js +186 -157
- package/lib/commonjs/utils/audioRecorder.js.map +1 -1
- package/lib/module/components/input.js +28 -1
- package/lib/module/components/input.js.map +1 -1
- package/lib/module/components/voice.js +28 -11
- package/lib/module/components/voice.js.map +1 -1
- package/lib/module/components/welcomeInput.js +29 -2
- package/lib/module/components/welcomeInput.js.map +1 -1
- package/lib/module/utils/audioRecorder.js +186 -157
- package/lib/module/utils/audioRecorder.js.map +1 -1
- package/lib/typescript/components/input.d.ts.map +1 -1
- package/lib/typescript/components/voice.d.ts +3 -1
- package/lib/typescript/components/voice.d.ts.map +1 -1
- package/lib/typescript/components/welcomeInput.d.ts.map +1 -1
- package/lib/typescript/utils/audioRecorder.d.ts +1 -1
- package/lib/typescript/utils/audioRecorder.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/input.js +26 -1
- package/src/components/voice.js +36 -18
- package/src/components/welcomeInput.js +26 -1
- package/src/utils/audioRecorder.js +193 -174
|
@@ -7,7 +7,9 @@ import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
|
7
7
|
import { startRecording, stopRecording, cancelRecording, requestAudioPermission, cleanup, initVoice, resetStoredPermission, setPermissionStatusHandlers } from '../utils/audioRecorder';
|
|
8
8
|
import { AppContext } from '../contexts/AppContext';
|
|
9
9
|
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
10
|
-
export const VoiceButton = (
|
|
10
|
+
export const VoiceButton = ({
|
|
11
|
+
setInput
|
|
12
|
+
}) => {
|
|
11
13
|
const {
|
|
12
14
|
handleVoiceSend
|
|
13
15
|
} = useContext(AppContext);
|
|
@@ -35,23 +37,37 @@ export const VoiceButton = () => {
|
|
|
35
37
|
const permissionResult = await requestAudioPermission();
|
|
36
38
|
setHasPermission(permissionResult);
|
|
37
39
|
if (permissionResult) {
|
|
38
|
-
const initialized = await initVoice(
|
|
40
|
+
const initialized = await initVoice(
|
|
41
|
+
// Final result callback
|
|
42
|
+
(result, error) => {
|
|
43
|
+
console.log('Voice final result:', result, 'Error:', error);
|
|
44
|
+
|
|
45
|
+
// Always reset states when the recognition ends
|
|
46
|
+
setIsListening(false);
|
|
47
|
+
setLoading(false);
|
|
39
48
|
if (error) {
|
|
40
49
|
// Don't show alert for permission errors since we handle that elsewhere
|
|
41
50
|
if (!error.includes('permission')) {
|
|
42
51
|
Alert.alert('Error', error);
|
|
43
52
|
}
|
|
44
|
-
setIsListening(false);
|
|
45
|
-
setLoading(false);
|
|
46
53
|
return;
|
|
47
54
|
}
|
|
48
55
|
if (result) {
|
|
49
|
-
|
|
56
|
+
if (setInput) {
|
|
57
|
+
// For live transcription mode: just update input, don't auto-send
|
|
58
|
+
setInput(result);
|
|
59
|
+
} else {
|
|
60
|
+
// For original mode: send the message automatically
|
|
61
|
+
handleVoiceSend(null, result);
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
},
|
|
65
|
+
// Partial result callback for live transcription
|
|
66
|
+
setInput ? partialResult => {
|
|
67
|
+
if (partialResult) {
|
|
68
|
+
setInput(partialResult);
|
|
69
|
+
}
|
|
70
|
+
} : null);
|
|
55
71
|
if (!initialized) {
|
|
56
72
|
// Only show this alert once per session
|
|
57
73
|
if (!permissionChecked) {
|
|
@@ -69,7 +85,8 @@ export const VoiceButton = () => {
|
|
|
69
85
|
setupVoice();
|
|
70
86
|
}
|
|
71
87
|
return () => {
|
|
72
|
-
|
|
88
|
+
// Optional: only if the entire feature is being removed from the app
|
|
89
|
+
// cleanup();
|
|
73
90
|
};
|
|
74
91
|
}, [permissionStatus, permissionChecked]);
|
|
75
92
|
const toggleRecording = async () => {
|
|
@@ -116,7 +133,7 @@ export const VoiceButton = () => {
|
|
|
116
133
|
console.error('Error in toggleRecording:', error);
|
|
117
134
|
Alert.alert('Error', 'An error occurred while managing voice recognition');
|
|
118
135
|
setIsListening(false);
|
|
119
|
-
|
|
136
|
+
// Don't call cleanup here - let the natural session cleanup handle it
|
|
120
137
|
} finally {
|
|
121
138
|
setLoading(false);
|
|
122
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useState","useContext","useEffect","TouchableOpacity","ActivityIndicator","StyleSheet","Alert","Linking","Platform","Ionicons","useAsyncStorage","startRecording","stopRecording","cancelRecording","requestAudioPermission","cleanup","initVoice","resetStoredPermission","setPermissionStatusHandlers","AppContext","PERMISSION_STORAGE_KEY","VoiceButton","handleVoiceSend","isListening","setIsListening","loading","setLoading","permissionChecked","setPermissionChecked","hasPermission","setHasPermission","permissionStatus","setPermissionStatus","setupVoice","permissionResult","initialized","result","error","includes","alert","
|
|
1
|
+
{"version":3,"names":["React","useState","useContext","useEffect","TouchableOpacity","ActivityIndicator","StyleSheet","Alert","Linking","Platform","Ionicons","useAsyncStorage","startRecording","stopRecording","cancelRecording","requestAudioPermission","cleanup","initVoice","resetStoredPermission","setPermissionStatusHandlers","AppContext","PERMISSION_STORAGE_KEY","VoiceButton","setInput","handleVoiceSend","isListening","setIsListening","loading","setLoading","permissionChecked","setPermissionChecked","hasPermission","setHasPermission","permissionStatus","setPermissionStatus","setupVoice","permissionResult","initialized","result","error","console","log","includes","alert","partialResult","toggleRecording","text","style","onPress","openAppSettings","checkPermission","started","OS","openURL","openSettings","createElement","styles","button","disabled","size","color","name","create","justifyContent","alignItems"],"sourceRoot":"../../../src","sources":["components/voice.js"],"mappings":"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","ignoreList":[]}
|
|
@@ -16,6 +16,7 @@ export const WelcomeInput = ({
|
|
|
16
16
|
showModal,
|
|
17
17
|
theme
|
|
18
18
|
} = useContext(AppContext);
|
|
19
|
+
const inputRef = useRef(null);
|
|
19
20
|
const handleKeyPress = ({
|
|
20
21
|
nativeEvent
|
|
21
22
|
}) => {
|
|
@@ -33,6 +34,7 @@ export const WelcomeInput = ({
|
|
|
33
34
|
return /*#__PURE__*/React.createElement(View, {
|
|
34
35
|
style: styles.inputContainer
|
|
35
36
|
}, /*#__PURE__*/React.createElement(TextInput, {
|
|
37
|
+
ref: inputRef,
|
|
36
38
|
style: styles.input,
|
|
37
39
|
value: input,
|
|
38
40
|
onChangeText: setInput,
|
|
@@ -42,9 +44,34 @@ export const WelcomeInput = ({
|
|
|
42
44
|
returnKeyType: "send",
|
|
43
45
|
enablesReturnKeyAutomatically: true,
|
|
44
46
|
onKeyPress: handleKeyPress,
|
|
45
|
-
onSubmitEditing: onSubmitEditing
|
|
47
|
+
onSubmitEditing: onSubmitEditing,
|
|
48
|
+
selection: undefined
|
|
46
49
|
// blurOnSubmit={false}
|
|
47
|
-
}), /*#__PURE__*/React.createElement(VoiceButton,
|
|
50
|
+
}), /*#__PURE__*/React.createElement(VoiceButton, {
|
|
51
|
+
setInput: text => {
|
|
52
|
+
setInput(text);
|
|
53
|
+
// Auto-scroll to end
|
|
54
|
+
if (inputRef.current) {
|
|
55
|
+
// For iOS and Android, blur and refocus to ensure scroll
|
|
56
|
+
inputRef.current.blur();
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
if (inputRef.current) {
|
|
59
|
+
inputRef.current.focus();
|
|
60
|
+
// Set selection to end - Android sometimes needs a slight delay
|
|
61
|
+
if (Platform.OS === 'android') {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
if (inputRef.current) {
|
|
64
|
+
inputRef.current.setSelection(text.length, text.length);
|
|
65
|
+
}
|
|
66
|
+
}, 10);
|
|
67
|
+
} else {
|
|
68
|
+
inputRef.current.setSelection(text.length, text.length);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, 50);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}), /*#__PURE__*/React.createElement(TouchableOpacity, {
|
|
48
75
|
style: styles.sendButton,
|
|
49
76
|
onPress: () => handleSend(input),
|
|
50
77
|
disabled: !input.trim()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useState","useEffect","useContext","useRef","Text","StyleSheet","View","TextInput","TouchableOpacity","Platform","KeyboardAvoidingView","Keyboard","Header","AppContext","Ionicons","VoiceButton","WelcomeInput","onProductCardClick","onAddToCartClick","data","handleSend","input","setInput","showModal","theme","handleKeyPress","nativeEvent","key","shiftKey","preventDefault","onSubmitEditing","trim","createElement","style","styles","inputContainer","value","onChangeText","placeholder","placeholderTextColor","multiline","returnKeyType","enablesReturnKeyAutomatically","onKeyPress","sendButton","onPress","disabled","name","size","color","primaryColor","create","flexDirection","alignItems","paddingHorizontal","paddingVertical","backgroundColor","borderRadius","shadowColor","shadowOffset","width","height","shadowOpacity","shadowRadius","elevation","flex","fontSize","inputButton","padding","marginLeft","disabledButton","opacity"],"sourceRoot":"../../../src","sources":["components/welcomeInput.js"],"mappings":"AAAA,OAAOA,KAAK,IAAIC,QAAQ,EAAEC,SAAS,EAAEC,UAAU,EAAEC,MAAM,QAAQ,OAAO;AACtE,SACEC,IAAI,EACJC,UAAU,EACVC,IAAI,EACJC,SAAS,EACTC,gBAAgB,EAChBC,QAAQ,EACRC,oBAAoB,EACpBC,QAAQ,QACH,cAAc;AACrB,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,UAAU,QAAQ,wBAAwB;AACnD,OAAOC,QAAQ,MAAM,oCAAoC;AACzD,SAASC,WAAW,QAAQ,SAAS;AAErC,OAAO,MAAMC,YAAY,GAAGA,CAAC;EAAEC,kBAAkB;EAAEC;AAAiB,CAAC,KAAK;EAExE,MAAM;IAAEC,IAAI;IAAEC,UAAU;IAAEC,KAAK;IAAEC,QAAQ;IAAEC,SAAS;IAAEC;EAAM,CAAC,GAAGtB,UAAU,CAACW,UAAU,CAAC;
|
|
1
|
+
{"version":3,"names":["React","useState","useEffect","useContext","useRef","Text","StyleSheet","View","TextInput","TouchableOpacity","Platform","KeyboardAvoidingView","Keyboard","Header","AppContext","Ionicons","VoiceButton","WelcomeInput","onProductCardClick","onAddToCartClick","data","handleSend","input","setInput","showModal","theme","inputRef","handleKeyPress","nativeEvent","key","shiftKey","preventDefault","onSubmitEditing","trim","createElement","style","styles","inputContainer","ref","value","onChangeText","placeholder","placeholderTextColor","multiline","returnKeyType","enablesReturnKeyAutomatically","onKeyPress","selection","undefined","text","current","blur","setTimeout","focus","OS","setSelection","length","sendButton","onPress","disabled","name","size","color","primaryColor","create","flexDirection","alignItems","paddingHorizontal","paddingVertical","backgroundColor","borderRadius","shadowColor","shadowOffset","width","height","shadowOpacity","shadowRadius","elevation","flex","fontSize","inputButton","padding","marginLeft","disabledButton","opacity"],"sourceRoot":"../../../src","sources":["components/welcomeInput.js"],"mappings":"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","ignoreList":[]}
|
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
4
|
import Voice from '@react-native-voice/voice';
|
|
5
5
|
import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions';
|
|
6
|
-
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
7
6
|
let resultCallback = null;
|
|
7
|
+
let partialResultCallback = null;
|
|
8
8
|
let silenceTimer = null;
|
|
9
|
-
let isCurrentlyRecording = false;
|
|
10
9
|
let finalResult = '';
|
|
11
|
-
const SILENCE_DURATION = 1500;
|
|
10
|
+
const SILENCE_DURATION = 1500;
|
|
11
|
+
const State = {
|
|
12
|
+
IDLE: 'IDLE',
|
|
13
|
+
LISTENING: 'LISTENING',
|
|
14
|
+
FINALIZING: 'FINALIZING'
|
|
15
|
+
};
|
|
16
|
+
let state = State.IDLE;
|
|
17
|
+
let listenersBound = false;
|
|
12
18
|
|
|
13
19
|
// Add this constant for AsyncStorage key
|
|
14
20
|
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
@@ -22,11 +28,13 @@ export function setPermissionStatusHandlers(getter, setter) {
|
|
|
22
28
|
permissionStatusSetter = setter;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
// Initialize Voice handlers
|
|
26
|
-
export async function initVoice(onResult) {
|
|
31
|
+
// Initialize Voice handlers - modified to support live transcription
|
|
32
|
+
export async function initVoice(onResult, onPartialResult = null) {
|
|
27
33
|
try {
|
|
28
34
|
resultCallback = onResult;
|
|
35
|
+
partialResultCallback = onPartialResult; // Store partial callback
|
|
29
36
|
finalResult = '';
|
|
37
|
+
if (listenersBound) return true;
|
|
30
38
|
|
|
31
39
|
// Check if Voice module is available
|
|
32
40
|
if (!Voice) {
|
|
@@ -40,80 +48,103 @@ export async function initVoice(onResult) {
|
|
|
40
48
|
console.error('Speech recognition is not available on this device');
|
|
41
49
|
return false;
|
|
42
50
|
}
|
|
51
|
+
Voice.removeAllListeners();
|
|
43
52
|
|
|
44
53
|
// Set up all event listeners
|
|
45
|
-
Voice.onSpeechStart =
|
|
46
|
-
console.log('onSpeechStart
|
|
47
|
-
|
|
54
|
+
Voice.onSpeechStart = () => {
|
|
55
|
+
console.log('[onSpeechStart] Setting state to LISTENING');
|
|
56
|
+
state = State.LISTENING;
|
|
48
57
|
finalResult = '';
|
|
49
|
-
|
|
50
|
-
clearTimeout(silenceTimer);
|
|
51
|
-
silenceTimer = null;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
Voice.onSpeechRecognized = e => {
|
|
55
|
-
console.log('onSpeechRecognized: ', e);
|
|
56
|
-
if (e.isFinal) {
|
|
57
|
-
console.log('Speech recognition final');
|
|
58
|
-
handleFinalResult();
|
|
59
|
-
}
|
|
58
|
+
clearSilenceTimer();
|
|
60
59
|
};
|
|
61
|
-
Voice.onSpeechEnd = async e => {
|
|
62
|
-
console.log('onSpeechEnd: ', e);
|
|
63
|
-
if (silenceTimer) {
|
|
64
|
-
clearTimeout(silenceTimer);
|
|
65
|
-
silenceTimer = null;
|
|
66
|
-
}
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
// Optional: ignore onSpeechRecognized or just log
|
|
62
|
+
Voice.onSpeechRecognized = () => {};
|
|
63
|
+
Voice.onSpeechEnd = () => {
|
|
64
|
+
console.log('[onSpeechEnd] Speech ended, current state:', state);
|
|
65
|
+
clearSilenceTimer();
|
|
66
|
+
// Always reset to IDLE when speech ends (sessions should be considered over)
|
|
67
|
+
console.log('[onSpeechEnd] Scheduling IDLE reset');
|
|
68
|
+
if (Platform.OS === 'android') {
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
console.log('[onSpeechEnd] Android timeout - setting state to IDLE');
|
|
71
|
+
state = State.IDLE;
|
|
72
|
+
}, 800); // Increased delay
|
|
73
|
+
} else {
|
|
74
|
+
console.log('[onSpeechEnd] iOS - setting state to IDLE immediately');
|
|
75
|
+
state = State.IDLE;
|
|
71
76
|
}
|
|
72
77
|
};
|
|
73
|
-
Voice.onSpeechError =
|
|
78
|
+
Voice.onSpeechError = e => {
|
|
74
79
|
var _e$error, _e$error2;
|
|
75
|
-
|
|
80
|
+
console.log('[onSpeechError] Error occurred, current state:', state, 'error:', e);
|
|
81
|
+
clearSilenceTimer();
|
|
82
|
+
const code = (_e$error = e.error) === null || _e$error === void 0 || (_e$error = _e$error.code) === null || _e$error === void 0 ? void 0 : _e$error.toString();
|
|
83
|
+
const msg = ((_e$error2 = e.error) === null || _e$error2 === void 0 ? void 0 : _e$error2.message) || '';
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
// Handle callback first
|
|
86
|
+
if (Platform.OS === 'android' && (code === '7' || code === '5')) {
|
|
87
|
+
if (finalResult && resultCallback) resultCallback(finalResult, null);else if (resultCallback) resultCallback(null, null);
|
|
88
|
+
} else if (!msg.includes('No speech detected') && resultCallback) {
|
|
89
|
+
resultCallback(null, msg);
|
|
90
|
+
} else if (resultCallback) {
|
|
91
|
+
resultCallback(null, null);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// }
|
|
94
|
+
// Errors end the session immediately, reset to IDLE with delay
|
|
95
|
+
console.log('[onSpeechError] Scheduling IDLE reset');
|
|
96
|
+
if (Platform.OS === 'android') {
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
console.log('[onSpeechError] Android timeout - setting state to IDLE');
|
|
99
|
+
state = State.IDLE;
|
|
100
|
+
}, 800); // Increased delay to match onSpeechEnd
|
|
101
|
+
} else {
|
|
102
|
+
console.log('[onSpeechError] iOS - setting state to IDLE immediately');
|
|
103
|
+
state = State.IDLE;
|
|
104
|
+
}
|
|
94
105
|
};
|
|
95
106
|
Voice.onSpeechResults = e => {
|
|
96
|
-
console.log('onSpeechResults: ', e);
|
|
107
|
+
console.log('[onSpeechResults] Results received, current state:', state, 'results:', e);
|
|
108
|
+
clearSilenceTimer();
|
|
97
109
|
if (e.value && e.value.length > 0) {
|
|
98
110
|
finalResult = e.value[0];
|
|
99
|
-
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Only call callback if we haven't already (avoid double-calling)
|
|
114
|
+
if (state === State.LISTENING && resultCallback) {
|
|
115
|
+
console.log('[onSpeechResults] Calling callback with results');
|
|
116
|
+
resultCallback(finalResult, null);
|
|
117
|
+
} else {
|
|
118
|
+
console.log('[onSpeechResults] Not calling callback - state:', state);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// On Android, we must explicitly stop to avoid session corruption
|
|
122
|
+
if (Platform.OS === 'android') {
|
|
123
|
+
console.log('[onSpeechResults] Android: Explicitly calling stopRecording()');
|
|
124
|
+
stopRecording();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Results end the session, reset to IDLE with delay
|
|
128
|
+
console.log('[onSpeechResults] Scheduling IDLE reset');
|
|
129
|
+
if (Platform.OS === 'android') {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
console.log('[onSpeechResults] Android timeout - setting state to IDLE');
|
|
132
|
+
state = State.IDLE;
|
|
133
|
+
}, 800); // Increased delay
|
|
134
|
+
} else {
|
|
135
|
+
console.log('[onSpeechResults] iOS - setting state to IDLE immediately');
|
|
136
|
+
state = State.IDLE;
|
|
100
137
|
}
|
|
101
138
|
};
|
|
102
139
|
Voice.onSpeechPartialResults = e => {
|
|
103
|
-
console.log('onSpeechPartialResults: ', e);
|
|
104
|
-
if (silenceTimer) {
|
|
105
|
-
clearTimeout(silenceTimer);
|
|
106
|
-
}
|
|
107
140
|
if (e.value && e.value.length > 0) {
|
|
108
141
|
finalResult = e.value[0];
|
|
142
|
+
if (partialResultCallback) partialResultCallback(finalResult);
|
|
109
143
|
handleSilenceDetection();
|
|
110
144
|
}
|
|
111
145
|
};
|
|
112
|
-
if (Platform.OS === 'android') {
|
|
113
|
-
|
|
114
|
-
console.log('onSpeechVolumeChanged: ', e);
|
|
115
|
-
};
|
|
116
|
-
}
|
|
146
|
+
if (Platform.OS === 'android') Voice.onSpeechVolumeChanged = () => {};
|
|
147
|
+
listenersBound = true;
|
|
117
148
|
return true;
|
|
118
149
|
} catch (error) {
|
|
119
150
|
console.error('Error initializing Voice:', error);
|
|
@@ -125,138 +156,144 @@ const handleSilenceDetection = () => {
|
|
|
125
156
|
clearTimeout(silenceTimer);
|
|
126
157
|
}
|
|
127
158
|
silenceTimer = setTimeout(async () => {
|
|
128
|
-
if (
|
|
159
|
+
if (state === State.LISTENING) {
|
|
129
160
|
await handleFinalResult();
|
|
130
161
|
}
|
|
131
162
|
}, SILENCE_DURATION);
|
|
132
163
|
};
|
|
133
164
|
const handleFinalResult = async () => {
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
165
|
+
console.log('[handleFinalResult] Called, current state:', state);
|
|
166
|
+
if (state !== State.LISTENING) {
|
|
167
|
+
console.log('[handleFinalResult] State not LISTENING, returning');
|
|
168
|
+
return;
|
|
137
169
|
}
|
|
138
170
|
|
|
139
|
-
//
|
|
171
|
+
// Set to FINALIZING first to prevent double callbacks
|
|
172
|
+
console.log('[handleFinalResult] Setting state to FINALIZING');
|
|
173
|
+
state = State.FINALIZING;
|
|
174
|
+
|
|
175
|
+
// Call the callback with results
|
|
176
|
+
if (finalResult && resultCallback) {
|
|
177
|
+
console.log('[handleFinalResult] Calling callback with result:', finalResult);
|
|
178
|
+
resultCallback(finalResult, null);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Now stop recording (this will call Voice.stop())
|
|
182
|
+
console.log('[handleFinalResult] Calling stopRecording');
|
|
140
183
|
await stopRecording();
|
|
184
|
+
};
|
|
185
|
+
const cleanupVoiceSession = () => {
|
|
186
|
+
console.log('[cleanupVoiceSession] Called, current state:', state);
|
|
187
|
+
finalResult = '';
|
|
188
|
+
clearSilenceTimer();
|
|
141
189
|
|
|
142
|
-
//
|
|
143
|
-
|
|
190
|
+
// Add delay before allowing next session on Android
|
|
191
|
+
if (Platform.OS === 'android') {
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
console.log('[cleanupVoiceSession] Android timeout - setting state to IDLE');
|
|
194
|
+
state = State.IDLE;
|
|
195
|
+
}, 800);
|
|
196
|
+
} else {
|
|
197
|
+
console.log('[cleanupVoiceSession] iOS - setting state to IDLE immediately');
|
|
198
|
+
state = State.IDLE;
|
|
199
|
+
}
|
|
144
200
|
};
|
|
145
|
-
const
|
|
146
|
-
isCurrentlyRecording = false;
|
|
201
|
+
const clearSilenceTimer = () => {
|
|
147
202
|
if (silenceTimer) {
|
|
148
203
|
clearTimeout(silenceTimer);
|
|
149
204
|
silenceTimer = null;
|
|
150
205
|
}
|
|
206
|
+
};
|
|
207
|
+
export async function startRecording() {
|
|
151
208
|
try {
|
|
152
|
-
|
|
153
|
-
if (!Voice) {
|
|
154
|
-
console.log('Voice module not available during cleanup');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
209
|
+
console.log('[startRecording] Called, current state:', state);
|
|
157
210
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
if (isRecognizing) {
|
|
211
|
+
// On Android, destroy any lingering instance before starting
|
|
212
|
+
if (Platform.OS === 'android') {
|
|
161
213
|
try {
|
|
162
|
-
|
|
163
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
164
|
-
} catch (e) {
|
|
165
|
-
console.error('Error stopping in cleanup:', e);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Then force destroy
|
|
170
|
-
await Voice.destroy();
|
|
171
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
172
|
-
|
|
173
|
-
// Double check and force destroy again if needed
|
|
174
|
-
const stillRecognizing = await Voice.isRecognizing();
|
|
175
|
-
if (stillRecognizing) {
|
|
176
|
-
await Voice.destroy();
|
|
177
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error('Error in cleanupVoiceSession:', error);
|
|
181
|
-
// Final attempt to destroy on error
|
|
182
|
-
try {
|
|
183
|
-
if (Voice) {
|
|
214
|
+
console.log('[startRecording] Android: Proactively destroying Voice instance');
|
|
184
215
|
await Voice.destroy();
|
|
216
|
+
await new Promise(r => setTimeout(r, 100)); // Short delay for destroy to complete
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.log('[startRecording] Proactive destroy failed, may be okay:', e);
|
|
185
219
|
}
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.error('Final destroy attempt failed:', e);
|
|
188
220
|
}
|
|
189
|
-
}
|
|
190
|
-
finalResult = '';
|
|
191
|
-
};
|
|
192
|
-
export async function startRecording() {
|
|
193
|
-
try {
|
|
194
|
-
// Check if Voice module is available
|
|
195
221
|
if (!Voice) {
|
|
196
|
-
console.
|
|
222
|
+
console.log('[startRecording] Voice not available');
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (state !== State.IDLE) {
|
|
226
|
+
console.log('[startRecording] State not IDLE, returning false');
|
|
197
227
|
return false;
|
|
198
228
|
}
|
|
199
|
-
|
|
200
|
-
// Ensure cleanup of any existing session
|
|
201
|
-
await cleanupVoiceSession();
|
|
202
229
|
const hasPermission = await requestAudioPermission();
|
|
203
230
|
if (!hasPermission) {
|
|
204
231
|
console.error('No permission to record audio');
|
|
205
232
|
return false;
|
|
206
233
|
}
|
|
234
|
+
const recognizing = await Voice.isRecognizing();
|
|
235
|
+
console.log('[startRecording] Voice.isRecognizing():', recognizing);
|
|
236
|
+
if (recognizing) {
|
|
237
|
+
console.log('[startRecording] Already recognizing, canceling first');
|
|
238
|
+
await Voice.cancel();
|
|
239
|
+
// Wait longer for cancel to take effect
|
|
240
|
+
await new Promise(r => setTimeout(r, 500));
|
|
241
|
+
|
|
242
|
+
// Double-check if still recognizing after cancel
|
|
243
|
+
const stillRecognizing = await Voice.isRecognizing();
|
|
244
|
+
console.log('[startRecording] After cancel, still recognizing:', stillRecognizing);
|
|
245
|
+
if (stillRecognizing) {
|
|
246
|
+
console.log('[startRecording] Still recognizing after cancel, stopping');
|
|
247
|
+
try {
|
|
248
|
+
await Voice.stop();
|
|
249
|
+
await new Promise(r => setTimeout(r, 300));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.log('[startRecording] Error stopping:', e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
console.log('[startRecording] Calling Voice.start()');
|
|
207
256
|
await Voice.start('en-US');
|
|
208
|
-
|
|
257
|
+
console.log('[startRecording] Voice.start() completed, setting state to LISTENING');
|
|
258
|
+
state = State.LISTENING;
|
|
209
259
|
return true;
|
|
210
260
|
} catch (error) {
|
|
211
261
|
console.error('Error starting voice recognition:', error);
|
|
212
|
-
|
|
262
|
+
cleanupVoiceSession();
|
|
213
263
|
return false;
|
|
214
264
|
}
|
|
215
265
|
}
|
|
216
266
|
export async function stopRecording() {
|
|
217
267
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
clearTimeout(silenceTimer);
|
|
224
|
-
silenceTimer = null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// First try to stop
|
|
228
|
-
try {
|
|
229
|
-
await Voice.stop();
|
|
230
|
-
// Wait a bit for stop to complete
|
|
231
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error('Error stopping Voice:', error);
|
|
268
|
+
console.log('[stopRecording] Called, current state:', state);
|
|
269
|
+
// Can be called from LISTENING or FINALIZING state
|
|
270
|
+
if (state !== State.LISTENING && state !== State.FINALIZING || !Voice) {
|
|
271
|
+
console.log('[stopRecording] Invalid state or no Voice, returning');
|
|
272
|
+
return;
|
|
234
273
|
}
|
|
235
274
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error('Error destroying Voice:', error);
|
|
275
|
+
// Only set to FINALIZING if not already there
|
|
276
|
+
if (state === State.LISTENING) {
|
|
277
|
+
console.log('[stopRecording] Setting state to FINALIZING');
|
|
278
|
+
state = State.FINALIZING;
|
|
242
279
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await
|
|
280
|
+
clearSilenceTimer();
|
|
281
|
+
console.log('[stopRecording] Calling Voice.stop()');
|
|
282
|
+
await Voice.stop();
|
|
283
|
+
console.log('[stopRecording] Voice.stop() completed');
|
|
246
284
|
} catch (error) {
|
|
247
285
|
console.error('Error in stopRecording:', error);
|
|
248
|
-
|
|
249
|
-
await cleanupVoiceSession();
|
|
286
|
+
cleanupVoiceSession();
|
|
250
287
|
}
|
|
251
288
|
}
|
|
252
289
|
export async function cancelRecording() {
|
|
253
290
|
try {
|
|
254
291
|
if (!Voice) return;
|
|
255
292
|
await Voice.cancel();
|
|
256
|
-
|
|
293
|
+
cleanupVoiceSession();
|
|
257
294
|
} catch (error) {
|
|
258
295
|
console.error('Error canceling voice recognition:', error);
|
|
259
|
-
|
|
296
|
+
cleanupVoiceSession();
|
|
260
297
|
}
|
|
261
298
|
}
|
|
262
299
|
export async function requestAudioPermission() {
|
|
@@ -293,11 +330,16 @@ async function requestAndroidPermission() {
|
|
|
293
330
|
return false;
|
|
294
331
|
}
|
|
295
332
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
333
|
+
// Check speech recognition services
|
|
334
|
+
try {
|
|
335
|
+
const services = await Voice.getSpeechRecognitionServices();
|
|
336
|
+
if (!services || services.length === 0) {
|
|
337
|
+
console.error('No speech recognition services available');
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
console.log('Error checking speech services:', e);
|
|
342
|
+
// Continue anyway - some devices report error but work fine
|
|
301
343
|
}
|
|
302
344
|
return true;
|
|
303
345
|
} catch (error) {
|
|
@@ -334,19 +376,6 @@ export function resetStoredPermission() {
|
|
|
334
376
|
return false;
|
|
335
377
|
}
|
|
336
378
|
export function cleanup() {
|
|
337
|
-
|
|
338
|
-
console.log('Voice module not available during cleanup');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
Voice.destroy().then(() => {
|
|
342
|
-
Voice.removeAllListeners();
|
|
343
|
-
cleanupVoiceSession();
|
|
344
|
-
}).catch(error => {
|
|
345
|
-
console.error('Error in cleanup:', error);
|
|
346
|
-
// Try one more time
|
|
347
|
-
if (Voice) {
|
|
348
|
-
Voice.destroy().catch(e => console.error('Final cleanup attempt failed:', e));
|
|
349
|
-
}
|
|
350
|
-
});
|
|
379
|
+
cleanupVoiceSession();
|
|
351
380
|
}
|
|
352
381
|
//# sourceMappingURL=audioRecorder.js.map
|