Help us understand the problem. What is going on with this article?

Webページでブラウザの音声合成機能を使おう - Web Speech API Speech Synthesis

この記事は、CPS Lab Advent Calendar 2018の5日目の記事です。

音声認識編 → https://qiita.com/hmmrjn/items/4b77a86030ed0071f548

はじめに

みなさん、ウェブサイトで音声合成(読み上げ)、音声認識を使いたいとなった時、どうしますか?

  • Google Cloud Speech API
  • Microsoft Azure Text to Speech API
  • IBM Watson Text to Speech API
  • AWS Amazon Polly

などのがつがつとしたWebサービスが思いつくのではないでしょうか?

でも、登録しないといけないし、お金かかりそうですよね...
実はそれ、最近のブラウザであれば、無料でできるんです。

Web Speech API とは

自分の周りでは意外と知っている人が少なかったので紹介します。

  • Webページで、ブラウザの音声認識(マイクの音声を文章に変換)、音声合成(文章を読み上げる)機能を使うためのAPI。
  • 2012年にWC3から仕様が策定された。(が、最近になってやっとブラウザが対応してきている。)
  • Webサービスじゃないので、お金も、登録も、認証キーも一切いらない。
  • 軽いJavaScriptの知識があれば誰でも使える。
  • HTML/CSS/JSだけの静的ページでも動く。(ブラウザ上で動くので、バックエンドサーバは不要。)
  • ブラウザのネイティブAPIなので、JavaScriptライブラリをインポートする必要もなし。
  • ただ、音声合成はブラウザによって使える声が違って、音声認識は現時点では、Chromeしか対応していない...。

Demo

一見は百聞に如かず。

※ iPhoneの場合はマナーモードを解除しないと音声は流れません。

ブラウザの対応状況

音声合成 (Speech Synthesis)

スクリーンショット 2018-12-02 17.59.19.png
https://caniuse.com/#feat=speech-synthesis
オッ。Internet Explorer 以外はほとんどが対応してる。:clap::clap::clap:

音声認識 (Speech Recognition)

スクリーンショット 2018-12-02 18.02.37.png
https://caniuse.com/#feat=speech-recognition
ヒィー。Chromeしか対応してないし、部分対応だし、なんだこれ。:joy:
ただ、FirefoxとOperaは In development 中らしいので、 今後の動きに期待したい。詳細

音声認識 (Speech Recognition)

記事が長くなったので、次週やります。:bow:
https://qiita.com/hmmrjn/items/4b77a86030ed0071f548

音声合成 (SpeechSynthesis)

文字列をブラウザに声で読んでもらう機能です。

対応確認

対応していないブラウザは少ないですが、ユーザのブラウザが対応しているかどうか調べたい場合はこちら。

スクリーンショット 2018-12-05 4.55.51.png
https://jsfiddle.net/hmmrjn/o68vgqh0/1/

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

Hello World!

以下の2行で、ページをリロードするたび、ブラウザに「ハローワールド!」と喋らせることができます。
サーバ起動する必要なし。index.htmlファイルでも動きます。素敵ですね。

https://jsfiddle.net/hmmrjn/Loy85h9r/

index.html
<script>
  // 発言を作成
  const uttr = new SpeechSynthesisUtterance("Hello World!")
  // 発言を再生 (発言キューに発言を追加)
  speechSynthesis.speak(uttr)
</script>

入力欄の文章を読み上げる

ボタンをクリックしたときに、入力欄の中身を読み上げたい場合は以下となります。
ちなみに連打すると、連打した分だけ、続けて読み上げます。

スクリーンショット 2018-12-05 0.58.25.png
https://jsfiddle.net/hmmrjn/wafd3sum/

index.html
<textarea id="text">再生する内容</textarea>
<button id="speak-btn">再生</button>

<script>
  const text     = document.querySelector('#text')
  const speakBtn = document.querySelector('#speak-btn')

  speakBtn.addEventListener('click', function() {
    // 発言を作成
    const uttr = new SpeechSynthesisUtterance(text.value)
    // 発言を再生 (発言キューに発言を追加)
    speechSynthesis.speak(uttr)
  })
</script>

再生、停止、一時停止、再生再開

長文を読ませると、途中で黙らせるボタンが欲しくなります。
ここも直感的でとてもわかりやすいです。

speechSynthesis のメソッド

メソッド 意味
.speak(uttr) 発言を再生 (発言キュー発言に追加)
.cancel() 再生停止 (発言キューをクリアして止まる)
.pause() 一時停止 (発言キューを保持して止まる)
.resume() 再生再開 (一時停止を解除)

スクリーンショット 2018-12-05 1.12.43.png
https://jsfiddle.net/hmmrjn/dmbhr6yv/

