Web Speech APIの実装 前編 Speech Synthesis API

指定テキストを音声再生させることができる、Speech Synthesis APIについて紹介します。音声ファイルなどを用意せずとも手軽に扱えるメリットを活かした利用が考えられます。

発行

著者 森 大典 フロントエンド・エンジニア
Web Speech APIの実装 シリーズの記事一覧

はじめに

Web Speech APIは、W3C Community Final Specification Agreement(FSA)の元、Speech APIコミニュティグループにより公開されている仕様で、Speech Synthesis API(音声合成)とSpeech Recognition API(音声認識)の2つのAPI仕様から構成されています。これらの仕様はW3Cの正式なワーキンググループの作業として策定されてるわけでなく、コミュニティグループによるレポートという位置付けです。

今回は、音声ファイルを用意せずとも、指定したテキストを読み上げ音声再生させることができるSpeech Synthesis APIについて紹介します。Speech Synthesis APIは、2016年12月現在、次のブラウザで実装されており、その機能を試すことができます。

Can I useによると、Web Speech APIの仕様の1つであるSpeech Synthesis APIについては、2016年12月現在、Edge、Firefox、Chrome、Safari、Opera、iOS Safari、Chrome for Androidにて利用可となっています。

なお、今回紹介する機能やデモは、次の環境において動作確認した結果に基づいています。今後のOSやブラウザのバージョンアップにより、記事で解説している内容やデモの挙動が変わってくる可能性が大いにある点をご了承ください。

本記事内の機能やデモの動作確認環境

記事内で言及している各種ブラウザにおける動作については、次の環境下で動作確認した内容になっています。

  • Safari 9.1.2 (11601.7.7) + OS X El Capitan10.11.6
  • Chrome 54.0.2840.71 + OS X El Capitan10.11.6
  • Opera 41.0.2353.69 + OS X El Capitan10.11.6
  • Firefox 49.0.2 + OS X El Capitan10.11.6
  • iOS Safari 10.0 + iOS10.11
  • Edge 38.14393.0.0 + Windows10

どのデモも、テキストの読み上げ音声再生を伴いますので、試す際は音量にご注意ください。

紹介するデモは次のリポジトリからダウンロード、またはクローンできます。併せて参照してください。

テキストを音声再生する

Speech Synthesis APIによるテキストの音声再生は非常に簡単にできます。次のデモでは、「speak()」をクリックすると、表示されている「Web Speech API を使ってみよう!」というテキストが音声再生されます。

コードは次のようになっています。

<h1>シンプルなデモ</h1>
<input class="text" value="Web Speech API を使ってみよう!"/>
<button onclick="speak()">speak()</button>
<script>
function speak(){
  var text = document.querySelector('.text').value;
  speechSynthesis.speak(
    new SpeechSynthesisUtterance(text)
  );
}
</script>

SpeechSynthesisUtterance()に音声再生させたいテキストを指定しnewすることで、そのインスタンスを得て(以降、uttrインスタンスと呼びます)、speechSynthesis.speak()に渡すことで音声再生させています。

また、uttrインスタンスに各種属性を設定することで再生音声を制御することもできます。たとえば次のように指定テキストの言語種類を設定すれば、自然な発声で再生させることができます。

uttrインスタンスに指定テキストの言語種類を設定する

var uttr = new SpeechSynthesisUtterance();
uttr.text = document.querySelector('.text').value;

//言語種類を設定
uttr.lang = 'en-US';

speechSynthesis.speak(uttr);

注:上記コードが示すように、再生テキストもインスタンス生成後に設定することができます。他にも指定可能な属性がありますが、それらについては後述します。

次のデモでは「English」チェックボックスをONにしたときのみ、lang属性にen-USを指定するようにしています。チェックボックスをON/OFFし、発声の違いを聞き比べてみてください。

lang属性を指定しても変化がない場合

Chrome 54.0.2840.71 + Windows10にて上記デモの動作確認をしたところ、特に発声の変化は見られませんでした。同環境では後述するvoice属性にて「Google US English」などの英語圏の声質を設定することで、自然な発声で英文が読み上げられるようになりました。lang属性の指定が有効に機能しない環境の場合は、voice属性の設定で調整するとよさそうです。

また、Firefox 49.0.2 + OS X El Capitan10.11.6において「English」チェックボックスをONした状態で再生すると非常に機械的な音声で再生されました。これはvoice属性に「Zarvox」という声質を適用している状態と同じ音声になります。他のブラウザ同様に人間らしい音声で再生したい場合は、こちらについてもvoice属性の設定値で調整するとよさそうです。

いろいろな種類の声質で再生する

Speech Synthesis APIでは、前述の言語種類の指定とは別に音声の種類(声質)についても指定することができます。次のデモは、Speech Synthesis APIの持つ複数の声質の名前をボタンラベルとして表示し、クリックするとその声質で再生するようにしています。

さまざまな国の言葉、性別、年齢、あるいは歌を歌うように再生される声質があり、聞いてるだけでも楽しめるかと思います。

声質の種類を指定するには、uttrインスタンスのvoice属性にvoiceオブジェクトをセットする必要があります。voiceオブジェクトは、speechSynthesis.getVoices()を実行することで、利用環境で使用可能なvoiceオブジェクトを配列形式でまとめて取得することができます。

たとえば、「イギリス人男性風の声質」を適用するには次のように記述します。

声質の種類を「イギリス人男性風の声質」に指定する

function speak(){
  var text = document.querySelector('.text').value;
  var uttr = new SpeechSynthesisUtterance(text);

  //「イギリス人男性風の声質」のvoiceオブジェクトを取得
  var voice = speechSynthesis.getVoices().find(function(voice){
    return voice.name === 'Google UK English Male';
  });

  // 取得できた場合のみ適用する
  if(voice){
    uttr.voice = voice;
  }
  speechSynthesis.speak(uttr);
}

speechSynthesis.getVoices()により得られたvoiceオブジェクト群の中から、適用したいvoiceオブジェクトをname属性で判別し、見つかった場合のみ、そのvoiceオブジェクトを適用しています。

ブラウザやOSなどの利用環境によりspeechSynthesis.getVoices()により得られるvoiceオブジェクトは異なるので、上記コードのようにname属性で判別し指定の声質名が得られた場合のみ適用するとよいでしょう。

speechSynthesis.getVoices()の実行

今回確認した動作環境において、Chromeでは初回実行時あるいは画面ロード未完了時や画面ロード直後にこのメソッドを実行すると空の配列が返されるという挙動になり、これはバグ報告もされています(Operaでも同事象が発生します。Safari、Firefox、iOS Safari、Edgeでは正常に動作します)。

一度、空配列の取得を行えば2回目以降の実行では正しくvoiceオブジェクトが返されるので、事前に(画面表示時などに)speechSynthesis.getVoices()を実行しておくとよいでしょう。

ただし、ユーザーの操作を経由せず、たとえば画面表示時にvoiceオブジェクトを得て音声再生をするような場合は、単にspeechSynthesis.getVoices()を2回実行してもvoiceオブジェクトは得られないので注意が必要です。このような場合は非同期でspeechSynthesis.getVoices()を繰り返し行い、voiceオブジェクトを得ることができたあとに音声再生を行うといった工夫が必要になります。

ちなみにiOS環境ではユーザーの操作を経由しない音声再生はできないという制約がありますが(詳しくは以下記事を参照)、Speech Synthesis APIにおいてもこの制約は適用されます。

フロントエンドのサウンド実装 - audio要素 1 | CodeGrid

利用環境による声質データの違い

Speech Synthesis APIで扱える声質データは、ブラウザやOSなどの利用環境により異なります。以下は先ほどの複数の声質の名前をボタンとして表示するデモのスクリーンショットですが、各種環境で表示されているボタンの内容や数が異なっていることがわかります。

Speech Synthesis APIの活用事例

ブラウザ間の差異やバグらしき挙動はあるものの、ここまでの内容だけでも、すぐにでもなにかに使えそうなSpeech Synthesis APIですが、どのような活用方法が考えられるでしょうか? 音声ナビによるアクセシビリティの向上、ゲームの効果音などが思いつきますが、筆者は個人的なプロダクトで、タイピングゲームにて出題されるテキストの音声再生に使用してみました。

3Dシューティングゲーム風の構成にし、「敵(出題テキスト)が出現時は読み取れないフォントサイズで表示させ、同時に発声される英語テキストの音声を正しく読み取ることができれば先行してタイプし攻撃される前に撃ち落とすことができる」というようにしました。単に効果音としての利用に留まらず、ゲーム性に幅を持たせることができたかと思います。

また、ピクセルグリッドの社内勉強会で別のスタッフは、スライド内に登場するWebGLで描画されたキャラクターにスライドの内容をSpeech Synthesis APIで喋らせ、状況に応じてキャラクターが発表者に話を振りながら進行していくという、まるでスライドの中にアシスタントがいるようなおもしろい構成で発表をしていました。

音声ファイルなどを用意せずとも手軽に扱えるメリットを活かし、思いついたアイデアを気軽に試してみるのも楽しいのではないでしょうか。

その他の属性、メソッド、イベント

Speech Synthesis APIの持っているその他の属性やメソッド、イベントについて一部紹介します。詳細は、「Web Speech API Specification」のThe SpeechSynthesis Interfaceを参照ください。

SpeechSynthesisの属性

次の属性を参照することにより、音声の再生状況を確認することができます。

属性 内容
pending speak()メソッドによりキューされた再生待ちテキストが存在する場合、trueが設定されます
speaking 音声が再生中の場合、trueが設定されます。pause()メソッドによる一時停止中の場合もtrueになります
paused 音声再生が一時停止中の場合、trueが設定されます

SpeechSynthesisのメソッド

すでに紹介したspeak()getVoices()メソッド以外にも、次のようなメソッドがあります。

メソッド 内容
cancel() 再生待ちテキストとして保持されてるキューの削除と、再生中の音声再生を停止します
pause() 再生中の音声再生を一時停止します
resume() 音声再生の一時停止状態を解除し、再生を再開します

前述の再生状況を管理する属性値の表示と、上記メソッドを任意のタイミングで実行できるデモを用意したので試してみてください。

SpeechSynthesisUtteranceの属性

すでに紹介したlangvoicetext属性以外にも次のような属性が用意されています。

属性 内容
volume 音声の音量を0〜1の範囲で指定できます(デフォルトは1)
rate 音声の再生速度を0.1〜10の範囲で指定できます(デフォルトは1)
pitch 音声のピッチを0〜2の範囲で指定できます(デフォルトは1)

SpeechSynthesisUtteranceのイベント

SpeechSynthesisUtterance関連のイベントとしては次のようなものがあります。

イベント 内容
start 再生開始時に発火
end 再生終了時時に発火(このとき、errorイベントは発火しません)
error エラーが発生したときに発火(このとき、endイベントは発火しません)
pause 再生が途中でpauseしたときに発火
resume pause後、再生が再開したときに発火
boundary 再生テキストの文の境目にきたときに発火

また、これらイベントは「on+イベント名」という名前でuttrインスタンスにコールバック関数を設定することができます。

var uttr = new SpeechSynthesisUtterance();

// endイベントのコールバックを設定
uttr.onend = function(event) {
    alert('Finished in ' + event.elapsedTime + ' seconds.');
};

利用上の注意点

手軽に利用できるSpeech Synthesis APIですが、利用に際し注意しなければいけない点があります。

再生待ちデータはキューされる

音声再生中にspeechSynthesis.speak(uttr)を実行し、次の再生テキストを指定するとそのデータはキューされ、現在行われてる再生処理が完了後に再生されることになります。この挙動は仕様にしたがったものであり、複数テキストの同時再生はできないということを意味します。より深い活用の可能性を考えると、Web Audio APIのような非同期の音声再生ができないということが仕様として規定されてることは残念なところです。

ポーズしたらキャンセルかリジュームするまで再生することができない

前節で、SpeechSynthesis.pause()を実行すると再生中の音声を一時停止できることについて触れましたが、この状態になった場合、SpeechSynthesis.cancel()SpeechSynthesis.resume()が実行されるまでは音声再生ができなくなります。 ポーズを利用する場合は、いずれかのメソッドを適切なタイミングで実行することを前提に実装を考える必要がありそうです。

読み上げは完璧ではない

たとえば「Web Speech API を使ったことのある方はいますか?」というテキストを再生させると、「使ったことのあるほうは」と読み上げられてしまいます。漢字の読み上げを行う場合は、ある程度の誤読を許容した上で利用する必要がありそうです。

Chromeと一部ブラウザにおける利用上の注意点

Chromeと一部のブラウザでは、以降に挙げる挙動についても注意して使用する必要があります(これは今回確認した動作環境における挙動ですので、今後変わる可能性もあります)。

画面をリロードしても音声再生は停止しない

再生中のテキスト、キューされた再生待ちのテキストも含め、画面をリロードしてもキューが空になるまで再生処理は止まりません。かなり意表を突く挙動なので注意が必要です。次のデモでは「reload()」ボタンのクリックで画面のリロードを行います。音声再生中にクリックしても再生処理が止まらないことを確認してみてください。ただし、ブラウザによって挙動が異なります。

Chrome以外のブラウザの挙動

Chromeの他、Opera、iOS Safariにおいても同様の挙動となりました。Safari、Firefox、Edgeでは同事象は発生せず画面のリロードにより音声再生は停止しました。

必要に応じてwindowオブジェクトのbeforeunloadイベントなどで、speechSynthesis.cancel()を実行し音声再生の停止とキューのクリアをするとよいでしょう。

window.addEventListener('beforeunload', function(){
  speechSynthesis.cancel();
});

一時停止状態で画面をリロードすると音が出なくなる

音声再生中にspeechSynthesis.puase()で再生を一時停止後、画面をリロードし、改めてspeechSynthesis.speak(uttr)を実行しても音声が再生されなくなります。リロード前の一時停止を解除する意味でspeechSynthesis.resume()としても再生されません。この場合、画面リロード後、以下のいずれかの手順を踏むことで再生されるようになるようです*。

手順A

  1. speechSynthesis.cancel()を実行
  2. speechSynthesis.speak(uttr)を実行
  3. 2.で指定したテキストが再生される

手順B

  1. speechSynthesis.speak(uttr)を実行
  2. speechSynthesis.resume()を実行
  3. リロード前に中断した再生が再開され、再生完了すると1.で指定したテキストが再生される

手順Bのように、リロード前の中断音声を継続して再生したいという要件がないのであれば、常に画面ロード時にspeechSynthesis.cancel()を実行しておくとよいかもしれません。

Chrome以外のブラウザの挙動

Chromeの他、Opera、iOS Safariにおいても同様の事象が発生しました。また、iOS Safariにおいては手順A、Bを実施しても音声再生は行われず、ブラウザを再起動することで音声再生が可能となりました。Safari、Firefox、Edgeでは同事象は発生しませんでした。

1つのブラウザで同時に再生できる音声は1つまで

複数テキストの同時再生ができないことは先に述べたとおりですが、この制約はブラウザ単位に適用されます。具体的には1つの音声を再生中に他のタブあるいは他のウィンドウでも音声再生しようとすると、再生中の音声が完了するのを待つかたちでキューされることになります(ただし、ユーザプロファイルを分けてそれぞれのプロファイルでブラウザを起動した場合は、起動ブラウザ単位でキューが管理されるため同時再生は行われます)。

Chrome以外のブラウザの挙動

Chromeの他、Operaにおいても同様の挙動となりました。iOS Safariでは非アクティブになったタブの音声再生は一時停止され、再度アクティブになった際、再生が再開される挙動となりました。Safari、Firefox、Edgeでは同事象は発生せず、複数のタブにおける同時再生は可能でした。

複数タブでの同時再生を必要とするケース自体が考えづらくあまり実害はなさそうですが、あるタブにおいて先に述べたような音声が再生されなくなる事象が発生してしまうと、その他のタブにおいても同様に再生できなくなってしまうので注意が必要です。

最後に

単純な読み上げ処理に留まらず各種APIを使用した一歩踏み込んだ活用や、読み上げの正確性などを求めると、先に挙げたような注意点が大きな障壁になる可能性があるかもしれません。また、W3Cの正式な仕様として策定されてない点、ブラウザの実装状況により微妙に挙動が変化する点を考慮すると、正式なプロダクトへの適用も難しい面がありそうです。

一方、これだけ手軽にテキストの音声再生が実現できるのであれば、アイデア次第でなにかおもしろいものを作れるのではないかという可能性も感じます。

まずは、ブラウザ互換の考慮が不要なElectronや個人プロジェクトで利用し、思いついたアイデアを試してみてはいかがでしょうか。