典型的な説明
典型的な TTS のインスタンスの初期化手順の説明では、1)ACTION_CHECK_TTS_DATA 投げる(非同期)
Intent intent = new Intent(); startActivityForResult(intent, REQ_CHECK_TTS);
2)1)の結果を onActivitiyResult() で受ける
protected void onActivitiyResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQ_CHECK_TTS) { if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { ...... } }
3)2)を受けて TTS エンジンのインスタンスを作成する(非同期)
tts = new TextToSpeech(this, this);
4)3)の結果を onInit() で受ける
public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { // TTSオブジェクト初期化成功 ... } }
5)言語を設定し、実際の発声を行う
Locale locale = 適当なロケール; if (tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE) { tts.setLanguage(locale); tts.speak(text , TextToSpeech.QUEUE_FLUSH, null); }
のようになる。
2)で、resultCode が CHECK_VOICE_DATA_PASS になっている場合に、「TTSエンジンが使えるようになった」と説明されるのが普通であるが、定数の名前がそうはなっていないので説明と齟齬があるように感じる。しかも、端末購入直後の Google TTS エンジンしか入っていない状況なら、選択できる TTS エンジンは1つだけなので、ユーザーには何の選択肢も与えられないが、日本のユーザーが TTS を使う場合、大抵 KDDI Lab の N2 TTS エンジンを入れるだろう。その状況で、1)を行うと、 Google TTS か N2 TTS かを選択するダイアログがでて、選択することになる。しかし、N2 TTS を選択したとしても、端末設定の TTS の設定値が Google TTS になっていれば、3)の時点で生成される TTS エンジンのインスタンスは Google TTS になってしまう。ユーザーの選択が反映されるのは、ただただ、2)の時点でかえってくるインテントに含まれるTextToSpeech.Engine.EXTRA_AVAILABLE_VOICES で取り出し利用可能な言語情報だけだ。この値の中から適当なものを選んで、5)の段階で isLanguageAvailable () せずに、無理矢理 setLanguage() しても、TTS エンジンが違うので、偶然一致するものがない限り、反映されない。
欧米圏以外で、自国語の TTS エンジンを利用しようとすると、以上のような状況になってしまって、UX 的に非常にまずいことになる。
たぶん Google の説明が不十分なのだ。そして、不十分な情報を元に英語だけ発話させておしまいのサンプル説明がはびこっているのだ。
それにもまして、API の構成があまりよろしくないと思う。よろしくないので、直してほしいが、多分どうかならないので、自前でなんとかしなければならない。ここからが本題です。
TTS エンジンは切り替え可能
まず問題なのは、TTS エンジンの切り替えだ。これができないと、いちいちユーザーに設定画面から、切り替えてから使ってもらわざるを得ない。古い API では、
tts = new TextToSpeech(this, this); tts.setEngineByPackageName(enginepackangename);のように setEngineByPackageName() で、エンジンのパッケージ名を使って切り替え可能だ。
API Level 14 以降はこれが廃止されて、
tts = new TextToSpeech(this, this, enginepackangename);
のように、コンストラクタの引数としてパッケージ名を与えて、所望の TTS エンジンを生成できる。多分途中で切り替えると何かと問題が起こったので、こういう仕様になったのであろうと推測する。また、どの API Level でも
tts = new TextToSpeech(this, this);は、端末設定の設定値のよってTTSエンジンが決まる。
TTS エンジンのパッケージ名も取得可能
これについては、公開APIの中にそれらしいものが見当たらない。しかし以下のようにすれば、自前で可能だ。PackageManager pm = getPackageManager(); Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); List<ResolveInfo> resolveInfos
= pm.queryIntentServices(intent,PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfos != null) { for (ResolveInfo resolveInfo : resolveInfos) { ServiceInfo service = resolveInfos.serviceInfo; if (service != null) { // ここでTTSエンジンの情報を集める // service.packageName の値がTTSエンジンのパッケージ名 } } }
ACTION_CHECK_TTS_DATA の本当の役割
実のところ、ACTION_CHECK_TTS_DATA をしないでも TTS エンジンの生成は可能だ。全く問題なく機能する。端末設定に従って発話するだけなら問題ない(が TTS エンジンの辞書にない文字列が来たら、TTS エンジンはシカトすることになる)
TTS エンジンを生成する前に、TTS エンジンのパッケージ名から、サポートするロケールの一覧がとれればいいことになる。当然 Android OS 内部ではそれができているので、方法さえわかれば、後はまねするだけだ。で、発見したのが、以下のコードだ。
一緒に、パッケージ名もつけてかえってきてくれると何かと便利なんだが、そうなっていないので、与えたパッケージ名は自前て管理しないといけないのがちと面倒くさいが、我慢しよう。アイディアとしては REQ_CHECK_TTS は任意の整数値が使えるので、これをうまいこと使うてもあるかと思う。
で、ACTION_CHECK_TTS_DATA の本当の意義は、EXTRA_AVAILABLE_VOICES で使えるロケールを知らせるばかりでなく、EXTRA_UNAVAILABLE_VOICES で使えないものも同時に教えてくれる点にある。「使えないものって無限にあるじゃないか」と怪訝に思うだろうが、ACTION_CHECK_TTS_DATAのインテントに EXTRA_CHECK_VOICE_DATA_FOR として、チェックしたいロケールをつめてやると、使えるもの/使えないものに分類してくれる。これが、ACTION_CHECK_TTS_DATAの元々の意味だった訳だ。
使えるロケールの一覧が欲しいだけなら、EXTRA_CHECK_VOICE_DATA_FORは設定しないでかまわない。
以上をふまえれば、より良いUXのTTS アプリが作成できるでしょう。
*注意
TTS エンジンを生成する前に、TTS エンジンのパッケージ名から、サポートするロケールの一覧がとれればいいことになる。当然 Android OS 内部ではそれができているので、方法さえわかれば、後はまねするだけだ。で、発見したのが、以下のコードだ。
intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); intent.setPackage(enginepackangename); startActivityForResult(intent, REQ_CHECK_TTS);インテントに、setPackage() でパッケージ名を指定してやるだけで、例の TTS エンジン選択ダイアログは出ずに、欲しいエンジンのロケールだけを返してくれる。
一緒に、パッケージ名もつけてかえってきてくれると何かと便利なんだが、そうなっていないので、与えたパッケージ名は自前て管理しないといけないのがちと面倒くさいが、我慢しよう。アイディアとしては REQ_CHECK_TTS は任意の整数値が使えるので、これをうまいこと使うてもあるかと思う。
で、ACTION_CHECK_TTS_DATA の本当の意義は、EXTRA_AVAILABLE_VOICES で使えるロケールを知らせるばかりでなく、EXTRA_UNAVAILABLE_VOICES で使えないものも同時に教えてくれる点にある。「使えないものって無限にあるじゃないか」と怪訝に思うだろうが、ACTION_CHECK_TTS_DATAのインテントに EXTRA_CHECK_VOICE_DATA_FOR として、チェックしたいロケールをつめてやると、使えるもの/使えないものに分類してくれる。これが、ACTION_CHECK_TTS_DATAの元々の意味だった訳だ。
使えるロケールの一覧が欲しいだけなら、EXTRA_CHECK_VOICE_DATA_FORは設定しないでかまわない。
以上をふまえれば、より良いUXのTTS アプリが作成できるでしょう。
*注意
N2 TTSで、setLanguage()を実行しても、指定のロケール(女性/男性)に切り替えできません。多分N2 TTS の仕様だと思います。
もしかかすると、ロケール情報に女性/男性の識別に含まれてしまっているのが、実装者を混乱させているのかもしれません。
*注意
SVOX Classic TTSというTTSエンジンが無料でダウンロードできるのだが、インストールすると、SVOX Classic TTSのパッケージ名とともにACTION_CHECK_TTS_DATA を投げたとき、Google TTSとSVOX Classic TTSの選択ダイアログがでてきてしまう。そしてSVOX Classic TTSを選択すると、SVOX Classic TTSのでもアプリが起動されてしまうので、本来欲しかった情報が得られないことになる。無料配布の宣伝用のエンジンなので致し方ない気もするが、UXを破壊していることに変わりはないとおもう。
*注意
SVOX Classic TTSというTTSエンジンが無料でダウンロードできるのだが、インストールすると、SVOX Classic TTSのパッケージ名とともにACTION_CHECK_TTS_DATA を投げたとき、Google TTSとSVOX Classic TTSの選択ダイアログがでてきてしまう。そしてSVOX Classic TTSを選択すると、SVOX Classic TTSのでもアプリが起動されてしまうので、本来欲しかった情報が得られないことになる。無料配布の宣伝用のエンジンなので致し方ない気もするが、UXを破壊していることに変わりはないとおもう。