index.html
<textarea id="text">
アドベントカレンダーは、クリスマスまでの日数を数えるためのカレンダー。
イエス・キリストの降誕を待ち望む24日間、窓を毎日ひとつずつ開けていき、
中には、イラストや物語の一編、甘いお菓子、小さな贈り物などが入っている。
</textarea>
<button id="speak-btn">再生</button>
<button id="cancel-btn">停止</button>
<button id="pause-btn">一時停止</button>
<button id="resume-btn">再開</button>

<script>
  const text      = document.querySelector('#text')
  const speakBtn  = document.querySelector('#speak-btn')
  const cancelBtn = document.querySelector('#cancel-btn')
  const pauseBtn  = document.querySelector('#pause-btn')
  const resumeBtn = document.querySelector('#resume-btn')

  speakBtn.addEventListener('click', function() {
    // 発言を作成
    const uttr = new SpeechSynthesisUtterance(text.value)
    // 発言を再生 (発言キュー発言に追加)
    speechSynthesis.speak(uttr)
  })
  cancelBtn.addEventListener('click', function() {
    // 再生停止 (発言キューをクリアして止まる)
    speechSynthesis.cancel()
  })
  pauseBtn.addEventListener('click', function() {
    // 一時停止 (発言キューを保持して止まる)
    speechSynthesis.pause()
  })
  resumeBtn.addEventListener('click', function() {
    // 再生再開 (一時停止を解除)
    speechSynthesis.resume()
  })
</script>

言語、速度、高さ、音量を指定

HALL 9000 っぽい声にしてみる。

image.png

https://jsfiddle.net/hmmrjn/0wb48ctg/

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

// 文章 (コンストラクタの引数以外に、この方法でも指定できます)
uttr.text = "Hi, Dave."

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

// 速度 0.1-10 初期値:1 (倍速なら2, 半分の倍速なら0.5)
uttr.rate = 0.5

// 高さ 0-2 初期値:1
uttr.pitch = 0.5

// 音量 0-1 初期値:1
uttr.volume = 0.75

// 再生 (発言キュー発言に追加)
speechSynthesis.speak(uttr)

声の種類の指定

同じ言語でも、様々な声の種類が存在するときがあります。
例えば、男性と女性の声だったり、Appleが提供する声とGoogleが提供する声だったりします。
ただ、この指定の仕方が少し複雑だったので、ここは丁寧にみていきましょう。

コード 説明
speechSynthesis.getVoices() SpeechSynthesisVoice[] ブラウザで使える声の配列を得る。
speechSynthesis.onvoiceschanged = e => {...} コールバック関数 Chromeは使える声を非同期で読み込むため、使える声が増えたときに着火するイベント(これ)が必要となる。
uttr.voice = voice SpeechSynthesisVoice 使う声を指定する。

流れとしては、
getVoices() で、使える声の配列を得て、
② 使える声が増えたらまた getVoices() で、使える声の配列を得て、
③ その配列の中から使いたい声を uttr.voice = に渡す形となります。

ちなみに、声のオブジェクト (SpeechSynthesisVoice) の中身はこんな感じです。

[object SpeechSynthesisVoice] {
  name: "Google 日本語",      // 声の名前 (Google 日本語, Alice, Kyokoとか。ブラウザによって使える声が違う。)
  lang: "ja-JP",             // 声の言語 (BCP47言語タグ:ja-JP, en-US, en-GB, zh-CN, ko-KR とか。)
  default: false,            // ブラウザの既定の声か。(自分のChromeの場合、Kyokoがデフォルトだった。)
  localService: false,       // true:ローカル(OS)の声 false:リモート(ネット上)の声
  voiceURI: "Google 日本語"   // WebサービスのURI (のはずなんだが、Chromeではなぜか声の名前が入ってる。気にしない。)
}

たとえば、selectboxで声の種類を選べるようにしたい場合こうなります。

スクリーンショット 2018-12-05 2.31.08.png
https://jsfiddle.net/hmmrjn/u1y4aL9v/

index.html
<textarea id="text">Hello. Nice to meet you.</textarea>
<select id="voice-select"></select>
<button id="speak-btn">再生</button>

