Web音声APIを使用してテキスト読み上げアプリを構築する方法
序章
何らかの音声体験を提供するアプリを操作した可能性が非常に高くなります。 テキストメッセージや通知を声に出して読むなど、テキスト読み上げ機能を備えたアプリである可能性があります。 また、SiriやGoogleアシスタントなどの音声認識機能を備えたアプリの場合もあります。
HTML5の登場により、Webプラットフォームで利用可能なAPIの数が急速に増加しました。 Web Speech API と呼ばれるAPIがいくつかあり、Web用のさまざまな種類の音声アプリケーションとエクスペリエンスをシームレスに構築できるように開発されています。 これらのAPIはまだかなり実験的なものですが、最近のすべてのブラウザーでそれらのほとんどのサポートが増えています。
この記事では、ランダムな引用を取得し、引用を表示し、ブラウザーが引用を読み上げるためにテキスト読み上げを使用する機能をユーザーに提供するアプリケーションを構築します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
このチュートリアルは、Nodev14.4.0で検証されました。 npm
v6.14.5、 axios
v0.19.2、 cors
v2.8.5、 express
v4.17.1、およびjQueryv3.5.1。
WebSpeechAPIの使用
Web Speech APIには、次の2つの主要なインターフェイスがあります。
-
SpeechSynthesis-テキスト読み上げアプリケーション用。 これにより、アプリはデバイスの音声シンセサイザーを使用してテキストコンテンツを読み上げることができます。 利用可能な音声タイプは、
SpeechSynthesisVoice
オブジェクト、発声されるテキストはで表されますSpeechSynthesisUtterance
物体。 サポートテーブルを参照してください。SpeechSynthesis
ブラウザのサポートについて詳しく知るためのインターフェース。 -
SpeechRecognition-非同期音声認識を必要とするアプリケーション向け。 これにより、アプリは音声入力から音声コンテキストを認識できます。 A
SpeechRecognition
オブジェクトは、コンストラクターを使用して作成できます。 TheSpeechGrammar
アプリが認識すべき文法のセットを表すためのインターフェースが存在します。 サポートテーブルを参照してください。SpeechRecognition
ブラウザのサポートについて詳しく知るためのインターフェース。
このチュートリアルでは、 SpeechSynthesis
.
参照を取得する
への参照を取得する SpeechSynthesis
オブジェクトは、1行のコードで実行できます。
var synthesis = window.speechSynthesis;
次のコードスニペットは、ブラウザのサポートを確認する方法を示しています。
if ('speechSynthesis' in window) {
var synthesis = window.speechSynthesis;
} else {
console.log('Text-to-speech not supported.');
}
次のことを確認すると非常に便利です SpeechSynthesis
ブラウザが提供する機能を使用する前に、ブラウザでサポートされています。
利用可能な音声の取得
このステップでは、既存のコードに基づいて、利用可能な音声音声を取得します。 The getVoices()
メソッドはのリストを返します SpeechSynthesisVoice
デバイスで使用可能なすべての音声を表すオブジェクト。
次のコードスニペットを見てください。
if ('speechSynthesis' in window) {
var synthesis = window.speechSynthesis;
// Regex to match all English language tags e.g en, en-US, en-GB
var langRegex = /^en(-[a-z]{2})?$/i;
// Get the available voices and filter the list to only have English speakers
var voices = synthesis
.getVoices()
.filter((voice) => langRegex.test(voice.lang));
// Log the properties of the voices in the list
voices.forEach(function (voice) {
console.log({
name: voice.name,
lang: voice.lang,
uri: voice.voiceURI,
local: voice.localService,
default: voice.default,
});
});
} else {
console.log('Text-to-speech not supported.');
}
コードのこのセクションでは、デバイスで使用可能な音声のリストを取得し、を使用してリストをフィルタリングします langRegex
英語を話す人だけに声を届けるための正規表現。 最後に、リスト内の音声をループして、それぞれのプロパティをコンソールに記録します。
発話の構築
このステップでは、を使用して音声発話を作成します SpeechSynthesisUtterance
コンストラクターと使用可能なプロパティの設定値。
次のコードスニペットは、テキストを読むための音声発話を作成します "Hello World"
:
if ('speechSynthesis' in window) {
var synthesis = window.speechSynthesis;
// Get the first `en` language voice in the list
var voice = synthesis.getVoices().filter(function (voice) {
return voice.lang === 'en';
})[0];
// Create an utterance object
var utterance = new SpeechSynthesisUtterance('Hello World');
// Set utterance properties
utterance.voice = voice;
utterance.pitch = 1.5;
utterance.rate = 1.25;
utterance.volume = 0.8;
// Speak the utterance
synthesis.speak(utterance);
} else {
console.log('Text-to-speech not supported.');
}
ここで、あなたは最初のものを手に入れます en
利用可能な音声のリストからの言語音声。 次に、を使用して新しい発話を作成します SpeechSynthesisUtterance
コンストラクタ。 次に、発話オブジェクトのいくつかのプロパティを次のように設定します。 voice
, pitch
, rate
、 と volume
. 最後に、それは使用して発話を話します speak()
の方法 SpeechSynthesis
.
注:発話で話すことができるテキストのサイズには制限があります。 各発話で話せるテキストの最大長は32,767文字です。
コンストラクターで発声するテキストを渡したことに注意してください。
発声するテキストを設定することもできます text
発話オブジェクトのプロパティ。
簡単な例を次に示します。
var synthesis = window.speechSynthesis;
var utterance = new SpeechSynthesisUtterance("Hello World");
// This overrides the text "Hello World" and is uttered instead
utterance.text = "My name is Glad.";
synthesis.speak(utterance);
これは、コンストラクターで渡されたテキストをオーバーライドします。
発話を話す
前のコードスニペットでは、 speak()
上のメソッド SpeechSynthesis
実例。 これで、 SpeechSynthesisUtterance
の引数としてのインスタンス speak()
発話を話す方法。
var synthesis = window.speechSynthesis;
var utterance1 = new SpeechSynthesisUtterance("Hello World");
var utterance2 = new SpeechSynthesisUtterance("My name is Glad.");
var utterance3 = new SpeechSynthesisUtterance("I'm a web developer from Nigeria.");
synthesis.speak(utterance1);
synthesis.speak(utterance2);
synthesis.speak(utterance3);
あなたがでできる他のいくつかのことがあります SpeechSynthesis
発話の一時停止、再開、キャンセルなどのインスタンス。 従って pause()
, resume()
、 と cancel()
メソッドは、 SpeechSynthesis
実例。
ステップ1—テキスト読み上げアプリを作成する
私たちはの基本的な側面を見てきました SpeechSynthesis
インターフェース。 次に、テキスト読み上げアプリケーションの作成を開始します。 始める前に、マシンにNodeとnpmがインストールされていることを確認してください。
端末で次のコマンドを実行して、アプリのプロジェクトをセットアップし、依存関係をインストールします。
新しいプロジェクトディレクトリを作成します。
- mkdir web-speech-app
新しく作成されたプロジェクトディレクトリに移動します。
- cd web-speech-app
プロジェクトを初期化します。
- npm init -y
プロジェクトに必要な依存関係をインストールします- express
, cors
、 と axios
:
- npm install express cors axios
を変更します "scripts"
のセクション package.json
次のスニペットのように見えるファイル:
"scripts": {
"start": "node server.js"
}
アプリケーションのプロジェクトを初期化したので、Expressを使用してアプリのサーバーをセットアップします。
新しいを作成します server.js
ファイルを作成し、それに次のコンテンツを追加します。
const cors = require('cors');
const path = require('path');
const axios = require('axios');
const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;
app.set('port', PORT);
// Enable CORS (Cross-Origin Resource Sharing)
app.use(cors());
// Serve static files from the /public directory
app.use('/', express.static(path.join(__dirname, 'public')));
// A simple endpoint for fetching a random quote from QuotesOnDesign
app.get('/api/quote', (req, res) => {
axios
.get(
'https://quotesondesign.com/wp-json/wp/v2/posts/?orderby=rand'
)
.then((response) => {
const [post] = response.data;
const { title, content } = post || {};
return title && content
? res.json({ status: 'success', data: { title, content } })
: res
.status(500)
.json({ status: 'failed', message: 'Could not fetch quote.' });
})
.catch((err) =>
res
.status(500)
.json({ status: 'failed', message: 'Could not fetch quote.' })
);
});
app.listen(PORT, () => console.log(`> App server is running on port ${PORT}.`));
ここでは、Expressを使用してノードサーバーをセットアップします。 を使用してCORS(クロスオリジンリクエストシェアリング)を有効にしました cors()
ミドルウェア。 また、 express.static()
から静的ファイルを提供するミドルウェア /public
プロジェクトルートのディレクトリ。 これにより、すぐに作成するインデックスページを提供できるようになります。
最後に、 GET
/api/quote
QuotesOnDesignAPIサービスからランダムな見積もりを取得するためのルート。 axios (PromiseベースのHTTPクライアントライブラリ)を使用してHTTPリクエストを作成しています。
QuotesOnDesignAPIからのサンプル応答は次のようになります。
Output[
{
"title": { "rendered": "Victor Papanek" },
"content": {
"rendered": "<p>Any attempt to separate design, to make it a thing-by-itself, works counter to the inherent value of design as the primary, underlying matrix of life.</p>\n",
"protected": false
}
}
]
注: QuotesOnDesignのAPIの変更の詳細については、4.0と5.0の間の変更を文書化したページを参照してください。
見積もりを正常に取得すると、見積もりは title
と content
で返されます data
JSON応答のフィールド。 それ以外の場合は、JSON応答の失敗 500
HTTPステータスコードが返されます。
次に、アプリビューのインデックスページを作成します。
まず、新しいを作成します public
プロジェクトのルートにあるフォルダー:
- mkdir public
次に、新しいを作成します index.html
新しく作成されたファイル public
フォルダに次のコンテンツを追加します。
<html>
<head>
<title>Daily Quotes</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body class="position-absolute h-100 w-100">
<div id="app" class="d-flex flex-wrap align-items-center align-content-center p-5 mx-auto w-50 position-relative"></div>
<script src="https://unpkg.com/jquery/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="main.js"></script>
</body>
</html>
これにより、アプリの基本的なインデックスページが1つだけ作成されます <div id="app">
これは、アプリのすべての動的コンテンツのマウントポイントとして機能します。
また、Bootstrap CDNへのリンクを追加して、アプリのデフォルトの Bootstrap4スタイルを取得しました。 また、DOM操作とAJAXリクエスト用の jQuery と、エレガントなSVGアイコン用のフェザーアイコンも含まれています。
ステップ2—メインスクリプトを作成する
これで、アプリを強化する最後の部分であるメインスクリプトにたどり着きました。 新しいを作成します main.js
のファイル public
アプリのディレクトリに次のコンテンツを追加します。
jQuery(function ($) {
let app = $('#app');
let SYNTHESIS = null;
let VOICES = null;
let QUOTE_TEXT = null;
let QUOTE_PERSON = null;
let VOICE_SPEAKING = false;
let VOICE_PAUSED = false;
let VOICE_COMPLETE = false;
let iconProps = {
'stroke-width': 1,
'width': 48,
'height': 48,
'class': 'text-secondary d-none',
'style': 'cursor: pointer'
};
function iconSVG(icon) {}
function showControl(control) {}
function hideControl(control) {}
function getVoices() {}
function resetVoice() {}
function fetchNewQuote() {}
function renderQuote(quote) {}
function renderVoiceControls(synthesis, voice) {}
function updateVoiceControls() {}
function initialize() {}
initialize();
});
このコードは jQuery
DOMがロードされたときに関数を実行します。 あなたはへの参照を取得します #app
要素を作成し、いくつかの変数を初期化します。 また、次のセクションで実装するいくつかの空の関数を宣言します。 最後に、 initialize()
アプリケーションを初期化する関数。
The iconProps
変数には、フェザーアイコンをSVGとしてDOMにレンダリングするために使用されるいくつかのプロパティが含まれています。
そのコードを配置すると、関数の実装を開始する準備が整います。 を変更します public/main.js
次の機能を実装するファイル:
// Gets the SVG markup for a Feather icon
function iconSVG(icon) {
let props = $.extend(iconProps, { id: icon });
return feather.icons[icon].toSvg(props);
}
// Shows an element
function showControl(control) {
control.addClass('d-inline-block').removeClass('d-none');
}
// Hides an element
function hideControl(control) {
control.addClass('d-none').removeClass('d-inline-block');
}
// Get the available voices, filter the list to have only English filters
function getVoices() {
// Regex to match all English language tags e.g en, en-US, en-GB
let langRegex = /^en(-[a-z]{2})?$/i;
// Get the available voices and filter the list to only have English speakers
VOICES = SYNTHESIS.getVoices()
.filter(function (voice) {
return langRegex.test(voice.lang);
})
.map(function (voice) {
return {
voice: voice,
name: voice.name,
lang: voice.lang.toUpperCase(),
};
});
}
// Reset the voice variables to the defaults
function resetVoice() {
VOICE_SPEAKING = false;
VOICE_PAUSED = false;
VOICE_COMPLETE = false;
}
The iconSVG(icon)
関数は、引数としてフェザーアイコン名の文字列を取ります(例: 'play-circle'
)そしてアイコンのSVGマークアップを返します。 フェザーのWebサイトをチェックして、使用可能なフェザーアイコンの完全なリストを確認してください。 APIの詳細については、フェザードキュメントも確認してください。
The getVoices()
関数はを使用します SYNTHESIS
デバイスで使用可能なすべての音声のリストをフェッチするオブジェクト。 次に、正規表現を使用してリストをフィルタリングし、英語を話す人だけの声を取得します。
次に、DOMで引用符をフェッチおよびレンダリングするための関数を実装します。 を変更します public/main.js
次の機能を実装するファイル:
function fetchNewQuote() {
// Clean up the #app element
app.html('');
// Reset the quote variables
QUOTE_TEXT = null;
QUOTE_PERSON = null;
// Reset the voice variables
resetVoice();
// Pick a voice at random from the VOICES list
let voice =
VOICES && VOICES.length > 0
? VOICES[Math.floor(Math.random() * VOICES.length)]
: null;
// Fetch a quote from the API and render the quote and voice controls
$.get('/api/quote', function (quote) {
renderQuote(quote.data);
SYNTHESIS && renderVoiceControls(SYNTHESIS, voice || null);
});
}
function renderQuote(quote) {
// Create some markup for the quote elements
let quotePerson = $('<h1 id="quote-person" class="mb-2 w-100"></h1>');
let quoteText = $('<div id="quote-text" class="h3 py-5 mb-4 w-100 font-weight-light text-secondary border-bottom border-gray"></div>');
// Add the quote data to the markup
quotePerson.html(quote.title.rendered);
quoteText.html(quote.content.rendered);
// Attach the quote elements to the DOM
app.append(quotePerson);
app.append(quoteText);
// Update the quote variables with the new data
QUOTE_TEXT = quoteText.text();
QUOTE_PERSON = quotePerson.text();
}
ここで fetchNewQuote()
メソッドでは、最初にアプリの要素と変数をリセットします。 次に、を使用してランダムに音声を選択します Math.random()
に保存されている音声のリストから VOICES
変数。 あなたが使う $.get()
にAJAXリクエストを送信するには /api/quote
エンドポイント。ランダムな見積もりを取得し、見積もりデータを音声コントロールと一緒にビューにレンダリングします。
The renderQuote(quote)
メソッドは引数としてquoteオブジェクトを受け取り、その内容をDOMに追加します。 最後に、quote変数を更新します。 QUOTE_TEXT
と QUOTE_PERSON
.
あなたが見れば fetchNewQuote()
関数、あなたはあなたが呼び出しを行ったことに気付くでしょう renderVoiceControls()
関数。 この機能は、音声出力の再生、一時停止、および停止のコントロールをレンダリングする役割を果たします。 また、使用中の現在の音声と言語をレンダリングします。
に次の変更を加えます public/main.js
を実装するファイル renderVoiceControls()
関数:
function renderVoiceControls(synthesis, voice) {
let controlsPane = $('<div id="voice-controls-pane" class="d-flex flex-wrap w-100 align-items-center align-content-center justify-content-between"></div>');
let voiceControls = $('<div id="voice-controls"></div>');
// Create the SVG elements for the voice control buttons
let playButton = $(iconSVG('play-circle'));
let pauseButton = $(iconSVG('pause-circle'));
let stopButton = $(iconSVG('stop-circle'));
// Helper function to enable pause state for the voice output
let paused = function () {
VOICE_PAUSED = true;
updateVoiceControls();
};
// Helper function to disable pause state for the voice output
let resumed = function () {
VOICE_PAUSED = false;
updateVoiceControls();
};
// Click event handler for the play button
playButton.on('click', function (evt) {});
// Click event handler for the pause button
pauseButton.on('click', function (evt) {});
// Click event handler for the stop button
stopButton.on('click', function (evt) {});
// Add the voice controls to their parent element
voiceControls.append(playButton);
voiceControls.append(pauseButton);
voiceControls.append(stopButton);
// Add the voice controls parent to the controlsPane element
controlsPane.append(voiceControls);
// If voice is available, add the voice info element to the controlsPane
if (voice) {
let currentVoice = $('<div class="text-secondary font-weight-normal"><span class="text-dark font-weight-bold">' + voice.name + '</span> (' + voice.lang + ')</div>');
controlsPane.append(currentVoice);
}
// Add the controlsPane to the DOM
app.append(controlsPane);
// Show the play button
showControl(playButton);
}
ここでは、音声コントロールとコントロールペインのコンテナ要素を作成します。 あなたは iconSVG()
コントロールボタンのSVGマークアップを取得し、ボタン要素も作成するために以前に作成された関数。 あなたは paused()
と resumed()
ボタンのイベントハンドラーを設定するときに使用されるヘルパー関数。
最後に、音声制御ボタンと音声情報をDOMにレンダリングします。 また、最初は再生ボタンのみが表示されるように構成されています。
次に、前のセクションで定義した音声コントロールボタンのクリックイベントハンドラーを実装します。
次のコードスニペットに示すように、イベントハンドラーを設定します。
// Click event handler for the play button
playButton.on('click', function (evt) {
evt.preventDefault();
if (VOICE_SPEAKING) {
// If voice is paused, it is resumed when the playButton is clicked
if (VOICE_PAUSED) synthesis.resume();
return resumed();
} else {
// Create utterances for the quote and the person
let quoteUtterance = new SpeechSynthesisUtterance(QUOTE_TEXT);
let personUtterance = new SpeechSynthesisUtterance(QUOTE_PERSON);
// Set the voice for the utterances if available
if (voice) {
quoteUtterance.voice = voice.voice;
personUtterance.voice = voice.voice;
}
// Set event listeners for the quote utterance
quoteUtterance.onpause = paused;
quoteUtterance.onresume = resumed;
quoteUtterance.onboundary = updateVoiceControls;
// Set the listener to activate speaking state when the quote utterance starts
quoteUtterance.onstart = function (evt) {
VOICE_COMPLETE = false;
VOICE_SPEAKING = true;
updateVoiceControls();
};
// Set event listeners for the person utterance
personUtterance.onpause = paused;
personUtterance.onresume = resumed;
personUtterance.onboundary = updateVoiceControls;
// Refresh the app and fetch a new quote when the person utterance ends
personUtterance.onend = fetchNewQuote;
// Speak the utterances
synthesis.speak(quoteUtterance);
synthesis.speak(personUtterance);
}
});
// Click event handler for the pause button
pauseButton.on('click', function (evt) {
evt.preventDefault();
// Pause the utterance if it is not in paused state
if (VOICE_SPEAKING) synthesis.pause();
return paused();
});
// Click event handler for the stop button
stopButton.on('click', function (evt) {
evt.preventDefault();
// Clear the utterances queue
if (VOICE_SPEAKING) synthesis.cancel();
resetVoice();
// Set the complete status of the voice output
VOICE_COMPLETE = true;
updateVoiceControls();
});
ここでは、音声コントロールボタンのクリックイベントリスナーを設定します。 再生ボタンをクリックすると、 quoteUtterance
そして personUtterance
. ただし、音声出力が一時停止状態の場合は再開します。
あなたが設定した VOICE_SPEAKING
に true
の中に onstart
のイベントハンドラ quoteUtterance
. アプリはまた、更新して新しい見積もりを取得します personUtterance
終了します。
Pause ボタンは音声出力を一時停止し、 Stop ボタンは音声出力を終了し、キューからすべての発話を削除します。 cancel()
の方法 SpeechSynthesis
インターフェース。 コードは updateVoiceControls()
適切なボタンを表示するために毎回機能します。
あなたはいくつかの電話をかけ、 updateVoiceControls()
前のコードスニペットで機能します。 この関数は、音声コントロールを更新して、音声状態変数に基づいて適切なコントロールを表示する役割を果たします。
に次の変更を加えます public/main.js
を実装するファイル updateVoiceControls()
関数:
function updateVoiceControls() {
// Get a reference to each control button
let playButton = $('#play-circle');
let pauseButton = $('#pause-circle');
let stopButton = $('#stop-circle');
if (VOICE_SPEAKING) {
// Show the stop button if speaking is in progress
showControl(stopButton);
// Toggle the play and pause buttons based on paused state
if (VOICE_PAUSED) {
showControl(playButton);
hideControl(pauseButton);
} else {
hideControl(playButton);
showControl(pauseButton);
}
} else {
// Show only the play button if no speaking is in progress
showControl(playButton);
hideControl(pauseButton);
hideControl(stopButton);
}
}
コードのこのセクションでは、最初に各音声コントロールボタン要素への参照を取得します。 次に、音声出力のさまざまな状態で表示する音声制御ボタンを指定します。
これで、を実装する準備ができました initialize()
関数。 この関数は、アプリケーションの初期化を担当します。 次のコードスニペットをに追加します public/main.js
を実装するファイル initialize()
関数。
function initialize() {
if ('speechSynthesis' in window) {
SYNTHESIS = window.speechSynthesis;
let timer = setInterval(function () {
let voices = SYNTHESIS.getVoices();
if (voices.length > 0) {
getVoices();
fetchNewQuote();
clearInterval(timer);
}
}, 200);
} else {
let message = 'Text-to-speech not supported by your browser.';
// Create the browser notice element
let notice = $('<div class="w-100 py-4 bg-danger font-weight-bold text-white position-absolute text-center" style="bottom:0; z-index:10">' + message + '</div>');
fetchNewQuote();
console.log(message);
// Display non-support info on DOM
$(document.body).append(notice);
}
}
このコードは最初に speechSynthesis
で利用可能です window
グローバルオブジェクトに割り当てられ、 SYNTHESIS
利用可能な場合は変数。 次に、使用可能な音声のリストを取得するための間隔を設定します。
で既知の非同期動作があるため、ここでは間隔を使用しています SpeechSynthesis.getVoices()
これにより、音声がまだロードされていないため、最初の呼び出しで空の配列が返されます。 間隔は、ランダムな引用をフェッチして間隔をクリアする前に、音声のリストを確実に取得します。
これで、テキスト読み上げアプリが正常に完了しました。 ターミナルで次のコマンドを実行すると、アプリを起動できます。
- npm start
アプリはポートで実行されます 5000
利用可能な場合。
訪問 localhost:5000
ブラウザでアプリを観察します。
次に、再生ボタンを操作して、引用が話されているのを聞きます。
結論
このチュートリアルでは、Web Speech APIを使用して、Web用のテキスト読み上げアプリを作成しました。 Web Speech APIの詳細については、MDN WebDocsでいくつかの役立つリソースを見つけることができます。
アプリの改良を続けたい場合は、音量コントロール、音声ピッチコントロール、速度/速度コントロール、発声されたテキストの割合など、まだ実装して実験できる興味深い機能がいくつかあります。
このチュートリアルの完全なソースコードは、GitHubで入手できます。