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
JavaScript
"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