<script>
  const text        = document.querySelector('#text')
  const voiceSelect = document.querySelector('#voice-select')
  const speakBtn    = document.querySelector('#speak-btn')

  // selectタグの中身を声の名前が入ったoptionタグで埋める
  function appendVoices() {
    // ① 使える声の配列を取得
    // 配列の中身は SpeechSynthesisVoice オブジェクト
    const voices = speechSynthesis.getVoices()
    voiceSelect.innerHTML = ''
    voices.forEach(voice => { // アロー関数 (ES6)
      // 日本語と英語以外の声は選択肢に追加しない。
      if(!voice.lang.match('ja|en-US')) return
      const option = document.createElement('option')
      option.value = voice.name
      option.text  = `${voice.name} (${voice.lang})` // テンプレートリテラル (ES6)
      option.setAttribute('selected', voice.default)
      voiceSelect.appendChild(option)
    });
  }

  appendVoices()

  // ② 使える声が追加されたときに着火するイベントハンドラ。
  // Chrome は非同期に(一個ずつ)声を読み込むため必要。
  speechSynthesis.onvoiceschanged = e => {
    appendVoices()
  }

  speakBtn.addEventListener('click', function() {
    // 発言を作成
    const uttr = new SpeechSynthesisUtterance(text.value)
    // ③ 選択された声を指定
    uttr.voice = speechSynthesis
      .getVoices()
      .filter(voice => voice.name === voiceSelect.value)[0]
    // 発言を再生 (発言キュー発言に追加)
    speechSynthesis.speak(uttr)
  })
</script>

使える声

このリンクで今のブラウザでどんな声が使えるかみれます。
https://codepen.io/rodhamjun/full/jQJEWQ/
自分の環境(Google Chrome/macOS Mojave)では、色んな言語の声があって、合わせて65個の声が使えました。

比較のため、macOSとWindowsとChromeで使える声を下に貼っておきます。
※多いので、英語と日本語の声のみで。

macOS

macOS 10.14 Mojave

name lang  localService default
Kyoko ja-JP true true
Alex en-US true false
Daniel en-GB true false
Fiona en true false
Fred en-US true false

Windows

Windows 10 Home 1803

name lang  localService default
Microsoft Ayumi - Japanese (Japan) ja-JP true true
Microsoft Haruka - Japanese (Japan) ja-JP true false
Microsoft Ichiro - Japanese (Japan) ja-JP true false

Chrome

Chrome 69 for Mac

OSが提供する声に加えて、

name lang  localService default
Google 日本語 ja-JP false false
Google US English en-US false false
Google UK English Female en-GB false false
Google UK English Male en-GB false false
  • Windows の Microsoft Edge では Windowsの声、
  • macOS の Safari では macOS の声、
  • Google Chrome では macOS + Google の声 / Windows + Google の声、
  • Firefox と Opera では OS (macOS / Windows) の声のみが使えました。

細かいところ

SpeechSynthesis

プロパティ

名前 意味
paused boolean 一時停止中か
pending boolean 発言キューにまだ再生されていない発言が残っているか
speaking boolean 再生中か

イベントハンドラ
onvoiceschanged
getVoices() で返されるオブジェクトに変化があった。

メソッド

名前 戻り値 意味
cancel() void 停止して、発言キューを空に
pause() void 一時停止して、発言キューを保持
resume() void 一時停止から再生状態に復帰
speak(uttr) void 発言キューに発言を追加
uttr: SpeechSynthesisUtterance

SpeechSynthesisUtterance

コンストラクタ
SpeechSynthesisUtterance()
SpeechSynthesisUtterance("発言する文章")

プロパティ

名前 意味
lang String 言語。BCP 47言語タグ
(ja-JP,en-US,en-GB,zh-CN,ko-KR)
pitch float 声の高さ。 02 初期値: 1
rate float 速度。0.110 初期値: 1 (2= 倍速)
text String or SSML 文章。
voice SpeechSynthesisVoice 声の種類。
volume float 音量。01 初期値: 1

イベントハンドラ

名前 着火時
onboundary 単語もしくは文の境界(?)に達した
onend 発言が終了した
onerror エラーが発生した
onmark SSMLの"mark"タグに達したとき
onpause 途中で一時停止された
onresume 一時停止状態が解除された
onstart 再生開始した

onendが来ない問題
https://qiita.com/XyYaE7gmJtMJDjh/items/38f4c28d2dbe5b7ef9e2

SpeechSynthesisVoice

プロパティ

名前 意味
name String 声の名前
lang String 声の言語 (BCP47言語タグ)
default boolean ブラウザの既定の声か。
localService boolean ローカル(OS)の声か ↔️リモート(ネット上)の声
voiceURI String WebサービスのURI

最後に

最初は、音声認識と音声合成両方を記事にしようと思っていたのですが、
想定以上に長く(TL;DR不可避に)なってしまったので、音声認識はまた来週投稿します。
お楽しみに。
待ちきれない方は、「Web Speech API | JavaScript プログラミング解説 - so-zou.jp」
がとてもよくまとまっているのでおすすめです。:santa::ok_hand:

最後に、SafariとEdge様へ:
純正が対応してくれないと困る

参考文献

MDN Web Docs
https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API

W3C Specification
https://w3c.github.io/speech-api/speechapi.html

宣伝

hmmrjn
社会人1年目でWebエンジニアやってます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした