🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more →
Socket
Book a DemoInstallSign in
Socket

react-native-voice-hold

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-native-voice-hold

React Native Voice library with enhanced hold recording functionality and React Native 0.80+ compatibility fixes

1.0.7
latest
Source
npm
Version published
Weekly downloads
403
Maintainers
1
Weekly downloads
 
Created
Source

đŸŽ™ī¸ react-native-voice-hold

npm version npm license

Enhanced React Native Voice Recognition Library with Hold Recording Support

A robust React Native voice recognition library with critical bug fixes for React Native 0.76+ and the New Architecture. This package resolves the major event listener issues that break speech-to-text functionality in the latest React Native versions.

🚨 Key Fixes & Improvements

✅ React Native 0.76+ Compatibility

  • Fixed: Critical event listener bug in React Native 0.76+ with New Architecture
  • Solution: Implemented DeviceEventEmitter pattern for reliable event handling
  • Result: Speech-to-text results now work properly in RN 0.76+ and 0.80+

đŸ”Ĩ Critical Fix: Empty Final Results

  • Issue: Android Speech Recognition API sometimes returns empty final results even when partial results contain valid transcription
  • Symptoms: onSpeechResults callback receives empty array [] despite successful partial results
  • Root Cause: Device-specific speech recognition quirks, short speech segments, background noise, timing issues
  • Solution: Implemented comprehensive fallback mechanism using partial results
    • Timeout Fallback: 1-second timeout ensures transcript processing even if final results fail
    • Partial Result Storage: Stores meaningful partial results for fallback use
    • Smart Fallback Logic: Uses partial results when final results are empty
    • Error Recovery: Proper cleanup and interruption handling

đŸŽ¯ Enhanced Hold Recording

  • New: startHoldRecording() and stopHoldRecording() methods
  • Feature: Continuous recording without auto-stop timeouts
  • Use Case: Perfect for voice messages and long-form dictation

🔧 Production Ready

  • Architecture: Full New Architecture (TurboModules) support
  • Platforms: iOS 11+ and Android API 21+
  • Stability: Comprehensive error handling and state management

📱 Installation

npm install react-native-voice-hold

iOS Setup (Required)

cd ios && pod install

Android Setup

Add microphone permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />

🚀 Quick Start

Basic Usage (Fixed for RN 0.76+)

import Voice from 'react-native-voice-hold';
import { DeviceEventEmitter } from 'react-native';

const VoiceComponent = () => {
  const [results, setResults] = useState<string[]>([]);
  const [isListening, setIsListening] = useState(false);

  useEffect(() => {
    // ✅ CRITICAL: Use DeviceEventEmitter for RN 0.76+ compatibility
    const onSpeechResults = DeviceEventEmitter.addListener(
      'onSpeechResults',
      (e: any) => {
        console.log('📝 Speech results:', e.value);
        setResults(e.value);
      }
    );

    const onSpeechStart = DeviceEventEmitter.addListener(
      'onSpeechStart',
      () => {
        console.log('🎤 Speech started');
        setIsListening(true);
      }
    );

    const onSpeechEnd = DeviceEventEmitter.addListener(
      'onSpeechEnd',
      () => {
        console.log('🔇 Speech ended');
        setIsListening(false);
      }
    );

    return () => {
      onSpeechResults.remove();
      onSpeechStart.remove();
      onSpeechEnd.remove();
    };
  }, []);

  const startListening = async () => {
    try {
      await Voice.start('en-US');
    } catch (error) {
      console.error('❌ Error starting voice recognition:', error);
    }
  };

  const stopListening = async () => {
    try {
      await Voice.stop();
    } catch (error) {
      console.error('❌ Error stopping voice recognition:', error);
    }
  };

  return (
    <View>
      <Button
        title={isListening ? 'Stop Listening' : 'Start Listening'}
        onPress={isListening ? stopListening : startListening}
      />
      {results.map((result, index) => (
        <Text key={index}>{result}</Text>
      ))}
    </View>
  );
};

Hold Recording (New Feature)

import Voice from 'react-native-voice-hold';

