ReactHookで簡単に音声認識

# はじめに
ChatGPTの影響で音声認識をさくっと使いたいとき、
あると思います。
さくっと入れられるようにコンポネント化しました。

## 実行結果
![recog2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493888/2e4df8fc-1dfb-0dc1-4618-92974a53d95f.gif)

## ソースコード

### 音声認識コンポネント
```react
import { useEffect, useRef, useState } from "react";

interface ISpeechRecognitionEvent {
  isTrusted?: boolean;
  resultIndex: number;
  results: {
    isFinal: boolean;
    [key: number]:
    | undefined
    | {
      transcript: string;
    };
  }[];
}

interface ISpeechRecognition extends EventTarget {
  grammars: string;
  lang: string;
  continuous: boolean;
  interimResults: boolean;
  maxAlternatives: number;
  serviceURI: string;
  onaudiostart: () => void;
  onaudioend: () => void;
  onend: () => void;
  onerror: () => void;
  onnomatch: () => void;
  onresult: (event: ISpeechRecognitionEvent) => void;
  onsoundstart: () => void;
  onsoundend: () => void;
  onspeechstart: () => void;
  onspeechend: () => void;
  onstart: () => void;
  abort(): void;
  start(): void;
  stop(): void;
}

declare global {
  interface Window {
    SpeechRecognition: any | { webkitSpeechRecognition: any };
  }
}
declare var webkitSpeechRecognition: any;

interface IUseSpeechRecognition {
  enabled: boolean;
  lang: "ja" | "en";
  continuous: boolean; // 連続的に音声認識
  interimResults: boolean; // 途中結果の出力
}

interface ISpeechRecognitionResult {
  finishText: string;
  interimText: string;
}

/**
 * 音声認識ReactHook
 * @param props 
 * @returns 
 */
export const useSpeechRecognition = (props: IUseSpeechRecognition): ISpeechRecognitionResult => {
  const ref = useRef<ISpeechRecognitionResult>({
    finishText: '',
    interimText: ''
  });
  const [text, setText] = useState<string>();
  window.SpeechRecognition = window.SpeechRecognition || webkitSpeechRecognition;
  var recognition: ISpeechRecognition = new webkitSpeechRecognition();
  recognition.lang = props.lang;
  recognition.interimResults = props.interimResults;
  recognition.continuous = props.continuous;

  recognition.onresult = (event: ISpeechRecognitionEvent) => {
    if (props.enabled) {
      let interimTranscript = '';
      let finalTranscript = '';

      for (let i = event.resultIndex; i < event.results.length; ++i) {
        const transcript = event.results[i][0].transcript;
        if (event.results[i].isFinal) {
          finalTranscript += transcript;
          ref.current.finishText = finalTranscript;
        } else {
          interimTranscript += transcript;
          ref.current.interimText = interimTranscript;
        }
      }
    }
    else {
      ref.current.interimText = "";
      ref.current.finishText = "";
    }
  };

  const startRecognition = () => {
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(() => {
        recognition.start();
      })
      .catch(error => {
        console.error(error);
      });
  }

  const stopRecognition = () => {
    recognition.stop();
  };

  useEffect(() => {
    if (props.enabled) {
      startRecognition();
    }
    else {
      stopRecognition();
    }
  }, [props.enabled]);

  return ref.current;
}
```

### 呼び出すとき
```react
const speechText = useSpeechRecognition({ 
  enabled: true, 
  lang: "ja", 
  continuous: true, 
  interimResults: true 
});

const getNowText = () => {
  console.log("音声認識(途中): " + speechText.interimText);
  console.log("音声認識(結果): " + speechText.finishText);
}
```