@lakshmiprasanth/react-voice-to-text
Version:
A modern React package for voice-to-text conversion with real-time speech recognition and file upload support
964 lines (954 loc) • 33.8 kB
JavaScript
import { useState, useEffect, useCallback, useRef } from 'react';
/**
* Simple EventEmitter implementation for browser compatibility
*/
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return this;
}
once(event, listener) {
const onceWrapper = (...args) => {
this.off(event, onceWrapper);
listener.apply(this, args);
};
return this.on(event, onceWrapper);
}
off(event, listener) {
if (!this.events[event])
return this;
this.events[event] = this.events[event].filter(l => l !== listener);
return this;
}
emit(event, ...args) {
if (!this.events[event])
return false;
this.events[event].forEach(listener => {
try {
listener.apply(this, args);
}
catch (error) {
console.error('Error in event listener:', error);
}
});
return true;
}
removeAllListeners(event) {
if (event) {
delete this.events[event];
}
else {
this.events = {};
}
return this;
}
}
/**
* Error types that can occur during speech recognition
*/
var SpeechRecognitionErrorType;
(function (SpeechRecognitionErrorType) {
SpeechRecognitionErrorType["NO_SPEECH"] = "no-speech";
SpeechRecognitionErrorType["ABORTED"] = "aborted";
SpeechRecognitionErrorType["AUDIO_CAPTURE"] = "audio-capture";
SpeechRecognitionErrorType["NETWORK"] = "network";
SpeechRecognitionErrorType["NOT_ALLOWED"] = "not-allowed";
SpeechRecognitionErrorType["SERVICE_NOT_ALLOWED"] = "service-not-allowed";
SpeechRecognitionErrorType["BAD_GRAMMAR"] = "bad-grammar";
SpeechRecognitionErrorType["LANGUAGE_NOT_SUPPORTED"] = "language-not-supported";
SpeechRecognitionErrorType["ENGINE_ERROR"] = "engine-error";
SpeechRecognitionErrorType["INVALID_CONFIG"] = "invalid-config";
})(SpeechRecognitionErrorType || (SpeechRecognitionErrorType = {}));
/**
* Speech recognition error
*/
class SpeechRecognitionError extends Error {
constructor(type, message, originalError) {
super(message);
this.type = type;
this.originalError = originalError;
this.name = 'SpeechRecognitionError';
}
}
/**
* Base class for speech recognition engines
*/
class BaseSpeechRecognitionEngine extends EventEmitter {
constructor() {
super();
this.isRecording = false;
this.config = {};
}
/**
* Validate configuration
*/
validateConfig(config) {
if (config.confidenceThreshold && (config.confidenceThreshold < 0 || config.confidenceThreshold > 1)) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Confidence threshold must be between 0 and 1');
}
if (config.maxAlternatives && config.maxAlternatives < 1) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Max alternatives must be at least 1');
}
if (config.sampleRate && config.sampleRate < 8000) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Sample rate must be at least 8000 Hz');
}
}
/**
* Validate audio input configuration
*/
validateAudioConfig(audioConfig) {
if (!audioConfig.source) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Audio source must be specified');
}
if (audioConfig.source === 'file' && !audioConfig.filePath) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'File path must be specified for file source');
}
if (audioConfig.source === 'stream' && !audioConfig.audioStream) {
throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Audio stream must be specified for stream source');
}
}
/**
* Emit error event with proper error handling
*/
emitError(type, message, originalError) {
const error = new SpeechRecognitionError(type, message, originalError);
this.emit('error', error);
}
/**
* Emit result event with validation
*/
emitResult(result) {
// Validate result
if (!result.transcript) {
return; // Skip empty results
}
// Apply confidence threshold if configured
if (this.config.confidenceThreshold && result.confidence < this.config.confidenceThreshold) {
return; // Skip low-confidence results
}
this.emit('result', result);
}
/**
* Get default configuration merged with provided config
*/
getConfig(config) {
return {
language: 'en-US',
sampleRate: 16000,
continuous: false,
interimResults: false,
maxAlternatives: 1,
confidenceThreshold: 0.0,
encoding: 'LINEAR16',
...this.config,
...config
};
}
/**
* Check if currently recording
*/
get isActive() {
return this.isRecording;
}
/**
* Set recording state
*/
setRecordingState(recording) {
this.isRecording = recording;
}
/**
* Clean up resources
*/
cleanup() {
this.removeAllListeners();
this.isRecording = false;
}
/**
* Type-safe event emitter methods
*/
emit(event, ...args) {
return super.emit(event, ...args);
}
on(event, listener) {
return super.on(event, listener);
}
once(event, listener) {
return super.once(event, listener);
}
off(event, listener) {
return super.off(event, listener);
}
}
/**
* Web Speech API engine for browser environments
*/
class WebSpeechEngine extends BaseSpeechRecognitionEngine {
constructor() {
super();
this.recognition = null;
this.mediaRecorder = null;
this.audioChunks = [];
}
/**
* Check if Web Speech API is available
*/
isAvailable() {
if (typeof window === 'undefined') {
return false; // Not in browser environment
}
return !!(window.SpeechRecognition || window.webkitSpeechRecognition);
}
/**
* Get supported languages (common languages supported by most browsers)
*/
getSupportedLanguages() {
return [
'en-US', 'en-GB', 'en-AU', 'en-CA', 'en-IN', 'en-NZ', 'en-ZA',
'es-ES', 'es-MX', 'es-AR', 'es-CO', 'es-CL', 'es-PE', 'es-VE',
'fr-FR', 'fr-CA', 'fr-CH', 'fr-BE',
'de-DE', 'de-AT', 'de-CH',
'it-IT', 'it-CH',
'pt-BR', 'pt-PT',
'ru-RU',
'ja-JP',
'ko-KR',
'zh-CN', 'zh-TW', 'zh-HK',
'ar-SA', 'ar-EG',
'hi-IN',
'th-TH',
'tr-TR',
'pl-PL',
'nl-NL', 'nl-BE',
'sv-SE',
'da-DK',
'no-NO',
'fi-FI'
];
}
/**
* Initialize the speech recognition engine
*/
async initialize(_config) {
if (!this.isAvailable()) {
throw new Error('Web Speech API is not available in this environment');
}
this.config = this.getConfig(_config);
this.validateConfig(this.config);
try {
await this.initializeRecognition();
this.emit('initialized');
}
catch (error) {
this.emitError(SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to initialize Web Speech recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Start listening for speech
*/
async startListening(config) {
if (!this.recognition) {
throw new Error('Speech recognition not initialized. Call initialize() first.');
}
if (this.isRecording) {
await this.stopListening();
}
try {
this.recognition.start();
this.isRecording = true;
this.emit('started');
}
catch (error) {
this.emitError(SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to start listening: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Stop listening for speech
*/
async stopListening() {
if (!this.recognition) {
return;
}
try {
this.recognition.stop();
this.isRecording = false;
this.emit('stopped');
}
catch (error) {
this.emitError(SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to stop listening: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Start speech recognition
*/
async start(audioConfig, recognitionConfig) {
if (!this.isAvailable()) {
throw new Error('Web Speech API is not available in this environment');
}
this.validateAudioConfig(audioConfig);
this.config = this.getConfig(recognitionConfig);
this.validateConfig(this.config);
if (this.isRecording) {
await this.stop();
}
try {
await this.initializeRecognition();
if (audioConfig.source === 'microphone') {
await this.startMicrophoneRecognition(audioConfig);
}
else if (audioConfig.source === 'file') {
await this.processAudioFile(audioConfig.filePath);
}
else {
throw new Error('Web Speech API only supports microphone and file input');
}
}
catch (error) {
this.emitError(SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to start Web Speech recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Initialize speech recognition instance
*/
async initializeRecognition() {
const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;
this.recognition = new SpeechRecognitionClass();
// Configure recognition
this.recognition.continuous = this.config.continuous || false;
this.recognition.interimResults = this.config.interimResults || false;
this.recognition.lang = this.config.language || 'en-US';
this.recognition.maxAlternatives = this.config.maxAlternatives || 1;
// Set up event handlers
this.recognition.onstart = () => {
this.setRecordingState(true);
this.emit('start');
this.emit('audiostart');
};
this.recognition.onend = () => {
this.setRecordingState(false);
this.emit('audioend');
this.emit('end');
};
this.recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const result = event.results[i];
const alternative = result[0];
const speechResult = {
transcript: alternative.transcript,
confidence: alternative.confidence,
isFinal: result.isFinal,
alternatives: Array.from(result).map((alt) => ({
transcript: alt.transcript,
confidence: alt.confidence
}))
};
this.emitResult(speechResult);
}
};
this.recognition.onerror = (event) => {
let errorType;
switch (event.error) {
case 'no-speech':
errorType = SpeechRecognitionErrorType.NO_SPEECH;
break;
case 'aborted':
errorType = SpeechRecognitionErrorType.ABORTED;
break;
case 'audio-capture':
errorType = SpeechRecognitionErrorType.AUDIO_CAPTURE;
break;
case 'network':
errorType = SpeechRecognitionErrorType.NETWORK;
break;
case 'not-allowed':
errorType = SpeechRecognitionErrorType.NOT_ALLOWED;
break;
case 'service-not-allowed':
errorType = SpeechRecognitionErrorType.SERVICE_NOT_ALLOWED;
break;
case 'bad-grammar':
errorType = SpeechRecognitionErrorType.BAD_GRAMMAR;
break;
case 'language-not-supported':
errorType = SpeechRecognitionErrorType.LANGUAGE_NOT_SUPPORTED;
break;
default:
errorType = SpeechRecognitionErrorType.ENGINE_ERROR;
}
this.emitError(errorType, `Web Speech API error: ${event.error}`);
};
this.recognition.onspeechstart = () => this.emit('speechstart');
this.recognition.onspeechend = () => this.emit('speechend');
this.recognition.onsoundstart = () => this.emit('soundstart');
this.recognition.onsoundend = () => this.emit('soundend');
}
/**
* Start microphone recognition
*/
async startMicrophoneRecognition(audioConfig) {
if (!this.recognition) {
throw new Error('Recognition not initialized');
}
try {
// Request microphone permission and start recognition
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: audioConfig.deviceId ? { exact: audioConfig.deviceId } : undefined,
sampleRate: this.config.sampleRate || 16000
}
});
// Start recognition
this.recognition.start();
// If duration is specified, stop after that time
if (audioConfig.duration) {
setTimeout(() => {
this.stop().catch(console.error);
}, audioConfig.duration);
}
// Clean up stream when recognition ends
this.recognition.onend = () => {
stream.getTracks().forEach(track => track.stop());
this.setRecordingState(false);
this.emit('audioend');
this.emit('end');
};
}
catch (error) {
this.emitError(SpeechRecognitionErrorType.AUDIO_CAPTURE, `Failed to access microphone: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Process audio file (requires MediaRecorder API)
*/
async processAudioFile(_filePath) {
// Note: Direct file processing with Web Speech API is limited
// This is a simplified implementation that would need additional audio processing
throw new Error('File processing with Web Speech API requires additional audio processing libraries');
}
/**
* Stop speech recognition
*/
async stop() {
if (this.recognition && this.isRecording) {
this.recognition.stop();
}
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
this.mediaRecorder.stop();
}
this.setRecordingState(false);
}
/**
* Abort speech recognition
*/
async abort() {
if (this.recognition) {
this.recognition.abort();
}
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
this.mediaRecorder.stop();
}
this.setRecordingState(false);
}
/**
* Process audio file directly (not supported by Web Speech API)
*/
async processFile(_file, _config) {
throw new Error('Direct file processing is not supported by Web Speech API. Use start() with file source instead.');
}
/**
* Process audio stream (not supported by Web Speech API)
*/
async processStream(_stream, _config) {
throw new Error('Direct stream processing is not supported by Web Speech API. Use start() with stream source instead.');
}
/**
* Get available audio input devices
*/
static async getAudioDevices() {
if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
return [];
}
try {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === 'audioinput');
}
catch (error) {
console.error('Failed to enumerate audio devices:', error);
return [];
}
}
/**
* Check browser compatibility
*/
static getBrowserSupport() {
const features = [];
let supported = false;
if (typeof window !== 'undefined') {
if (window.SpeechRecognition || window.webkitSpeechRecognition) {
features.push('SpeechRecognition');
supported = true;
}
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
features.push('getUserMedia');
}
if (window.MediaRecorder) {
features.push('MediaRecorder');
}
}
return { supported, features };
}
}
/**
* Check browser support for speech recognition and related APIs
*/
function getBrowserSupport() {
if (typeof window === 'undefined') {
return {
webSpeechAPI: false,
mediaRecorder: false,
getUserMedia: false,
browser: {
name: 'unknown',
version: 'unknown'
}
};
}
const browserInfo = getBrowserInfo();
return {
webSpeechAPI: 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window,
mediaRecorder: 'MediaRecorder' in window,
getUserMedia: 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices,
browser: browserInfo
};
}
/**
* Get browser name and version
*/
function getBrowserInfo() {
if (typeof navigator === 'undefined') {
return { name: 'unknown', version: 'unknown' };
}
const userAgent = navigator.userAgent;
let name = 'unknown';
let version = 'unknown';
// Chrome
if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) {
name = 'Chrome';
const match = userAgent.match(/Chrome\/(\d+)/);
if (match)
version = match[1];
}
// Safari
else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
name = 'Safari';
const match = userAgent.match(/Version\/(\d+)/);
if (match)
version = match[1];
}
// Firefox
else if (userAgent.includes('Firefox')) {
name = 'Firefox';
const match = userAgent.match(/Firefox\/(\d+)/);
if (match)
version = match[1];
}
// Edge
else if (userAgent.includes('Edg')) {
name = 'Edge';
const match = userAgent.match(/Edg\/(\d+)/);
if (match)
version = match[1];
}
return { name, version };
}
const useVoiceToText = (options = {}) => {
const [isInitialized, setIsInitialized] = useState(false);
const [isRecording, setIsRecording] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const [results, setResults] = useState([]);
const [error, setError] = useState(null);
const [browserSupport, setBrowserSupport] = useState(null);
const [config, setConfig] = useState({
defaultRecognitionConfig: {
language: 'en-US',
continuous: true,
interimResults: true
},
debug: false,
...options
});
const [engine, setEngine] = useState(null);
// Initialize the engine
useEffect(() => {
const initialize = async () => {
try {
// Check browser support
const support = getBrowserSupport();
setBrowserSupport(support);
if (!support.webSpeechAPI) {
throw new Error('Speech recognition is not supported in this browser');
}
// Initialize Web Speech engine
const speechEngine = new WebSpeechEngine();
await speechEngine.initialize(config.defaultRecognitionConfig);
// Set up event listeners
speechEngine.on('result', (result) => {
setResults(prev => [...prev, result]);
options.onResult?.(result);
});
speechEngine.on('error', (error) => {
const errorMessage = error.message || 'Speech recognition error';
setError(errorMessage);
options.onError?.(errorMessage);
});
speechEngine.on('start', () => {
setIsRecording(true);
options.onStart?.();
});
speechEngine.on('end', () => {
setIsRecording(false);
options.onStop?.();
});
setEngine(speechEngine);
setIsInitialized(true);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize voice recognition';
setError(errorMessage);
options.onError?.(errorMessage);
}
};
initialize();
// Cleanup
return () => {
if (engine) {
engine.cleanup();
}
};
}, []);
const startRecording = useCallback(async (recordingOptions) => {
if (!engine || !isInitialized) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
setError(null);
setResults([]);
const finalConfig = {
...config.defaultRecognitionConfig,
...recordingOptions
};
await engine.startListening(finalConfig);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to start recording';
setError(errorMessage);
options.onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
}, [engine, isInitialized, config.defaultRecognitionConfig, options]);
const stopRecording = useCallback(async () => {
if (!engine) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
await engine.stopListening();
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to stop recording';
setError(errorMessage);
options.onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
}, [engine, options]);
const clearResults = useCallback(() => {
setResults([]);
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
const updateConfig = useCallback((newConfig) => {
setConfig(prev => ({ ...prev, ...newConfig }));
}, []);
const getConfig = useCallback(() => config, [config]);
return {
isInitialized,
isRecording,
isProcessing,
results,
error,
browserSupport,
startRecording,
stopRecording,
clearResults,
clearError,
updateConfig,
getConfig
};
};
const useVoiceRecorder = (options = {}) => {
const { language = 'en-US', continuous = true, interimResults = true, onResult, onError, onStart, onStop } = options;
const voiceToText = useVoiceToText({
defaultRecognitionConfig: {
language,
continuous,
interimResults
},
onResult,
onError,
onStart,
onStop
});
const startRecording = useCallback(async () => {
await voiceToText.startRecording({
language,
continuous,
interimResults
});
}, [voiceToText, language, continuous, interimResults]);
const stopRecording = useCallback(async () => {
await voiceToText.stopRecording();
}, [voiceToText]);
return {
isInitialized: voiceToText.isInitialized,
isRecording: voiceToText.isRecording,
isProcessing: voiceToText.isProcessing,
results: voiceToText.results,
error: voiceToText.error,
startRecording,
stopRecording,
clearResults: voiceToText.clearResults,
clearError: voiceToText.clearError
};
};
const useFileUpload = (options = {}) => {
const { acceptedFormats = ['audio/*'], maxFileSize = 50 * 1024 * 1024, // 50MB
onFileSelect, onConvert, onError } = options;
const [selectedFile, setSelectedFile] = useState(null);
const [isConverting, setIsConverting] = useState(false);
const [results, setResults] = useState([]);
const [error, setError] = useState(null);
const validateFile = useCallback((file) => {
// Validate file size
if (file.size > maxFileSize) {
return {
isValid: false,
error: `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum allowed size (${(maxFileSize / 1024 / 1024).toFixed(2)}MB)`
};
}
// Validate file type
const isValidType = acceptedFormats.some(format => {
if (format === 'audio/*') {
return file.type.startsWith('audio/');
}
return file.type === format;
});
if (!isValidType) {
return {
isValid: false,
error: `File type ${file.type} is not supported. Please select an audio file.`
};
}
return { isValid: true };
}, [acceptedFormats, maxFileSize]);
const selectFile = useCallback((file) => {
const validation = validateFile(file);
if (!validation.isValid) {
setError(validation.error || 'Unknown validation error');
onError?.(validation.error);
return;
}
setSelectedFile(file);
setError(null);
onFileSelect?.(file);
}, [validateFile, onFileSelect, onError]);
const convertFile = useCallback(async (language = 'en-US') => {
if (!selectedFile) {
const error = 'No file selected';
setError(error);
onError?.(error);
return;
}
setIsConverting(true);
setError(null);
try {
const result = await onConvert?.(selectedFile, language);
if (result) {
setResults(prev => [...prev, result]);
}
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to convert file';
setError(errorMessage);
onError?.(errorMessage);
}
finally {
setIsConverting(false);
}
}, [selectedFile, onConvert, onError]);
const clearFile = useCallback(() => {
setSelectedFile(null);
}, []);
const clearResults = useCallback(() => {
setResults([]);
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return {
selectedFile,
isConverting,
results,
error,
selectFile,
convertFile,
clearFile,
clearResults,
clearError,
validateFile
};
};
const useSpeechRecognition = (options = {}) => {
const { language = 'en-US', continuous = true, interimResults = true, maxAlternatives = 1, onResult, onError, onStart, onEnd, onAudioStart, onAudioEnd, onSoundStart, onSoundEnd, onSpeechStart, onSpeechEnd, onNoMatch, onNomatch } = options;
const [isSupported, setIsSupported] = useState(false);
const [isListening, setIsListening] = useState(false);
const [transcript, setTranscript] = useState('');
const [finalTranscript, setFinalTranscript] = useState('');
const [interimTranscript, setInterimTranscript] = useState('');
const [error, setError] = useState(null);
const recognitionRef = useRef(null);
const finalTranscriptRef = useRef('');
// Check browser support and initialize
useEffect(() => {
const support = getBrowserSupport();
setIsSupported(support.webSpeechAPI);
if (support.webSpeechAPI) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognitionRef.current = new SpeechRecognition();
const recognition = recognitionRef.current;
// Configure recognition
recognition.continuous = continuous;
recognition.interimResults = interimResults;
recognition.lang = language;
recognition.maxAlternatives = maxAlternatives;
// Set up event listeners
recognition.onresult = (event) => {
let interimTranscript = '';
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript;
}
else {
interimTranscript += transcript;
}
}
finalTranscriptRef.current = finalTranscript;
setFinalTranscript(finalTranscript);
setInterimTranscript(interimTranscript);
setTranscript(finalTranscript + interimTranscript);
onResult?.(event);
};
recognition.onerror = (event) => {
setError(event.error);
setIsListening(false);
onError?.(event);
};
recognition.onstart = () => {
setIsListening(true);
setError(null);
onStart?.();
};
recognition.onend = () => {
setIsListening(false);
onEnd?.();
};
recognition.onaudiostart = onAudioStart;
recognition.onaudioend = onAudioEnd;
recognition.onsoundstart = onSoundStart;
recognition.onsoundend = onSoundEnd;
recognition.onspeechstart = onSpeechStart;
recognition.onspeechend = onSpeechEnd;
recognition.onnomatch = onNomatch || onNoMatch;
}
// Cleanup
return () => {
if (recognitionRef.current) {
recognitionRef.current.abort();
}
};
}, [continuous, interimResults, language, maxAlternatives, onResult, onError, onStart, onEnd, onAudioStart, onAudioEnd, onSoundStart, onSoundEnd, onSpeechStart, onSpeechEnd, onNoMatch, onNomatch]);
const start = useCallback(() => {
if (!isSupported || !recognitionRef.current) {
setError('Speech recognition is not supported');
return;
}
try {
recognitionRef.current.start();
}
catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start recognition');
}
}, [isSupported]);
const stop = useCallback(() => {
if (recognitionRef.current) {
recognitionRef.current.stop();
}
}, []);
const abort = useCallback(() => {
if (recognitionRef.current) {
recognitionRef.current.abort();
}
}, []);
const reset = useCallback(() => {
setTranscript('');
setFinalTranscript('');
setInterimTranscript('');
setError(null);
finalTranscriptRef.current = '';
}, []);
const updateConfig = useCallback((config) => {
if (recognitionRef.current) {
if (config.language !== undefined) {
recognitionRef.current.lang = config.language;
}
if (config.continuous !== undefined) {
recognitionRef.current.continuous = config.continuous;
}
if (config.interimResults !== undefined) {
recognitionRef.current.interimResults = config.interimResults;
}
if (config.maxAlternatives !== undefined) {
recognitionRef.current.maxAlternatives = config.maxAlternatives;
}
}
}, []);
return {
isSupported,
isListening,
transcript,
finalTranscript,
interimTranscript,
error,
start,
stop,
abort,
reset,
updateConfig
};
};
export { useFileUpload, useSpeechRecognition, useVoiceRecorder, useVoiceToText };
//# sourceMappingURL=hooks.esm.js.map