こんにちは、もうすっかり令和ですね。UXデザインセンターのsgi-changです。
今日はVue.js、Firebase、Web Speech APIを使って、

こんな感じのSpeech To Text←→Text To Speechなアプリを作ってみようと思います。
お手軽さ、
所要時間:2時間~半日ぐらい
でできます。
コンセプトは、
- 文字を入力しないでも、できなくても音声・発話でコミュニケーションが取れる。(clientアプリ)
- マイクやスピーカーがなくても、音声を聞くことができなくても、文字と文字入力だけでコミュニケーションが取れる。(hostアプリ)
というアクセシビリティに配慮したアプリです。

- 画面をタップ(クリック)するとマイクのアイコンが表示される。
- マイク表示中は音声を認識する。
- hostアプリからの入力文字を発話する。

- 上部に入力欄があり、文字を入力してクリックまたは紙ヒコーキのアイコンをクリックしてclientアプリに発話させることができる。
- 入力欄の下には、clientアプリからの発話、hostアプリからの入力値が上から新しい順に表示される。
- 濃い青色の枠がclientアプリ、薄い青色の枠がhostアプリの内容。(カツメさんの「あなたもカラーユニバーサルデザイン」で紹介された「色のシミュレータ」で確認済み)
- hostアプリは文字が入力できること。
- clientアプリはマイク、スピーカーが使えること、マイクを許可すること。(最初のみ聞かれます)
- 開始はclientアプリでタップ(クリック)されること。(Chromeの自動再生ポリシーによりユーザーの動作なしで発話することができない為)
ざっくり皆さんをご紹介します。

Webで音声データを扱うことができる。
SpeechSynthesis (Text-to-Speech; 音声合成) と SpeechRecognition (Asynchronous Speech Recognition; 非同期音声認識) の 2 つの部分から成り立っていて、音声認識はGoogleの「Cloud Speech API」を内部で実装、今のところ完全に動作するのはChromeのみ。(2019年5月現在)
(Cloud Speech APIは利用時間で料金が発生しますが、Chrome経由で使用する場合のみ時間制限や利用料金を発生させないことにしているもよう)

Google が提供しているモバイルおよび Web アプリケーションのバックエンドサービス。BaaSとかmBaaSと呼ばれていますが、インフラ構築に手間取ることなく(用意しなくても)簡単にアプリから使えるサービスという認識。

JavaScriptのフレームワーク。高機能なシングルページアプリケーション(SPA)を容易に構築することができる。
React、Angularよりも後発だが、今やGitHubのスター数は他を抜いてJavaScriptのフレームワークでは1位となっている。(2019年5月現在)
賛否両論ありますが、私はVueのロゴがお気に入りです。

UIデザインのフレームワーク。レスポンシブに対応。CSSをほとんど書かずにそれなりのUIデザインを実装できる。今日からあなたもUIデザイナー!?
それぞれの詳細は各リンク先の公式ページをご参照下さい。
- Node.jsがインストールされている。(できれば最新)
- Chromeがインストールされている。(できれば最新)
- JavaScriptが開発できる。
上記がそろっていない方は以下のリンクからインストールしてください。
#Node.js
https://nodejs.org/ja/
#Chrome
https://www.google.com/chrome/
エディタは何でもいいですが「Visual Studio Code」があれば何かと捗ります。
#Visual Studio Code
https://code.visualstudio.com/
以下の流れで進めていこうと思います。
- Vue CLIインストール (約5分)
- Firebaseアカウント、Realtime Database作成 (約10分)
- Vuetifyインストール (約1分)
- Firebaseインストール (約1分)
- hostアプリの実装 (約10分)
- clientアプリの作成、実装 (約10分)
- Firebase Realtime Databaseの実装
- Web Speech APIの実装
Vue単体よりもこちらのほうが何かと便利なので。
詳細はこちらhttps://cli.vuejs.org/
以下、コンソールで
$ npm install -g @vue/cli |
$ npm install -g @vue/cli-service-global |
を実行
次にhostアプリを作成します。
$ vue create host |
(Push Enter) |
Successfully created project host. |
Get started with the following commands: |
$ cd host/ |
$ npm run serve |
http://localhost:8080/
(ポートは、適宜考慮して起動。起動時のコンソール画面を確認して下さい。)
上記URLをブラウザで開いてVueのlogoが見れればOK!

