UNPKG

ndf-elements

Version:

My collection of useful custom elements.

130 lines (99 loc) 4.08 kB
/** * Speech synthesis using the Web Speech API. * * @copyright © Nick Freear, 10-Jan-2022. * * @see https://codepen.io/nfreear/pen/eYKzbwJ * @see https://wicg.github.io/speech-api/#tts-section * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API * @see https://translate.google.com/?sl=auto&tl=zh-CN&text=Hello%20world.&op=translate; * * @was * <my-speech> * <my-speech-synthesis> - MySpeechSynthesisElement * <my-text-to-speech> - MyTextToSpeechElement * * @status experimental * @since 1.X.0 */ import { MyElement } from '../MyElement.js'; const { speechSynthesis, SpeechSynthesisUtterance } = window; export class MyTextToSpeechElement extends MyElement { static getTag () { return 'my-text-to-speech'; // Was: 'my-speech-synthesis'; } /* constructor () { // "Useless constructor"! super(); } */ async connectedCallback () { const langRegex = this.getAttribute('lang-regex') || 'en.*'; const voxRegex = this.getAttribute('vox-regex') || '(Fiona|Goo*UK Eng*Female)'; const pitch = parseFloat(this.getAttribute('pitch')) || 1; // Default: 1; Range: 0-2; const rate = parseFloat(this.getAttribute('rate')) || 1; // Default: 1; Range: 0.1-10; const volume = parseFloat(this.getAttribute('volume')) || 1; // Range: 0-1; await this._initialize({ langRegex, voxRegex, pitch, rate, volume }); } async _initialize (ATTR = {}) { await this.getTemplate('my-text-to-speech'); const utterElem = this.shadowRoot.querySelector('#utterance'); const voxSelect = this.shadowRoot.querySelector('#vox select'); const button = this.shadowRoot.querySelector('button'); const log = this.shadowRoot.querySelector('#log'); this.$$ = { ...ATTR, utterElem, voxSelect, button, log }; button.addEventListener('click', ev => this._speak(null, ev)); speechSynthesis.onvoiceschanged = this._addVoicesToSelect; setTimeout(() => this._addVoicesToSelect(), 1500); console.debug('my-text-to-speech:', this); console.dir(this); } _speak (say = null, ev = null) { const ATTR = this.$$; const SAY = this._getText(say); const VOX = this._findVoiceByName(ATTR.voxRegex); // /(Fiona|Goo*UK Eng*Female)/); const utterThis = new SpeechSynthesisUtterance(SAY); utterThis.lang = this.lang || 'en'; utterThis.pitch = ATTR.pitch; utterThis.rate = ATTR.rate; utterThis.volume = ATTR.volume; utterThis.voice = VOX; speechSynthesis.speak(utterThis); console.debug('Speak:', SAY, utterThis, VOX, ev); } _getText (say = null) { const TEXT = this.textContent.replace(/[ \n]{2,}/g, '\n'); const EL = this.$$.utterElem; // this.shadowRoot.querySelector('#utterance'); // console.debug('Utter:', ELEM, ELEM.innerHTML, ELEM.content); // console.dir(this); return say || TEXT || EL.textContent; // .$$.utterElem.innerText; // .textContent } _filterVoicesByLang (langPattern = 'en.*') { const langRegex = new RegExp(langPattern, 'i'); const VOX = speechSynthesis.getVoices(); return VOX.filter(vox => langRegex.test(vox.lang)); } _findVoiceByName (namePattern) { const nameRegex = new RegExp(namePattern, 'i'); const VOX = speechSynthesis.getVoices(); return VOX.find(vox => nameRegex.test(vox.name)); } _addVoicesToSelect () { const ALL = /\?vox=all/.test(document.location.href); const SELECT = this.$$.voxSelect; // document.querySelector('#vox'); const LOG = this.$$.log; // document.querySelector('#log'); const VOICES = this._filterVoicesByLang(ALL ? '.*' : this.$$.langRegex); if (SELECT.children.length) { console.debug('Voices already loaded'); return; } const VA = []; VOICES.forEach(vox => { const OPT = document.createElement('option'); OPT.value = vox.voiceURI; OPT.textContent = `${vox.name}\t(${vox.lang})`; SELECT.appendChild(OPT); VA.push(OPT.textContent); }); LOG.textContent = `Voices: ${VA.length}\n${VA.join('\n')}`; console.debug('Voices:', SELECT, VOICES); } }