UNPKG

voice-to-text-converter

Version:

A modern, lightweight Node.js package for speech-to-text conversion with support for multiple engines

273 lines 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSpeechEngine = void 0; const base_engine_1 = require("./base-engine"); const types_1 = require("../types"); /** * Web Speech API engine for browser environments */ class WebSpeechEngine extends base_engine_1.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' ]; } /** * 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(types_1.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 = types_1.SpeechRecognitionErrorType.NO_SPEECH; break; case 'aborted': errorType = types_1.SpeechRecognitionErrorType.ABORTED; break; case 'audio-capture': errorType = types_1.SpeechRecognitionErrorType.AUDIO_CAPTURE; break; case 'network': errorType = types_1.SpeechRecognitionErrorType.NETWORK; break; case 'not-allowed': errorType = types_1.SpeechRecognitionErrorType.NOT_ALLOWED; break; case 'service-not-allowed': errorType = types_1.SpeechRecognitionErrorType.SERVICE_NOT_ALLOWED; break; case 'bad-grammar': errorType = types_1.SpeechRecognitionErrorType.BAD_GRAMMAR; break; case 'language-not-supported': errorType = types_1.SpeechRecognitionErrorType.LANGUAGE_NOT_SUPPORTED; break; default: errorType = types_1.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(types_1.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(filePath, 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 }; } } exports.WebSpeechEngine = WebSpeechEngine; //# sourceMappingURL=web-speech-engine.js.map