ここまで約5分!
この時点でもうVueアプリは作成できました。
Hello World!
以下、componentは「HelloWorld.vue」をそのまま使います。
次にFirebaseの登録をします。
以下のURLにアクセスして
https://firebase.google.com/
- 使ってみる
- Firebase へようこそ
- プロジェクトを追加
- プロジェクト名の入力(use-speech-apiなど)
- 各項目にチェック
- プロジェクトを作成
- 左のメニューからdatabaseを選択
- Realtime Databaseでデータベースを作成
- テストモードで開始、有効にする
上記の手順でFirebaseの登録とdatabaseの作成が終わりました。
作業時間は約10分
次にVuetifyをインストールします。
$ cd host |
$ npm install vuetify --save |
$ npm install material-design-icons-iconfont -D |
次にFirebaseをインストールします。
$ cd host |
$ npm install firebase --save |
以下の各ファイルにそれぞれソースコードを追記します。
"scripts": { |
"serve": "vue-cli-service serve --open", |
"build": "vue-cli-service build", |
"lint": "vue-cli-service lint" |
}, |
scripts/serveに
--open
import Vuetify from 'vuetify' |
import 'vuetify/dist/vuetify.min.css' |
import 'material-design-icons-iconfont/dist/material-design-icons.css' |
import firebase from 'firebase' |
import { firebaseConfig } from './firebase-config' |
firebase.initializeApp(firebaseConfig) |
Vue.use(Vuetify, { |
iconfont: 'md' |
}) |
// TODO: Replace with your project's config object |
export const firebaseConfig = { |
apiKey: "apiKey", |
authDomain: "projectId.firebaseapp.com", |
databaseURL: "https://databaseName.firebaseio.com", |
storageBucket: "bucket.appspot.com" |
} |
「2.Firebaseアカウント、Realtime Database作成」で作成したFirebaseのアカウント情報に書き換えてください。
apiKey…Firebaseコンソールの[Project Overview]横の設定歯車アイコンをクリック、全般タブにあります。
authDomain…<projectId>.firebaseapp.com databaseURL…Firebaseコンソールの[Database]ページにあります。 storageBucket…<projectId>.appspot.com |
<template> |
<v-app> |
<v-content> |
<HelloWorld/> |
</v-content> |
</v-app> |
</template> |
<script> |
import HelloWorld from "./components/HelloWorld"; |
export default { |
name: "app", |
components: { |
HelloWorld |
} |
}; |
</script> |
<style> |
</style> |
今回はHelloWorld.vueを表示するだけなのでApp.vueはシンプルに。
作業時間は約10分
clientアプリを作成します。ほぼhostアプリと同様の手順です。
$ vue create client |
(Push Enter) |
Successfully created project host. |
Get started with the following commands: |
(略) |
$ cd client/ |
$ npm install vuetify --save |
$ npm install material-design-icons-iconfont -D |
$ npm install firebase --save |
"scripts": { |
"serve": "vue-cli-service serve --open --https", |
"build": "vue-cli-service build", |
"lint": "vue-cli-service lint" |
}, |
clientはマイクを使うのでhttps接続で。
Vue CLI version 3.x.だと--https
を追記するだけでhttps化ができる。簡単ですね!
起動はhost同様に
$ cd client/ |
$ npm run serve |
https://localhost:8081/
(ポートは、適宜考慮して起動。起動時のコンソール画面を確認して下さい。)
起動時コンソール画面の「Network」のURLを起動PCと同じLANに接続したスマートフォンで閲覧すれば、スマートフォンのマイク、スピーカーをclientアプリで使うことができます。
以下のファイルは、ほぼhostアプリと同じです。
上記「#host/src/main.js」と同じ
上記「#host/src/firebase-config.js」と同じ
上記「#host/src/App.vue」とほぼ同じ
今回、ふくろうの画像を表示しています。
画像を変える場合はこちらを変更してください。
さあ、次からいよいよcomponentの実装に入ります。(ここから長いよ)
Databaseのデータ構造は以下の通りです。
{ |
"history" : { |
"-Le4s5mpMB15fJw0I389" : { |
"timeStamp" : 1557024238193, |
"type" : "speech", |
"value" : "こんにちは" |
}, |
"-Le4s6s3kiEx127HhzpK" : { |
"timeStamp" : 1557024242624, |
"type" : "text", |
"value" : "はあい" |
} |
}, |
"text" : "はあい" |
} |
. ├─history │ └─<key> │ ├─timeStamp:更新日時(1970年01月01日 00:00:00 UTC からのミリ秒数) │ ├─type:speech(発話) or text(文字入力) │ └─value:発話内容または入力文字 └─text:hostアプリからの入力文字 |
<key>はFirebaseにpushする際に作成。
機能は
- 履歴(history)の更新、及び通知
- hostアプリからの入力文字(text)の更新、及び通知
で、hostアプリから入力された文字やclientアプリの発話内容も合わせて履歴の更新はclientアプリのみで行っています。
hostアプリは入力内容の更新、更新履歴は表示のみとしています。
以下は各アプリのHelloWorld.vueでのFirebaseに関する処理を抜粋しています。
onText() { |
let self = this; |
firebase |
.database() |
.ref("text") |
.on("value", function(snapshot) { |
let text = snapshot.val(); |
self.text = text; |
}); |
}, |
Firebaseのtextが更新されるたびに呼ばれる
updateHistory(val, type) { |
let database = firebase.database().ref(); |
let updates = {}; |
let postData = { |
timeStamp: new Date().getTime(), |
type: type, |
value: val |
}; |
var newPostKey = database.child("history").push().key; |
updates[`/history/${newPostKey}`] = postData; |
database.update(updates); |
} |
hostアプリでの入力値、clientアプリでの音声認識した内容をFirebaseに更新する
setMessage() { |
firebase |
.database() |
.ref("text") |
.set(this.text); |
this.text = ""; |
}, |
入力した文字をFirebaseに登録する
onHistory() { |
let self = this; |
firebase |
.database() |
.ref("history") |
.on("value", function(snapshot) { |
let history = snapshot.val(); |
self.reverseHistory(history); |
}); |
}, |
履歴が更新されるたびに呼ばれる
clientアプリのみで実装しています。
recognition() { |
let self = this; |
self.mic = true; |
var recognition = new webkitSpeechRecognition(); |
recognition.lang = "ja-JP"; |
recognition.start(); |
recognition.onresult = function(event) { |
let speech = event.results[0][0].transcript; |
// 発話内容を履歴に残す |
self.updateHistory(speech, "speech"); |
}; |
recognition.onend = function(event) { |
self.mic = false; |
}; |
}, |
音声認識を実装。Chromeで動かす際はSpeechRecognitionの接頭辞にwebkitを付与する。Firefoxの場合は付与しなくて良い(らしい)が今回はChromeのみを対象とするので割愛。
textToSpeech(text) { |
let utterThis = new SpeechSynthesisUtterance(text); |
synth.speak(utterThis); |
utterThis.onerror = function(event) { |
console.log("SpeechSynthesisUtterance Error: " + event.utterance.text); |
}; |
}, |
hostアプリで入力された文字列を読み上げる。
Windows、Mac、Android等、利用環境により音声の種類が若干異なる。今回は特に指定していないが、環境ごとに選択可能。
インフルエンザで寝込んでいた時に、別の部屋で遊んでいたこども(5歳)とコミュニケーションを取りたいなと思ったことがきっかけです。
その時は、某スマートスピーカーが家にあるので、スマートフォンからスマートスピーカーを使って話しかけたら、ものすごくびっくりして
「今、スマートスピーカーがなんかしゃべった!!!」
と泣きながら部屋に駆け込んできました。
声は私のはずなのに…スマートスピーカーが突然、話し出すとそんなに驚くんだと驚きました。
その体験を元に今回のようなアプリを作ったらおもしろいんじゃないかと思いつきました。
(ちなみにかなりお気に入りのぬいぐるみがふくろうの為、ふくろうの画像をチョイス)
そしてこどものために、こどもが使いやすいように考慮して作ってみて気づいたのですが、うちのこどもはほとんどスマートフォンに慣れておらず、文字も打てないし、操作もあまりできません。
そんなこどもでも直感的に使える(clientアプリ)、話せなくても文字入力で会話ができる(hostアプリ)、機能が少なくわかりやすい、年齢、性別、国籍(今回は日本語設定していますが)、障害の有無、能力差などにかかわらず、できるだけ多くの人が利用可能なデザイン…
これってもしかして、ユニバーサルデザイン!?
以下はユニバーサルデザインの7原則です。(Wikipediaより抜粋)
1.どんな人でも公平に使えること。(公平な利用)
2.使う上での柔軟性があること。(利用における柔軟性)
3.使い方が簡単で自明であること。(単純で直感的な利用)
4.必要な情報がすぐに分かること。(認知できる情報)
5.うっかりミスを許容できること。(失敗に対する寛大さ)
6.身体への過度な負担を必要としないこと。(少ない身体的な努力)
7.アクセスや利用のための十分な大きさと空間が確保されていること。(接近や利用のためのサイズと空間)
米ノースカロライナ州立大学デザイン学部・デザイン学研究科(College of Design)のロナルド・メイスが提唱
かなり該当すると思う(自負)のですが、どうでしょうか。
(あ、後付けすぎる…す、すみません…)

大喜びでした。最初は、clientアプリを表示したスマートフォンを渡して、hostアプリから文字を打って名前を呼び掛けたら「???」となって大興奮。
スマートフォンのふくろうが話しかけたと思ったみたいです。でもそれもすぐにバレました。(笑)
その後は、hostアプリで適当にキーボードを叩いてスマートフォンからの発話を楽しんでいました。
どちらかというと、そっちが楽しかったようで私の思惑からは外れてしまいましたが、こどもといつもとは違うコミュニケーションが取れて楽しかったです。
ここから色々と工夫してまた遊んでみたいと思いました。(←エピソード的UX)
最後までお読みいただき誠にありがとうございます。