// Start continuous recording (perfect for voice messages)
const startHoldRecording = async () => {
  try {
    await Voice.startHoldRecording('en-US', {
      // Hold mode disables silence timeouts
      continuous: true,
      maximumWaitTime: 300000, // 5 minutes max
    });
    console.log('đŸŽ™ī¸ Hold recording started');
  } catch (error) {
    console.error('❌ Error starting hold recording:', error);
  }
};

// Stop hold recording manually
const stopHoldRecording = async () => {
  try {
    await Voice.stopHoldRecording();
    console.log('âšī¸ Hold recording stopped');
  } catch (error) {
    console.error('❌ Error stopping hold recording:', error);
  }
};

đŸ”Ĩ Critical Fix: Empty Final Results Solution

import { useVoiceRecognition } from 'react-native-voice-hold';

const VoiceAssistant = () => {
  const [state, actions] = useVoiceRecognition({
    locale: 'en-US',
    onStart: () => {
      console.log('🎤 Speech started');
      // Clear any existing timeout and reset state
    },
    onEnd: () => {
      console.log('🔇 Speech ended - processing...');
      // đŸ”Ĩ CRITICAL FIX: 1-second timeout ensures transcript processing
      // even if final results don't come
    },
    onResults: results => {
      console.log('📝 Final results received:', results);
      if (results.length > 0) {
        // Process final results normally
        processTranscript(results[0]);
      } else {
        // đŸ”Ĩ CRITICAL FIX: Use partial results as fallback
        console.log('âš ī¸ Final results empty, using partial results fallback');
        // Hook automatically handles fallback logic
      }
    },
    onPartialResults: partial => {
      console.log('🔄 Partial results:', partial);
      // đŸ”Ĩ CRITICAL FIX: Hook stores partial results for fallback
    },
  });

  // The hook automatically handles all fallback logic!
  return (
    <View>
      <TouchableOpacity
        onPressIn={() => actions.startHoldRecording()}
        onPressOut={() => actions.stopListening()}
      >
        <Text>Hold to Speak</Text>
      </TouchableOpacity>
    </View>
  );
};

Direct Library Implementation

import React, { useEffect, useState, useRef } from 'react';
import { DeviceEventEmitter, Platform } from 'react-native';
import Voice from 'react-native-voice-hold';

const VoiceComponent = () => {
  const [results, setResults] = useState<string[]>([]);
  const [isListening, setIsListening] = useState(false);
  
  // đŸ”Ĩ CRITICAL FIX: Add refs for fallback mechanism
  const lastPartialResult = useRef<string>('');
  const resultsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const isInterrupted = useRef<boolean>(false);

  useEffect(() => {
    const onSpeechStart = DeviceEventEmitter.addListener('onSpeechStart', () => {
      console.log('🎤 Speech started');
      setIsListening(true);
      // Clear any existing timeout
      if (resultsTimeoutRef.current) {
        clearTimeout(resultsTimeoutRef.current);
        resultsTimeoutRef.current = null;
      }
      lastPartialResult.current = '';
    });

    const onSpeechEnd = DeviceEventEmitter.addListener('onSpeechEnd', () => {
      console.log('🔇 Speech ended - processing...');
      // đŸ”Ĩ CRITICAL FIX: Add timeout to ensure we process transcript
      resultsTimeoutRef.current = setTimeout(() => {
        console.log('⏰ Timeout: No final results received, using partial results');
        const fallbackTranscript = lastPartialResult.current;
        if (fallbackTranscript.trim() && !isInterrupted.current) {
          console.log('🔄 Timeout fallback transcript:', fallbackTranscript);
          handleTranscript(fallbackTranscript);
        }
      }, 1000); // Wait 1 second for final results
    });

    const onSpeechResults = DeviceEventEmitter.addListener('onSpeechResults', (e) => {
      console.log('📝 Speech results:', e.value);
      // Clear the timeout since we got results
      if (resultsTimeoutRef.current) {
        clearTimeout(resultsTimeoutRef.current);
        resultsTimeoutRef.current = null;
      }

      if (e.value && e.value.length > 0) {
        const finalTranscript = e.value[0];
        console.log('⚡ Fast transcript:', finalTranscript);
        handleTranscript(finalTranscript);
      } else {
        // đŸ”Ĩ CRITICAL FIX: Use partial results as fallback
        console.log('âš ī¸ Final results empty, checking partial results...');
        const fallbackTranscript = lastPartialResult.current;
        if (fallbackTranscript.trim()) {
          console.log('🔄 Using fallback transcript:', fallbackTranscript);
          handleTranscript(fallbackTranscript);
        }
      }
    });

    const onSpeechPartialResults = DeviceEventEmitter.addListener('onSpeechPartialResults', (e) => {
      console.log('🔄 Partial results:', e.value);
      if (e.value && e.value.length > 0) {
        const partialTranscript = e.value[0];
        // đŸ”Ĩ CRITICAL FIX: Store the last partial result for fallback
        if (partialTranscript.trim()) {
          lastPartialResult.current = partialTranscript;
        }
      }
    });

    return () => {
      onSpeechStart.remove();
      onSpeechEnd.remove();
      onSpeechResults.remove();
      onSpeechPartialResults.remove();
      // Clear timeout on cleanup
      if (resultsTimeoutRef.current) {
        clearTimeout(resultsTimeoutRef.current);
        resultsTimeoutRef.current = null;
      }
      Voice.destroy();
    };
  }, []);

  const handleTranscript = (transcript: string) => {
    console.log('đŸŽ¯ Processing transcript:', transcript);
    setResults([transcript]);
  };

  // ... rest of component
};

