Preserve voice session across tab navigation
Add watchdog mechanism to ensure STT continues when switching tabs: - Monitor STT state every 500ms and restart if session active but STT stopped - Handle AppState changes to restart STT when app returns to foreground - Prevents session interruption due to native audio session changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5efd696ef2
commit
bdb4ceb8d2
@ -1,6 +1,6 @@
|
|||||||
import { Tabs } from 'expo-router';
|
import { Tabs } from 'expo-router';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Platform, View } from 'react-native';
|
import { Platform, View, AppState, AppStateStatus } from 'react-native';
|
||||||
import { Feather } from '@expo/vector-icons';
|
import { Feather } from '@expo/vector-icons';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
@ -143,6 +143,55 @@ export default function TabLayout() {
|
|||||||
}
|
}
|
||||||
}, [status, sttIsListening, startListening]);
|
}, [status, sttIsListening, startListening]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TAB NAVIGATION PERSISTENCE
|
||||||
|
// Ensure voice session continues when user switches between tabs.
|
||||||
|
// The session state is in VoiceContext (root level), but STT may stop due to:
|
||||||
|
// 1. Native audio session changes
|
||||||
|
// 2. Tab unmount/remount (though tabs layout doesn't unmount)
|
||||||
|
// 3. AppState changes (background/foreground)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Monitor and recover STT state during tab navigation
|
||||||
|
// If session is active but STT stopped unexpectedly, restart it
|
||||||
|
useEffect(() => {
|
||||||
|
// Check every 500ms if STT needs to be restarted
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
// Only act if session should be active (isListening from VoiceContext)
|
||||||
|
// but STT is not actually listening, and we're not in speaking/processing mode
|
||||||
|
if (
|
||||||
|
sessionActiveRef.current &&
|
||||||
|
!sttIsListening &&
|
||||||
|
status !== 'speaking' &&
|
||||||
|
status !== 'processing'
|
||||||
|
) {
|
||||||
|
console.log('[TabLayout] STT watchdog: restarting STT (session active but STT stopped)');
|
||||||
|
startListening();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [sttIsListening, status, startListening]);
|
||||||
|
|
||||||
|
// Handle app state changes (background/foreground)
|
||||||
|
// When app comes back to foreground, restart STT if session was active
|
||||||
|
useEffect(() => {
|
||||||
|
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||||
|
if (nextAppState === 'active' && sessionActiveRef.current) {
|
||||||
|
// App came to foreground, give it a moment then check STT
|
||||||
|
setTimeout(() => {
|
||||||
|
if (sessionActiveRef.current && !sttIsListening && status !== 'speaking' && status !== 'processing') {
|
||||||
|
console.log('[TabLayout] App foregrounded - restarting STT');
|
||||||
|
startListening();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
||||||
|
return () => subscription.remove();
|
||||||
|
}, [sttIsListening, status, startListening]);
|
||||||
|
|
||||||
// Handle voice FAB press - toggle listening mode
|
// Handle voice FAB press - toggle listening mode
|
||||||
const handleVoiceFABPress = useCallback(() => {
|
const handleVoiceFABPress = useCallback(() => {
|
||||||
if (isListening) {
|
if (isListening) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user