今回はHTMLのAPIであるSpeechSynthesis APIを使った音声読み上げ機能の紹介です。

注意点として、現在(2021年11月時点) AndroidのWebViewではSpeechSynthesis APIはサポートされていません(標準ブラウザでは動作します)。

アプリのソースコードは、こちらのリンクよりMonacaアカウントへインポートできます。


ベースになるアプリについて

今回はベースとしてFramework7フレームワークを用いています。
Monacaではテンプレートの中にも用意されていますので、導入は簡単です。

www/index.htmlについて

www/index.html ではdivタグだけ配置して、 data-url 属性にて /home に該当するファイルを読み込んでいます。

<div id="app">
    <!-- Views/Tabs container -->
    <div class="views tabs safe-areas">
        <div id="view-home" class="view view-main view-init" data-url="/home">
        </div>
    </div>
</div>

ルーティングを担当するファイル js/routes.js では /home にて pages/home.html を読み込む指定をしておきます。

const routes = [
  {
    path: "/",
    url: "./index.html",
  },
  // ホーム画面
  {
    path: "/home",
    name: "Home",
    componentUrl: "./pages/home.html"
  },
  // Default route (404 page). MUST BE THE LAST
  {
    path: "(.*)",
    url: "./pages/404.html",
  },
];

pages/home.html について

  
home.htmlのHTML部分は次のようになります。読み上げる文字列の入力と、それを実行するボタンになります。

<template>
    <div class="page" data-name="home">
        <!-- Top Navbar -->
        <div class="navbar">
            <div class="navbar-bg"></div>
            <div class="navbar-inner">
                <div class="title sliding">音声読み上げ</div>
            </div>
        </div>

        <!-- Scrollable page content-->
        <div class="page-content">
            <div class="list">
                <form id="login">
                    <ul>
                        <li>
                            <a href="#" class="item-link smart-select" id="smart-voice">
                                <select name="voices">
                                </select>
                                <div class="item-content"> 
                                    <div class="item-inner">
                                        <div class="item-title">音声</div>
                                        <div class="item-after"></div>
                                    </div>
                                </div>
                            </a>
                        </li>
                    </ul>
                </form>
            </div>
             <div class="block block-strong">
                 <textarea name="text" rows="4" cols="40" placeholder="読み上げる文章を入力してください"></textarea>
             </div>
             <div class="block">
                <button @click=${say} class="button button-large button-fill">読み上げる</button>
            </div>
        </div>
    </div>
</template>

なお、ここで name=voices というselectタグを用意しています。ここには選択可能な音声読み上げオプション(女性、男性の声など)が入ります。

音声オプションを取得する

speechSynthesis が存在するかどうかでSpeechSynthesis APIが利用できるかどうかを判定できます。実際に音声が使えるようになったかどうかは synth.onvoiceschanged のステータスで判定できます。これが呼ばれれば getVoices が利用できます。

export default (props, {$f7}) => {
       // SpeechSynthesis APIを取得
        const synth = window.speechSynthesis;
        let voices;

        // APIの利用可否を判定
        if (synth) {
            // ボイスが利用できるよう状態まで2秒ほど待機
            setTimeout(() => {
                voices = synth.getVoices();
                const html = voices.map((voice, index) => {
                    return <option value="${voice.voiceURI}">${voice.name} (${voice.lang})</option>
                }).join('');

                // selectタグに音声一覧を適用する
                $f7.$el.find('select[name="voices"]').html(html);

                // 日本語の音声を初期値にする
                const jaVoice = voices.filter(v => v.lang == 'ja-JP')[0].name;
                $('.item-after').text(jaVoice);
            }, 1000 * 2);
        } else { // SpeechSynthesis APIが使えない場合
            alert('音声読み上げ非対応です');
        }
}

音声読み上げの実行

そしてテキストを入力した後で、読み上げボタンを押します。読み上げるテキストを SpeechSynthesisUtterance のインスタンスに渡して、ボイスを指定します。iOSのようにボイスがない場合はデフォルトの音声が使われるようです。

最後に speak メソッドで読み上げを実行します。

// 読み上げボタンを押した時のイベント
const say = () => {
  // 入力されているテキストを取得
  const text = $f7.$el.find("[name="text"]").val();
  // 指定されているボイスを取得
  const select = $f7.$el.find("[name="voices"]").val();
  // 読み上げオブジェクトへの変換
  const utterThis = new SpeechSynthesisUtterance(text);
  // 音声を設定(なければデフォルトの音声)
  utterThis.voice = voices.filter(v => v.voiceURI === name)[0];
  // 読み上げ
  synth.speak(utterThis);
};