Skip to content

読むのが面倒なので、読み上げて貰おう

  • Gatsby

📅 October 31, 2020

⏱️4 min read

どうも、黙読が苦手すぎてタイポを発見できないおじさんです。 今日は、自分のブログのタイポをサクッと見つけるために、ブログに読み上げ機能をつけてみました。AWS pollyとか使って、ピタゴラスイッチして遊ぼうと思ったのですが、なんと、webのAPIに読み上げ機能が既にありました。すごいぞweb API!!!

どれくらい使えるの?

canIuse Can I use Support tables for HTML5 CSS3 etc

どれくらい使えるの?どうせお試し機能で、ほとんどのブラウザで未対応でしょう?と思って、canIuseを確認したところ、なんと意外とサポートされてるではありませんか!!すごいぞSpeech API!!!

詳しくはこちらを

しかも、読み上げだけじゃなくて、音声認識のAPIもあるみたい。ただ、Web Speech API Speech Recognitionの方は流石に、読み上げほどのサポートはされてない。

実際に使ってみる

ということで、早速、このブログに入れてみました。 「え?表示されないよ?」って方は、サポート対象外の数少ないブラウザをご利用されているかと思います。 もしくは、僕のコードがバグっているか。

import React, {useState, useEffect} from "react";
import styled from '@emotion/styled'

export const SpeechButton = ({content}) => {
  const [rate, setRate] = useState(1.5)
  const uttr = new SpeechSynthesisUtterance(content)
  uttr.rate = rate;
  const [isStart, startSpeech] = useIsBoolean({
    trueFunc: () => speechSynthesis.cancel(),
    falseFunc: () => speechSynthesis.speak(uttr),
  })
  console.log(speechSynthesis.speaking)
  const [isStop, stopSpeech] = useIsBoolean({
    trueFunc: () => speechSynthesis.resume(),
    falseFunc: () => speechSynthesis.pause(),
  }) 

  const onRateChange = (e) => {
    const rate = e.target.value;
    setRate(rate);
  }

  useEffect(() => {
    uttr.rate = rate;
    return () => {
      speechSynthesis.cancel();
    }
  }, [rate])

  const startText = isStart ? '終了' : '読み上げ'
  const pauseText = isStop ? '再開' : 'ストップ'

  const stopButton = isStart && <Button style={{backgroundColor: 'red'}}onClick={stopSpeech}>{pauseText}</Button>
  const changeRate = !isStart && (
    <>
    <label>Speed</label>
    <input
      type="number"
      min="0.1"
      max="5"
      step="0.1"
      value={rate}
      onChange={onRateChange}
    ></input>
    <Input
      type="range"
      min="0.1"
      max="5"
      step="0.1"
      value={rate}
      onChange={onRateChange}
    ></Input>
    </>
  )
  if (!('speechSynthesis' in window)) return

  return (
    <div>
      <Button style={{backgroundColor: 'blue'}} onClick={startSpeech}>{startText}</Button>
      {stopButton}
      {changeRate}
    </div>
  )
};

export const useIsBoolean = ({defaultBool = false, trueFunc, falseFunc}) => {
  const [bool, setBoolean] = useState(defaultBool);
  const onClick = () => {
    setBoolean(bool => !bool);
    if (bool) {
      trueFunc();
      return
    }
    falseFunc()
  }
  return [bool, onClick]
};

const Button = styled.button`
  color: #fff;
  border-radius: 0.5rem;
  box-shadow: 2px 2px 3px 1px #666;
  -moz-box-shadow: 2px 2px 3px 1px #666;
  -webkit-box-shadow: 2px 2px 3px 1px #666;
  margin-bottom: 2rem;
  margin-right: 0.6rem;
`
const Input = styled.input`
  background-color: #c7c7c7;
  height: 3.5px;
  width: 6rem;
`

なんか、ぐじゃぐじゃ書いてますが、こんな感じで使えます。

speechSynthesis.speak(
    new SpeechSynthesisUtterance("ここのテキストを読み上げてくれます。")
);

ハマリポイント

buildエラー

このブログは、GatsByを使っているので、デプロイするとNetlifyでSSGされます。 そのときにもちろん、ブラウザにしかないSpeechSynthesisUtterance コイツラを呼ぼうとするとエラーになります。

クライアントサイドでしか動かないコードをuseEffect内に移動するなど方法はありますが、面倒だったので、雑に、windowオブジェクトがない時は、コンポーネントを読み込まないようにしました。

const speechButton = (typeof window !== 'undefined') && <SpeechButton content={content} /> 

終わらない読み上げ

Speech APIの仕様っぽいのですが、リロードしても、ページバックしても読み上げ続けます。 なんとうざい仕様でしょうか。 仕方ないので、useEffectのクリーンアップ関数で、止めるようにしました。

  useEffect(() => {
    return () => {
      speechSynthesis.cancel();
    }
  }, [])

ホントは、問答無用でキャンセルを呼ぶよりも、読み上げてるかどうかSpeechSynthesis.speakingで調べた方が良さそうですが、正しくリロード後など読み上げ状況を返してくれないので、こうなりました・・・。

あれ、awsの勉強しようと思ってたのに、私は何をしてるのだろうか・・・。

ほいじゃーまたー。

← PrevNext →
  • @masayuki031