iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🎙️

Implementing iOS-Compatible Web Browser Audio Recording with audio-recorder-polyfill

に公開
1

What I want to do

  • Create a recording function in the browser

Issues

  1. I wanted to use MediaRecorder, but as of the investigation (February 2021), iOS Safari was not supported...
    • There seemed to be other methods besides MediaRecorder, but it was the simplest, and considering the future, it seemed better to use a polyfill to make it available.
  2. When using MediaRecorder purely, it conflicts with React's state management. Audio itself has internal state, and if it becomes double-managed with React's state, one of the states may be lost.
    👇 Reference
    https://qiita.com/kazumicho/items/086864a9d78121d54aa7

Background

  • Introducing it into React (Next.js)

Tools to use this time

Setup polyfill

  1. yarn add audio-recorder-polyfill
  2. Add file: public/polyfill.js
import AudioRecorder from "audio-recorder-polyfill";
window.MediaRecorder = AudioRecorder;
  1. Modify file: Add the following script to _app.tsx
      ...
      <Head>
        <script>
          {
            () => {
              // Executed to run the script in CSR
              if (!window.MediaRecorder) {
                document.write(
                  decodeURI('%3Cscript defer src="/polyfill.js"%3E%3C/script%3E')
                )
            }
          }
        }
        </script>
      </Head>
      ...

Install react-media-recorder

By using this library, you can balance the internal state of MediaRecorder with React's state management.

yarn add react-media-recorder

Hooks are provided, so you can declare it as follows:

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

Fin: Example

The implementation image is as follows.

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>
          {() => {
            // Done to execute the script in CSR
            if (!window.MediaRecorder) {
              document.write(
                decodeURI('%3Cscript defer src="/polyfill.js"%3E%3C/script%3E')
              );
            }
          }}
        </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>Recording</p> : <p>Ready to record</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>
  );
}

I have the repository published on GitHub and deployed on Vercel, so please try opening it on iOS for reference.

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

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

Discussion

ジャバ・ザ・ハットリジャバ・ザ・ハットリ

Safari 14.1で開くとAn unexpected error has occurred.となるようです。コンソールにはError: Unsupported Browserと出ていました。