Jak používat rozhraní API syntézy řeči HTML5 k přidávání textu do řeči do aplikace s několika hlasovými možnostmi.
Začínáme
Pro tento tutoriál použijeme full-stack JavaScriptový framework CheatCode, Joystick. Joystick spojuje rozhraní front-end UI s back-endem Node.js pro vytváření aplikací.
Pro začátek budeme chtít nainstalovat Joystick přes NPM. Před instalací se ujistěte, že používáte Node.js 16+, abyste zajistili kompatibilitu (pokud se potřebujete naučit, jak nainstalovat Node.js nebo spustit více verzí na vašem počítači, přečtěte si nejprve tento tutoriál):
Terminál
npm i -g @joystick.js/cli
Tím se Joystick nainstaluje globálně do vašeho počítače. Po instalaci vytvořte nový projekt:
Terminál
joystick create app
Po několika sekundách se zobrazí zpráva o odhlášení na cd
do nového projektu a spusťte joystick start
:
Terminál
cd app && joystick start
Poté by vaše aplikace měla být spuštěna a my jsme připraveni začít.
Přidání Bootstrap
Nejprve se ponoříme do kódu a chceme do naší aplikace přidat framework Bootstrap CSS. Zatímco vy nemáte Abychom toho dosáhli, naše aplikace bude vypadat o něco hezčí a nebudeme muset pro naše uživatelské rozhraní míchat CSS. Chcete-li to provést, přidáme odkaz Bootstrap CDN do /index.html
soubor v kořenovém adresáři našeho projektu:
/index.html
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<title>Joystick</title>
<meta name="description" content="An awesome JavaScript app that's under development.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#FFCC00">
<link rel="apple-touch-icon" href="/apple-touch-icon-152x152.png">
<link rel="stylesheet" href="/_joystick/index.css">
<link rel="manifest" href="/manifest.json">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
${css}
</head>
<body>
...
</body>
</html>
Zde, těsně nad ${css}
část v souboru jsme vložili do <link></link>
tag z dokumentace Bootstrap, který nám poskytuje přístup k CSS části rámce.
A je to. Joystick se automaticky restartuje a načte jej do prohlížeče, abychom jej mohli začít používat.
Zapojení komponenty joysticku s převodem textu na řeč
V aplikaci Joystick je naše uživatelské rozhraní vytvořeno pomocí vestavěné knihovny uživatelského rozhraní rámce @joystick.js/ui
. Když jsme spustili joystick create app
výše jsme dostali několik příkladů komponent, se kterými jsme mohli pracovat. Přepíšeme /ui/pages/index/index.js
soubor s nějakým HTML, který bude sloužit jako uživatelské rozhraní našeho překladače.
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const Index = ui.component({
css: `
h4 {
border-bottom: 1px solid #eee;
padding-bottom: 20px;
margin-bottom: 40px;
}
textarea {
margin-bottom: 40px;
}
`,
render: () => {
return `
<div>
<h4>Text to Speech Translator</h4>
<form>
<textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
<button class="btn btn-primary">Speak</button>
</form>
<div class="players"></div>
</div>
`;
},
});
export default Index;
Chcete-li začít, chceme nahradit komponentu, která byla v tomto souboru, tím, co vidíme výše. Zde definujeme jednoduchou komponentu se dvěma věcmi:render
funkce, která vrací řetězec HTML, který chceme zobrazit v prohlížeči a nad ním, řetězec css
které chceme použít na HTML, který vykreslujeme (Joystick automaticky upraví rozsah CSS, který zde předáme, na HTML vrácený naším render
funkce).
Pokud načteme http://localhost:2600
v prohlížeči (port 2600
je místo, kde se joystick standardně spouští, když spustíme joystick start
), měli bychom vidět verzi HTML ve stylu Bootstrap výše.
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const Index = ui.component({
events: {
'submit form': (event, component) => {
event.preventDefault();
const text = event?.target?.textToTranslate?.value;
const hasText = text.trim() !== '';
if (!hasText) {
return component.methods.speak('Well you have to say something!');
}
component.methods.speak(text);
},
},
css: `...`,
render: () => {
return `
<div>
<h4>Text to Speech Translator</h4>
<form>
<textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
<button class="btn btn-primary">Speak</button>
</form>
<div class="players"></div>
</div>
`;
},
});
export default Index;
Dále chceme přidat events
námitky proti naší složce. Jak název napovídá, zde definujeme posluchače událostí pro naši komponentu. Zde definujeme posluchače pro submit
událost na <form></form>
prvek vykreslovaný naší komponentou. Stejně jako naše CSS, i Joystick automaticky přiřazuje naše události k vykreslovanému HTML.
Přiřazeno k tomu submit form
vlastnost na našem events
object je funkce, která bude volána vždy, když je detekována událost odeslání na našem <form></form>
.
Uvnitř této funkce nejprve vezmeme event
(toto je událost DOM prohlížeče) jako první argument a okamžitě zavolejte event.preventDefault()
na to. To zabrání prohlížeči v pokusu o provedení HTTP POST
na action
atribut na našem formuláři. Jak název napovídá, toto je výchozí chování pro prohlížeče (nemáme action
atribut na našem formuláři, protože chceme kontrolovat odesílání pomocí JavaScriptu).
Dále, jakmile je toto zastaveno, chceme získat hodnotu zadanou do našeho <textarea></textarea>
. K tomu můžeme použít textToTranslate
vlastnost na event.target
objekt. Zde event.target
odkazuje na <form></form>
prvek tak, jak je vykreslen v prohlížeči (jeho reprezentace v paměti).
Máme přístup k textToTranslate
protože prohlížeč mu v paměti automaticky přiřadí všechna pole ve formuláři pomocí name
pole atribut jako název vlastnosti. Když se podíváme zblízka na naše <textarea></textarea>
, můžeme vidět, že má name
atribut textToTranslate
. Pokud toto změníme na pizza
, napsali bychom event?.target?.pizza?.value
místo toho.
S touto hodnotou uloženou v text
proměnná, dále vytvoříme další proměnnou hasText
který obsahuje kontrolu, abychom se ujistili, že naše text
proměnná není prázdný řetězec (.trim()
část zde "ořezává" všechny mezery v případě, že uživatel znovu a znovu mačká mezerník).
Pokud ve vstupu nemáme žádný text, chceme "namluvit" frázi "No, musíš něco říct!" Za předpokladu, že jsme udělali získat nějaký text, chceme jen "mluvit" to text
hodnotu.
Všimněte si, že zde voláme na číslo component.methods.speak
které jsme ještě nedefinovali. Použijeme methods
joysticku feature (kde můžeme definovat různé funkce na naší komponentě).
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const Index = ui.component({
methods: {
speak: (text = '') => {
window.speechSynthesis.cancel();
const message = new SpeechSynthesisUtterance(text);
speechSynthesis.speak(message);
},
},
events: {
'submit form': (event, component) => {
event.preventDefault();
const text = event?.target?.textToTranslate?.value;
const hasText = text.trim() !== '';
if (!hasText) {
return component.methods.speak('Well you have to say something!');
}
component.methods.speak(text);
},
},
css: `...`,
render: () => {
return `
<div>
<h4>Text to Speech Translator</h4>
<form>
<textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
<button class="btn btn-primary">Speak</button>
</form>
<div class="players"></div>
</div>
`;
},
});
export default Index;
Nyní k té zábavnější části. Protože API syntézy řeči je implementováno v prohlížečích (viz kompatibilita – je docela dobrá), nemusíme nic instalovat ani importovat; celé API je globálně přístupné v prohlížeči.
Přidání methods
objekt těsně nad naším events
, přiřadíme speak
metodu, kterou jsme zavolali z našeho submit form
obsluha události.
Uvnitř toho není moc co dělat:
- V případě, že změníme text, který jsme zadali, a klikneme na tlačítko „Hovořit“ uprostřed přehrávání, chceme zavolat
window.speechSynthesis.cancel()
metoda, která sdělí rozhraní API, aby vyčistilo frontu přehrávání. Pokud to neuděláme, pouze přidá přehrávání do své fronty a bude pokračovat v přehrávání toho, co jsme mu předali (i po obnovení prohlížeče). - Vytvořte instanci
SpeechSynthesisUtterance()
což je třída, která přebírá text, o kterém chceme mluvit. - Předejte tuto instanci do
speechSynthesis.speak()
metoda.
A je to. Jakmile do pole napíšeme nějaký text a stiskneme „Speak“, váš prohlížeč (za předpokladu, že podporuje API) by měl začít blábolit.
Úžasný. Ale ještě jsme neskončili. Věřte nebo ne, API Synthesis Speech obsahuje také možnost používat různé hlasy. Dále budeme aktualizovat HTML vrácené naším render
funkce zahrnout seznam hlasů, ze kterých si můžete vybrat, a aktualizovat methods.speak
přijímat různé hlasy.
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const Index = ui.component({
state: {
voices: [],
},
lifecycle: {
onMount: (component) => {
window.speechSynthesis.onvoiceschanged = () => {
const voices = window.speechSynthesis.getVoices();
component.setState({ voices });
};
},
},
methods: {
getLanguageName: (language = '') => {
if (language) {
const regionNamesInEnglish = new Intl.DisplayNames(['en'], { type: 'region' });
return regionNamesInEnglish.of(language?.split('-').pop());
}
return 'Unknown';
},
speak: (text = '', voice = '', component) => {
window.speechSynthesis.cancel();
const message = new SpeechSynthesisUtterance(text);
if (voice) {
const selectedVoice = component?.state?.voices?.find((voiceOption) => voiceOption?.voiceURI === voice);
message.voice = selectedVoice;
}
speechSynthesis.speak(message);
},
},
events: {
'submit form': (event, component) => {
event.preventDefault();
const text = event?.target?.textToTranslate?.value;
const voice = event?.target?.voice?.value;
const hasText = text.trim() !== '';
if (!hasText) {
return component.methods.speak('Well you have to say something!', voice);
}
component.methods.speak(text, voice);
},
},
css: `
h4 {
border-bottom: 1px solid #eee;
padding-bottom: 20px;
margin-bottom: 40px;
}
select {
margin-bottom: 20px;
}
textarea {
margin-bottom: 40px;
}
`,
render: ({ state, each, methods }) => {
return `
<div>
<h4>Text to Speech Translator</h4>
<form>
<label class="form-label">Voice</label>
<select class="form-control" name="voice">
${each(state?.voices, (voice) => {
return `
<option value="${voice.voiceURI}">${voice.name} (${methods.getLanguageName(voice.lang)})</option>
`;
})}
</select>
<textarea class="form-control" name="textToTranslate" placeholder="Type the text to speak here and then press Speak below."></textarea>
<button class="btn btn-primary">Speak</button>
</form>
<div class="players"></div>
</div>
`;
},
});
export default Index;
Abychom to urychlili, vypsali jsme zbytek kódu, který budeme potřebovat výše – pojďme si to projít.
Za prvé, abychom získali přístup k dostupným hlasům nabízeným rozhraním API, musíme počkat, až se načtou do prohlížeče. Nad naším methods
jsme do naší komponenty lifecycle
přidali další možnost a tomu jsme přiřadili onMount()
funkce.
Tato funkce je volána joystickem ihned po připojení naší komponenty k DOM. Je to dobrý způsob, jak spouštět kód, který je závislý na uživatelském rozhraní, nebo, jako v tomto případě, způsob, jak naslouchat a zpracovávat globální události nebo události na úrovni prohlížeče (na rozdíl od událostí generovaných kódem HTML vykresleným naší komponentou).
Než však získáme hlasy, musíme naslouchat window.speechSynthesis.onvoiceschanged
událost. Tato událost se spustí, jakmile jsou načteny hlasy (hovoříme o zlomcích sekund, ale dostatečně pomalu na to, abychom chtěli čekat na úrovni kódu).
Uvnitř onMount
, tuto hodnotu přiřadíme funkci, která bude volána, když se událost spustí na window
. Uvnitř této funkce voláme window.speechSynthesis.getVoices()
funkce, která nám vrátí seznam objektů popisujících všechny dostupné hlasy. Takže to můžeme použít v našem uživatelském rozhraní, vezmeme component
argument předán do onMount
funkci a zavolejte její setState()
funkce, předávání objektu s vlastností voices
.
Protože chceme přiřadit hodnotu stavu voices
na obsah proměnné const voices
zde můžeme přeskočit psaní component.setState({ voices: voices })
a použijte pouze zkrácenou verzi.
Důležité :nahoře nad lifecycle
možnost, všimněte si, že jsme přidali další možnost state
nastavit na objekt a na tomto objektu vlastnost voices
nastavit na prázdné pole. Toto je výchozí hodnota pro naše voices
pole, které vstoupí do hry příště v našem render
funkce.
Zde vidíme, že jsme aktualizovali náš render
funkce k použití destrukcí JavaScriptu, abychom mohli „vytrhnout“ vlastnosti z argumentu, který je předán – instance komponenty – pro použití v HTML, které vracíme z funkce.
Zde zavádíme state
, each
a methods
. state
a methods
jsou hodnoty, které jsme nastavili výše v komponentě. each
je to, co je známé jako "funkce vykreslení" (nezaměňovat s funkcí přiřazenou k render
možnost na naší komponentě).
Jak název napovídá, each()
se používá pro opakování nebo opakování seznamu a vrácení nějakého HTML pro každou položku v tomto seznamu.
Zde můžeme vidět použití interpolace řetězců JavaScript (označeno ${}
mezi otevřením a zavřením <select></select>
tag), abychom předali naše volání na each()
. Na each()
, předáme seznam nebo pole (v tomto případě state.voices
) jako první argument a pro druhý, funkce, která bude volána a obdrží aktuální hodnotu, která se iteruje.
Uvnitř této funkce chceme vrátit nějaké HTML, které bude na výstupu pro každý položka v state.voices
pole.
Protože jsme uvnitř <select></select>
chceme vykreslit volbu výběru pro každý z hlasů, které jsme získali z API syntézy řeči. Jak jsme zmínili výše, každý voice
je pouze objekt JavaScriptu s některými vlastnostmi. Ty, na kterých nám záleží, jsou voice.voiceURI
(jedinečné ID/název hlasu) a voice.name
(doslovné jméno mluvčího).
V neposlední řadě nám záleží také na tom, jakým jazykem se mluví. Toto je předáno jako lang
na každém voice
objekt ve formě standardního kódu jazyka ISO. Chcete-li získat „přátelskou“ reprezentaci (např. France
nebo Germany
), musíme převést kód ISO. Zde voláme metodu getLanguageName()
definované v našem methods
objekt, který přijímá voice.lang
hodnotu a převede ji na řetězec přátelský k lidem.
Když se podíváme na tuto funkci shora, vezmeme language
in jako argument (řetězec, který jsme předali z našeho each()
) a pokud to není prázdná hodnota, vytvořte instanci Intl.DisplayNames()
třída (Intl
je další globální dostupný v prohlížeči), předáváme mu pole oblastí, které chceme podporovat (protože autor je škubánek, stačí en
) a v možnostech pro druhý argument nastavením názvu type
na "region."
Výsledek je uložen v regionNamesInEnglish
, voláme na tuto proměnnou .of()
metodou, předáním language
argument předán naší funkci. Když jej předáme, zavoláme .split('-')
metodou na to říct „rozdělte tento řetězec na dva na -
znak (to znamená, že předáme en-US
dostali bychom pole jako ['en', 'US']
) a poté ve výsledném poli zavolejte .pop()
způsob, jak říct "vyndejte poslední položku a vraťte nám ji." V tomto případě je poslední položkou US
jako řetězec, což je formát předpokládaný .of()
metoda.
Ještě jeden krok. Všimněte si, že dole v našem submit form
obslužný program události, přidali jsme proměnnou pro voice
(pomocí stejné techniky k načtení její hodnoty jako u textToTranslate
) a poté jej předejte jako druhý argument našemu methods.speak()
funkce.
Zpět do této funkce přidáme voice
jako druhý argument spolu s component
jako třetí (Joystick automaticky předal component
jako poslední argument našich metod – byl by první, pokud nebyly předány žádné argumenty, nebo v tomto příkladu třetí, pokud jsou předány dva argumenty).
Do naší funkce jsme přidali if (voice)
check a uvnitř toho spustíme .find()
na state.voices
pole říct „najděte nám objekt s .voiceURI
hodnota se rovná voice
argument, který jsme předali speak
funkce (toto je en-US
řetězec nebo voice.lang
). S tím jsme jen nastavili .voice
na našem message
(SpeechSynthesisUtterance
instance třídy) a odtud přebírá API.
Hotovo! Pokud je vše na svém místě, měli bychom mít funkční překladač textu na řeč.
Zabalit
V tomto tutoriálu jsme se naučili, jak napsat komponentu pomocí @joystick.js/ui
framework, který nám pomůže vytvořit rozhraní API pro převod textu na řeč. Naučili jsme se, jak naslouchat událostem DOM a jak využít rozhraní Speech Synthesis API v prohlížeči, aby za nás mluvilo. Také jsme se dozvěděli o Intl
knihovna zabudovaná do prohlížeče, která nám pomáhá převést kód ISO pro řetězec data na jméno vhodné pro lidi. Nakonec jsme se naučili, jak dynamicky přepínat hlasy pomocí rozhraní Speech Synthesis API, abychom podporovali různé tóny a jazyky.