audio-recorder-polyfillを使ってiOS対応のWebブラウザ録音機能

4 min read読了の目安(約4000字

やりたいこと

  • ブラウザで録音機能を作りたい

問題

  1. MediaRecorderを使いたいが、調査(2021年2月)時点ではiOS Safariは未対応だった・・・。
     - MediaRecorder以外にも方法はありそうだったが、一番シンプルであり、今後を考えるとこれを使えるようにpolyfillした方がよさそうだった。
  2. 純粋にMediaRecorderを使用した場合、reactの状態管理とぶつかる。audioはそれ自体が内部的に状態を持っており、reactのstateと二重管理になった場合に片方の状態をロストすることが起きる。
    👇 参考
    https://qiita.com/kazumicho/items/086864a9d78121d54aa7

背景

  • React(Next.js)に導入する

今回つかうもの

Setup polyfill

  1. yarn add audio-recorder-polyfill
  2. ファイル追加: public/polyfill.js
import AudioRecorder from 'audio-recorder-polyfill'
window.MediaRecorder = AudioRecorder
  1. ファイル修正: _app.tsx に以下のスクリプトを追加
      ...
      <Head>
        <script>
          {
            () => {
              // CSRでscriptを実行するために行う
              if (!window.MediaRecorder) {
                document.write(
                  decodeURI('%3Cscript defer src="/polyfill.js">%3C/script>')
                )
            }
          }
        }
        </script>
      </Head>
      ...

Install react-media-recorder

このライブラリを使うことでMediaRecorderの内部状態とreactの状態管理を両立させる。

yarn add react-media-recorder

hooksが提供されているので、次のように宣言できる。

  const {
    status,
    startRecording,
    stopRecording,
    resumeRecording,
    pauseRecording,
    mediaBlobUrl,
  } = useReactMediaRecorder({ audio: true });

Fin: Example

実装イメージは次のような形です。

export default function Home() {
  const {
    status,
    startRecording,
    stopRecording,
    resumeRecording,
    pauseRecording,
    mediaBlobUrl,
  } = useReactMediaRecorder({ audio: true });

  const [isNowRecording, setIsNowRecording] = useState(false)


  function onStart() {
    startRecording()
    setIsNowRecording(true)
  }
  function onPause() {
    pauseRecording()
  }
  function onResume() {
    resumeRecording()
  }
  function onStop() {
    stopRecording()
    setIsNowRecording(false)
  }


  return (
    <div className={styles.container}>
      <Head>
        <script>
          {
            () => {
              // CSRでscriptを実行するために行う
              if (!window.MediaRecorder) {
                document.write(
                  decodeURI('%3Cscript defer src="/polyfill.js">%3C/script>')
                )
              }
            }
          }
        </script>

        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>


        <p className={styles.description}>
          Get started by editing{' '}
          <code className={styles.code}>pages/index.js</code>
        </p>

        <div className={styles.grid}>
          <div className={styles.card}>
            <button onClick={onStart} type="button" className="m-1">Record</button>
            <button onClick={onPause} type="button" className="m-1">Pause</button>
            <button onClick={onResume} type="button" className="m-1">Resume</button>
            <button onClick={onStop} type="button" className="m-1">Stop</button>
          </div>
          <div className={styles.card}>

            <audio src={mediaBlobUrl} controls />
          </div>



          <div className={styles.card}>
            {isNowRecording ? <p>録音中</p> : <p>録音可能</p>}
          </div>
        </div>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
        </a>
      </footer>
    </div>
  )
}

Githubにリポジトリ公開しているのと、Vercelにデプロイしているので参考にiOSで開いてみてください。

https://github.com/nitaking/react-voice-recorder-sample

https://react-voice-recorder-sample-git-main-nitaking.vercel.app/