📖 Complete API Reference

Standard Methods

  • Voice.start(locale) - Start voice recognition
  • Voice.stop() - Stop voice recognition
  • Voice.cancel() - Cancel voice recognition
  • Voice.destroy() - Clean up resources
  • Voice.isAvailable() - Check if voice recognition is available
  • Voice.isRecognizing() - Check if currently recognizing

Hold Recording Methods (New)

  • Voice.startHoldRecording(locale, options) - Start continuous recording
  • Voice.stopHoldRecording() - Stop hold recording

Event Listeners (DeviceEventEmitter - RN 0.76+ Compatible)

// ✅ CORRECT: Use DeviceEventEmitter
DeviceEventEmitter.addListener('onSpeechStart', handler);
DeviceEventEmitter.addListener('onSpeechRecognized', handler);
DeviceEventEmitter.addListener('onSpeechEnd', handler);
DeviceEventEmitter.addListener('onSpeechError', handler);
DeviceEventEmitter.addListener('onSpeechResults', handler);
DeviceEventEmitter.addListener('onSpeechPartialResults', handler);
DeviceEventEmitter.addListener('onSpeechVolumeChanged', handler);

// ❌ BROKEN: Direct assignment (doesn't work in RN 0.76+)
Voice.onSpeechResults = handler; // DON'T USE

🔄 Migration from react-native-voice

  • Install the new package:

    npm uninstall @react-native-voice/voice
    npm install react-native-voice-hold
    
  • Update your imports:

    // Old
    

import Voice from '@react-native-voice/voice';

// New import Voice from 'react-native-voice-hold';


3. **Update event listeners for RN 0.76+ compatibility:**
```typescript
// Old (broken in RN 0.76+)
Voice.onSpeechResults = (e) => console.log(e.value);

// New (works in all RN versions)
DeviceEventEmitter.addListener('onSpeechResults', (e) => console.log(e.value));
  • Add fallback logic for empty final results:
    // The useVoiceRecognition hook automatically handles this!
    // Or implement the fallback pattern shown above for direct usage
    

🐛 Troubleshooting

Empty Final Results

Problem: onSpeechResults receives empty array despite successful partial results.

Solution: ✅ Fixed in this package - The hook and examples above include comprehensive fallback logic.

Event Listeners Not Working (RN 0.76+)

Problem: Speech events not firing in React Native 0.76+.

Solution: ✅ Fixed - Use DeviceEventEmitter pattern instead of direct assignment.

Hold Recording Not Working

Problem: Hold recording stops unexpectedly.

Solution: ✅ Fixed - Proper hold mode state management prevents premature stopping.

📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests.

Built with â¤ī¸ for the React Native community

Keywords

react-native

FAQs

Package last updated on 26 Jun 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts