Web Speech API使ってみた

# 1. はじめに
もうすぐクリスマスです。ついこの間まで暖かかったのに、今では寒すぎて朝布団から出れず、二度寝ならぬ四度寝くらいしている毎日です。そんな毎朝を起こしてくれるのはAlexaのアラーム。そんな時ふと音声認識アプリを自分でも作れるのでは？と思い調べたどり着いたのが**Web Speech API**です。

# 2. Web Speech APIとは
#### Web Speech APIの概要
Web Speech APIは、ウェブブラウザ上で音声の合成（テキスト読み上げ）および音声認識を実現するためのAPI。これにより、ウェブアプリケーションがユーザーの音声を受け取り、またユーザーに対して音声で情報を提供することが可能となる。

#### 基本説明
* **音声合成（Text-to-Speech）**
テキストを音声に変換してユーザーに提供する。
* **音声認識（Speech Recognition）**
ユーザーの音声を認識し、テキストデータに変換する。

#### 特徴
* **ブラウザ対応**
主要なブラウザでサポートされており、専用のプラグインや拡張機能をインストールせずに利用可能。

* **リアルタイム性**
音声認識はほぼリアルタイムで行え、ユーザーとの対話性を向上。

* **多言語対応**
複数の言語に対応していて、グローバルなアプリケーションにも利用可能。

# 3. 基本的な使い方（音声認識）

```JavaScript:test.js
// 初期化
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
// 言語設定を日本語に
recognition.lang = 'ja-JP';
// 解析中の結果を表示する
recognition.interimResults = true;
// 認識のたびに継続的に結果を返す
recognition.continuous = true;

// 音声認識開始
recognition.start();

// 認識した音声の結果を処理するイベントハンドラー
recognition.onresult = (event) => {
    for (let i = event.resultIndex; i < event.results.length; i++) {
        let results = event.results
        if (results[i].isFinal) {
            console.log('解析終了')
            // 解析が終了したあとの処理
            setRecogniziedText(event.results[i][0].transcript)
        } else {
            console.log('認識中')
            // 解析中の処理
            setInputText(event.results[i][0].transcript)
        }
    }
}
```
詳しくは公式ドキュメントなど参考に[Web Speech API](https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API)
# 4. 実際の利用例
今回はReact、JavaScriptで音声認識機能（Speech Recognition）のみ使用してみました。
### 機能概要
* チャットアプリケーションの一部 Component
* ページ表示からずっと音声認識を続ける
（デフォルトの場合、音の検知ができなくなり数秒たつと終了してしまう。）
* 「開始」と「終了」の音声が認識された間のみ認識結果をブラウザに表示

