JavaScriptによりブラウザで読み上げする (WEB SPEECH API)


## 1. やりたいこと


ウェブページで文章を読み上げたい


## 2. 実現方法


JavaScriptからWEB SPEECH APIを使うと大抵のブラウザで読み上げが可能です
https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis


## 3. ブラウザが対応しているか確認する


ブラウザごとの対応状況は以下から確認できます
https://caniuse.com/speech-synthesis


実行中のJavaScriptからだと以下のように確認できるようです


```html:index.html
<script>
  if ('speechSynthesis' in window) {
    alert("このブラウザは音声合成に対応しています")
  } else {
    alert("このブラウザは音声合成に対応していません")
  }
</script>
```


## 4. 読み上げる


JavaScriptで以下を実行すると読み上げてくれます


```javascript:javascript
const uttr = new SpeechSynthesisUtterance("Hello World!")
speechSynthesis.speak(uttr)
```


Chromeだとユーザーが操作していないページでは音がならなくなっていますので、ボタン操作に伴い読み上げるようにしてみます


```javascript:index.html
<html>
  <head>
    <title>2 読み上げテスト</title>
    <link rel="icon" href="data:,">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta name="viewport" content="width=device-width">
  </head>

  <body>
    <input type="button" value="Hello World!" onclick="say('Hello World!')">
  </body>
</html>

<script>
  const say = (text) => {
    const uttr = new SpeechSynthesisUtterance("Hello World!")
    speechSynthesis.speak(uttr)
  }
</script>
```


## 5. 読み上げの一時停止、再開、終了


読み上げ中に一時停止したり再開したり、再生を終了したりできます


```javascript:index.html
<html>
  <head>
    <title>3 止めたり再開したり</title>
    <link rel="icon" href="data:,">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta name="viewport" content="width=device-width">
  </head>

  <body>
    <input type="button" value="再生" onclick="start()">
    <input type="button" value="停止" onclick="stop()">
    <input type="button" value="一時停止" onclick="pause()">
    <input type="button" value="再開" onclick="resume()">
  </body>
</html>

<script>
  const text = "グスコーブドリは、イーハトーヴの大きな森のなかに生まれました\
                おとうさんは、グスコーナドリという名高い木こりで\
                どんな大きな木でも、まるで赤ん坊を寝かしつけるようにわけなく切ってしまう人でした"

  const start = () => {
    const uttr = new SpeechSynthesisUtterance(text)
    speechSynthesis.speak(uttr)
  }
  const stop = () => {
    speechSynthesis.cancel()
  }
  const pause = () => {
    speechSynthesis.pause()
  }
  const resume = () => {
    speechSynthesis.resume()
  }
</script>
```


## 6. 声の選択


読み上げる声の種類を選べます


### 6-1. 選択肢の確認


利用可能な声の一覧を以下のコマンドで取得できます


```javascript:javascript
voices = speechSynthesis.getVoices()
```


Chromeでは使用可能な声を逐次取得していく為に、普通に取得しようとすると1回目は空配列が返ってきてしまいますが、少し間をおいて再取得すればちゃんと取れます


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/bec70817-3f17-667b-72c2-8bf0cd35393b.png" width=80%>


声が取得可能になる度にspeechSynthesis.onvoiceschangedに入れた関数が実行されます。これを仕込んでおけばスマートな書き方が出来そうです


```javascript:javascript
let voices = []
speechSynthesis.onvoiceschanged = e => {
  voices = speechSynthesis.getVoices()
}
```


声のサンプルはここで試聴することもできます
https://codepen.io/rodhamjun/full/jQJEWQ/


OSとブラウザによって使える声が違うので、公開するサービスに使うときはユーザー環境ごとに使える声が異なる前提で作成する必要があります


### 6-2. 声を選んで読み上げる


SpeechSynthesisUtteranceインスタンスのvoiceプロパティに取得した声情報のどれかを渡してやれば、その声で話すようになります


```html:javascript
const uttr = new SpeechSynthesisUtterance()
uttr.voice = voices[0]
speechSynthesis.speak(uttr)
```


## 7. 読み上げる速さや声の高さを変更する


読み上げる文章、言語、速さ、声の高さ、音量を変更できます
以下ではボタンを押すと取得した声のうち言語が日本語であるものを順番に使って読み上げを実行するようにしています


```html:index.html
<script>
  const uttr = new SpeechSynthesisUtterance() 
  let k = 0
  let voices = null

  const getVoices = () => {
    return  speechSynthesis.getVoices().filter((v) => v.lang === 'ja-JP')
  }
  voices = getVoices()

  const start = () => {
    voices = getVoices()

    // 発言を作成
    const uttr = new SpeechSynthesisUtterance()

    // 声を選択   
    uttr.voice = voices[k]
    console.log(k, uttr.voice.name)

    // 文章を設定
    uttr.text = "声や話す速度を指定して話すことができます"

    // 言語 (日本語:ja-JP, アメリカ英語:en-US, イギリス英語:en-GB, 中国語:zh-CN, 韓国語:ko-KR)
    uttr.lang = "ja-JP"

    // 速度 0.1-10
    uttr.rate = 2.0

    // 高さ 0-2
    uttr.pitch = 0.7

    // 音量 0-1
    uttr.volume = 0.75

    // 再生
    speechSynthesis.speak(uttr)    

    if (k < (voices.length - 1)) {
      k += 1
    } else {
      k = 0
    }
  }
</script>
```


Windows版Chromeでは日本語読み上げの選択肢が以下の5つになるようです


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/4ea1c9db-fe63-37bd-fdef-c9bc1a1ea8f2.png" width=50%>


実行してもらえば分かりますがMicrosoftの作ってくれた声の方が圧倒的に品質が高いです。Googleのは英語読み上げが抜群に素晴らしいのに日本語の方はかなりダメです


## 8. ステータスの確認


停止中かどうかなどが見られます


```javascript:
speechSynthesis.paused    // 一時停止中だとtrue
speechSynthesis.pending   // 発言キューにまだ再生していない文章が残っていればtrue
speechSynthesis.speaking  // 再生中だとtrue
```


## 9. イベントリスナ


### 9-1. 終了イベント


uttr.onendに関数を渡しておくと、読み上げ完了時に実行してくれます
読み終わったら次の行を読み上げるような繰り返しのトリガーに使えます


```javascript
uttr.onend = () => {
  console.log("読み上げ終了しました")
};
```


### 9-2. 進捗イベント


uttr.onboundaryに関数を渡しておくと、文節単位で読み進む度に実行してくれます
読んでいる位置も取得できるので、どこまで読んだか捕捉したりできます


```javascript
const text = '吾輩は猫である'

uttr.text = text
uttr.onboundary = (event) => {
  if (event.name === 'word') {
    const spokenWord = text.substring(event.charIndex, event.charIndex + event.charLength);
    console.log(`現在読み上げている文節: ${spokenWord}`);
    console.log(`読み上げ位置: ${event.charIndex}-${event.charIndex + event.charLength}: ${spokenWord}`);
  }
};    
```


## まとめ


手軽に読み上げが実装できてすごいです
レッツトライ