import { PureComponent } from 'react'; import PropTypes from 'prop-types'; import logger from '/imports/startup/client/logger'; import { throttle } from 'radash'; import Service from './service'; const THROTTLE_TIMEOUT = 200; class Speech extends PureComponent { constructor(props) { super(props); this.onEnd = this.onEnd.bind(this); this.onError = this.onError.bind(this); this.onResult = this.onResult.bind(this); this.result = { id: Service.generateId(), transcript: '', isFinal: true, }; this.idle = true; this.speechRecognition = Service.initSpeechRecognition(props.setUserSpeechLocale); if (this.speechRecognition) { this.speechRecognition.onend = () => this.onEnd(); this.speechRecognition.onerror = (event) => this.onError(event); this.speechRecognition.onresult = (event) => this.onResult(event); } this.throttledTranscriptUpdate = throttle( { interval: THROTTLE_TIMEOUT }, props.captionSubmitText ); } componentDidUpdate(prevProps) { const { locale, connected, talking, } = this.props; // Connected if (!prevProps.connected && connected) { this.start(locale); } // Disconnected if (prevProps.connected && !connected) { this.stop(); } // Switch locale if (prevProps.locale !== locale) { if (prevProps.connected && connected) { this.stop(); this.start(locale); } } // Recovery from idle if (!prevProps.talking && talking) { if (prevProps.connected && connected) { if (this.idle) { this.start(locale); } } } } componentWillUnmount() { this.stop(); } onEnd() { this.stop(); } onError(event) { this.stop(); logger.error({ logCode: 'captions_speech_recognition', extraInfo: { error: event.error, message: event.message, }, }, 'Captions speech recognition error'); } onResult(event) { const { resultIndex, results, } = event; const { id } = this.result; const { transcript } = results[resultIndex][0]; const { isFinal } = results[resultIndex]; this.result.transcript = transcript; this.result.isFinal = isFinal; const { locale, captionSubmitText } = this.props; if (isFinal) { captionSubmitText(id, transcript, locale, true); this.result.id = Service.generateId(); } else { this.throttledTranscriptUpdate(id, transcript, locale, false); } } start(locale) { if (this.speechRecognition && Service.isLocaleValid(locale)) { this.speechRecognition.lang = locale; try { this.result.id = Service.generateId(); this.speechRecognition.start(); this.idle = false; } catch (event) { this.onError(event); } } } stop() { this.idle = true; if (this.speechRecognition) { const { isFinal, transcript, } = this.result; if (!isFinal) { const { locale } = this.props; const { id } = this.result; Service.updateFinalTranscript(id, transcript, locale); this.speechRecognition.abort(); } else { this.speechRecognition.stop(); } } } render() { return null; } } Speech.propTypes = { locale: PropTypes.string.isRequired, connected: PropTypes.bool.isRequired, talking: PropTypes.bool.isRequired, setUserSpeechLocale: PropTypes.func.isRequired, }; export default Speech;