UNPKG

react-native-davoice-tts

Version:

tts library for React Native

203 lines (183 loc) 6.39 kB
// stt/index.ts import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; // Prefer new names; gracefully fall back to legacy names during migration. const STTNative = NativeModules.STT || NativeModules.RCTSTT || NativeModules.Voice || NativeModules.RCTVoice; if (STTNative) { console.log('STTNative loaded:', Object.keys(STTNative)); } else { console.error('STT native module is not linked correctly.'); } type SpeechStartEvent = { /* keep your shape if you had TS types */ }; type SpeechEndEvent = {}; type SpeechRecognizedEvent = { isFinal: boolean }; type SpeechErrorEvent = { error: { code?: string; message?: string } }; type SpeechResultsEvent = { value: string[] }; type SpeechVolumeChangeEvent = { value: number }; type SpeechEvents = { onSpeechStart?: (e: SpeechStartEvent) => void; onSpeechRecognized?: (e: SpeechRecognizedEvent) => void; onSpeechEnd?: (e: SpeechEndEvent) => void; onSpeechError?: (e: SpeechErrorEvent) => void; onSpeechResults?: (e: SpeechResultsEvent) => void; onSpeechPartialResults?: (e: SpeechResultsEvent) => void; onSpeechVolumeChanged?: (e: SpeechVolumeChangeEvent) => void; }; const sttEmitter = Platform.OS !== 'web' && STTNative ? new NativeEventEmitter(STTNative) : null; type SpeechEventName = keyof Required<SpeechEvents>; class RCTSTT { private _loaded: boolean; private _listeners: Array<{ remove: () => void }> | null; private _events: Required<SpeechEvents>; constructor() { this._loaded = false; this._listeners = null; this._events = { onSpeechStart: () => {}, onSpeechRecognized: () => {}, onSpeechEnd: () => {}, onSpeechError: () => {}, onSpeechResults: () => {}, onSpeechPartialResults: () => {}, onSpeechVolumeChanged: () => {}, }; } /** Remove static handlers on the native module (parity with old Voice bridge). */ removeAllListeners() { if (!STTNative) return; STTNative.onSpeechStart = undefined; STTNative.onSpeechRecognized = undefined; STTNative.onSpeechEnd = undefined; STTNative.onSpeechError = undefined; STTNative.onSpeechResults = undefined; STTNative.onSpeechPartialResults = undefined; STTNative.onSpeechVolumeChanged = undefined; } /** Destroy/native teardown (parity with old API). */ destroy(): Promise<void> { if (!this._loaded && !this._listeners) { return Promise.resolve(); } return new Promise((resolve, reject) => { if (!STTNative?.destroySpeech) { // Fallback: tolerate older native this._listeners?.forEach(l => l.remove?.()); this._listeners = null; resolve(); return; } STTNative.destroySpeech((error: string) => { if (error) { reject(new Error(error)); } else { this._listeners?.forEach(l => l.remove?.()); this._listeners = null; resolve(); } }); }); } /** Start recognition — mirrors old `.start(locale, options?)` */ start(locale: string, options: Record<string, any> = {}) { if (!this._loaded && !this._listeners && sttEmitter) { this._listeners = (Object.keys(this._events) as SpeechEventName[]).map( (key: SpeechEventName) => sttEmitter.addListener(key, this._events[key]), ); } return new Promise<void>((resolve, reject) => { const cb = (error: string) => (error ? reject(new Error(error)) : resolve()); if (Platform.OS === 'android' && STTNative?.startSpeech) { // Keep Android parity with your previous voice bridge contract STTNative.startSpeech( locale, { EXTRA_LANGUAGE_MODEL: 'LANGUAGE_MODEL_FREE_FORM', EXTRA_MAX_RESULTS: 5, EXTRA_PARTIAL_RESULTS: true, REQUEST_PERMISSIONS_AUTO: true, ...options, }, cb, ); } else if (STTNative?.startSpeech) { STTNative.startSpeech(locale, cb); } else if (STTNative?.start) { STTNative.start(locale, cb); } else { reject(new Error('STTNative.startSpeech not available')); } }); } stop(): Promise<void> { if (!this._loaded && !this._listeners) return Promise.resolve(); return new Promise((resolve, reject) => { if (!STTNative?.stopSpeech) return resolve(); STTNative.stopSpeech((error: string) => { error ? reject(new Error(error)) : resolve(); }); }); } cancel(): Promise<void> { if (!this._loaded && !this._listeners) return Promise.resolve(); return new Promise((resolve, reject) => { if (!STTNative?.cancelSpeech) return resolve(); STTNative.cancelSpeech((error: string) => { error ? reject(new Error(error)) : resolve(); }); }); } isAvailable(): Promise<0 | 1> { return new Promise((resolve, reject) => { if (!STTNative?.isSpeechAvailable) { // Best effort fallback resolve(1); return; } STTNative.isSpeechAvailable((isAvailable: 0 | 1, error: string) => { error ? reject(new Error(error)) : resolve(isAvailable); }); }); } isRecognizing(): Promise<0 | 1> { return new Promise(resolve => { if (!STTNative?.isRecognizing) return resolve(0); STTNative.isRecognizing((isRecognizing: 0 | 1) => resolve(isRecognizing)); }); } // Event setters (unchanged names so your app code stays the same) set onSpeechStart(fn: (e: SpeechStartEvent) => void) { this._events.onSpeechStart = fn; } set onSpeechRecognized(fn: (e: SpeechRecognizedEvent) => void) { this._events.onSpeechRecognized = fn; } set onSpeechEnd(fn: (e: SpeechEndEvent) => void) { this._events.onSpeechEnd = fn; } set onSpeechError(fn: (e: SpeechErrorEvent) => void) { this._events.onSpeechError = fn; } set onSpeechResults(fn: (e: SpeechResultsEvent) => void) { this._events.onSpeechResults = fn; } set onSpeechPartialResults(fn: (e: SpeechResultsEvent) => void) { this._events.onSpeechPartialResults = fn; } set onSpeechVolumeChanged(fn: (e: SpeechVolumeChangeEvent) => void) { this._events.onSpeechVolumeChanged = fn; } } export type { SpeechEndEvent, SpeechErrorEvent, SpeechEvents, SpeechStartEvent, SpeechRecognizedEvent, SpeechResultsEvent, SpeechVolumeChangeEvent, }; export default new RCTSTT();