```JavaScript:InputField.jsx
// InputField.jsx
import React, { useState } from 'react';

const InputField = ({ onSendMessage }) => {

  const [inputText, setInputText] = useState('');
  const [recogniziedText, setRecogniziedText] = useState('');

  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  let recognition = new SpeechRecognition();
  recognition.lang = 'ja-JP';
  recognition.interimResults = true;
  recognition.continuous = true;

  recognition.onend = () => {
    console.log('Audio capturing will be continued');
    recognition.start();
    return;
  }
  recognition.onaudiostart = () => {
    console.log('Audio capturing started onaudiostart')
  }
  recognition.onaudioend = () => {
    console.log("Audio capturing ended onaudioend");
  }
  recognition.error = (event) => {
    console.log('エラーが発生しました。', event.error)
  }
  recognition.onnomatch = () => {
    console.log('認識できませんでした。')
  }

  const sendMessage = () => {
    console.log('sendMessage')
    let text = document.getElementById('recognizedText').value
    if (text){
      onSendMessage({ from: 'user', text: text })
      sendMessageToAI(text)
      setRecogniziedText(() => {
        return ''
      })
    }
  }

  let recognizeFlag = false
  //認識した文字の処理
  recognition.onresult = (event) => {
    for (let i = event.resultIndex; i < event.results.length; i++) {
      let results = event.results
      if (results[i].isFinal) {
        //入力欄の値を初期化
        setInputText(() => {
          return ''
        })
        console.log('認識完了')
        if (event.results[i][0].transcript.includes('開始')){
          recognizeFlag = true
          continue
        } else if(event.results[i][0].transcript.includes('終わり')){
          recognizeFlag = false
          sendMessage()
        }
        if (recognizeFlag){
          setRecogniziedText((text) => text + event.results[i][0].transcript)
        }
      } else {
        console.log('認識中')
        setInputText(event.results[i][0].transcript)
      }
    }
    
  }

  //外部APIの呼び出し関数
  const sendMessageToAI = (message) => {
    // APIの呼び出し

    //処理詳細

    // レスポンスの受け取り
    const response = 'APIの返り値'
    onSendMessage({ from: 'bot', text: response})
  };

  const deleteInputText = () => {
    console.log('入力内容のクリア')
    setRecogniziedText(() => {
      return ''
    })
  }

  window.onload = function(){
    recognition.start();
  }

  return (
    <div className="input-field">
      <textarea id='recognizedText' type="text" rows='5' value={recogniziedText + inputText} />
      <button onClick={deleteInputText}>input Clear</button>
    </div>
  );
};

export default InputField;
```

# 5. 課題
#### Web Speech APIを使う際に遭遇した課題
* ドキュメント通り実装した場合、音の検知が数秒なくなると音声認識が終了してしまう。
* たまに音声を発していても反応しないことがある。
* 解析情報の限界（最近の言葉や、固有名詞などの認識がうまく行かなかった。）
#### それに対する解決策や工夫した点
* 音声認識が終了してしまっても、強制的にまた認識を始めることで永続的な音声認識を可能にさせた。
* 音声認識の終始をある一定の言葉の認識で合図させた。（実際は認識は常に行っており、言葉の合図により内部処理のオンオフを実装）
# 6. まとめ
無料のAPIですが認識もかなりよく、なにより導入がとても簡単でした。
簡単な音声認識機能をアプリケーションに入れたい場合などに良いのかと思いました。（ですが音声認識機能がメインだったり本格的なアプリケーションには物足りないのかと感じました。）
React用の音声認識hooksがあるみたいなので、そちらも今度使っていきたいと思います。（使われているAPIは同じものみたいです。）有料の音声認識APIも様々なものが出ているのでそちらも試せたらいいなと思いました。
上のソースは今回作成したアプリケーションのプロトタイプ的なもので、認識されたデータをOpen AI APIなどのAIツールに送りレスポンスを貰うチャットアプリケーションの一部です。まだ全然完成していなくバグや妥協部分が多いのでブラッシュアップをこれからしていきたいと思います。


# 7. 参考文献
#### 使用したリファレンスや参考になった資料のリスト
[Web Speech API を使ってWebブラウザで音声認識を行う](https://moewe-net.com/js/web-speech-api-recognition)
[Web Speech APIによる音声認識・音声合成](https://tomosoft.jp/design/?p=42975)
[Webアプリに高機能な音声認識を追加するWeb Speech API](https://kesin.hatenablog.com/entry/2013/08/27/Web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB%E9%AB%98%E6%A9%9F%E8%83%BD%E3%81%AA%E9%9F%B3%E5%A3%B0%E8%AA%8D%E8%AD%98%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8BWeb_Speech_API)
##### React hooks
[音声認識で音声をテキスト化するReactフック【react-speech-recognition】の紹介](https://bgtu-techblog.com/2023/05/21/%E9%9F%B3%E5%A3%B0%E8%AA%8D%E8%AD%98%E3%81%A7%E9%9F%B3%E5%A3%B0%E3%82%92%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%8C%96%E3%81%99%E3%82%8Breact%E3%83%95%E3%83%83%E3%82%AF%E3%80%90react-speech-recognition/)