Edited at

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

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


はじめに

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


  • 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)



https://caniuse.com/#feat=speech-synthesis

オッ。Internet Explorer 以外はほとんどが対応してる。:clap::clap::clap:


音声認識 (Speech Recognition)



https://caniuse.com/#feat=speech-recognition

ヒィー。Chromeしか対応してないし、部分対応だし、なんだこれ。:joy:

ただ、FirefoxとOperaは In development 中らしいので、 今後の動きに期待したい。詳細


音声認識 (Speech Recognition)

記事が長くなったので、次週やります。:bow:


音声合成 (SpeechSynthesis)

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


対応確認

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



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>


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

ボタンをクリックしたときに、入力欄の中身を読み上げたい場合は以下となります。

ちなみに連打すると、連打した分だけ、続けて読み上げます。



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()
再生再開 (一時停止を解除)



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 っぽい声にしてみる。

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で声の種類を選べるようにしたい場合こうなります。



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


宣伝