ブラウザにマイクテスト機能を付ける

#はじめに
音声を扱うWebアプリを作成する時に、マイクテスト機能が欲しくて作成しました。
getUserMediaメソッドで音声を取得し、[MediaRecorder API](https://developer.mozilla.org/ja/docs/Web/API/MediaStream_Recording_API)を使って録音する方法です。
こちらの[記事](https://qiita.com/massie_g/items/ecfc16018210441051b4)をベースに作成しているので適宜参照して下さい。
#HTMLの設定
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>マイクテスト</title>
</head>
<body>
  <h1>マイクテスト</h1>
  <button onclick="startAudio()">音声取得</button>
  <button onclick="startMicTest()">録音開始</button>
  <button onclick="stopMicTest()">音声確認</button><br>
  <audio id="playback_mictest" controls></audio>
</body>
<script>
// 下記で記述
</script>
</html>
```
まずはHTMLから。buttonタグで、「音声取得」「録音開始」「音声確認」の3つのボタンを作ります。
下記ではそれぞれの関数「startAudio()」「startMicTest()」「stopMicTest()」について説明していきます。
再生用のプレイヤーはaudioタグで簡単に設定できます。

(画面イメージ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1669710/f7229685-2b97-4876-ff90-b093dd8055f6.png)
#音声の取得
```javascript:main.js
  // -- 音声の取得 --
  function startAudio() {
    if (!audioStream){
      // getUserMediaはpromise を返す
      navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true
      })
      .then(function (audio) { // promiseのresultをaudioStreamに格納
        audioStream = audio;
      })
      .catch(function (error) { // error
        console.error('mediaDevice.getUserMedia() error:', error);
        return;
      });
    }
  }
```
getUserMediaでユーザーが使うマイクとカメラの情報を取得できます。今回はaudioのみTrueにして音声を取得します。
getUserMediaはpromiseを返すので、.thenで受けて音声をaudioStreamに格納しておきます。
関数を設定すると、下記のポップアップが表示されるようになるので確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1669710/87931e58-384b-a77b-b2b6-5fa51d1b8c5d.png)

#録音スタート
```javascript:main.js
  // -- 録音開始 --
  function startMicTest() {
    micTestStream = new MediaStream();
    micTestStream.addTrack(audioStream.getAudioTracks()[0]);
    const micTestOptions = {
      mimeType : 'audio/webm; codecs=opus'
    };
    micTestRecorder = new MediaRecorder(micTestStream, micTestOptions);
    micTestChunks = []; // 格納場所をクリア
    // 一定間隔で録音が区切られて、データが渡される。
    micTestRecorder.ondataavailable = function(evt) {
      console.log("type=" + evt.data.type + " size=" + evt.data.size);
      micTestChunks.push(evt.data);
    };
    micTestRecorder.start(1000);
    console.log('start mic test');
  }
```
取得した音声をMediaRecorderAPIに録音する設定を行います。
MediaRecorderAPIにデータを渡すには、まずMediaStreamオブジェクトを作成する必要があります。getUserMediaで取得した音声データを([addTrackメソッド](https://developer.mozilla.org/ja/docs/Web/API/MediaStream/addTrack)) でMediaStreamオブジェクトに格納します。
次に、MediaStreamオブジェクトを、mimeTypeを任意で設定した上で、[MediaRecorder](https://developer.mozilla.org/ja/docs/Web/API/MediaRecorder)へ渡します。
そして最後に、イベントハンドラーの[MediaRecorder.ondataavailable](https://developer.mozilla.org/ja/docs/Web/API/MediaRecorder/ondataavailable)を使って、音声データ(dataavailableイベント)をchunksに格納します。
以上の設定を、[MediaRecorder.start](https://developer.mozilla.org/ja/docs/Web/API/MediaRecorder/start)で起動します。timesliceの設定ができるので、今回は1秒ごとに区切ってデータを処理しました。

#音声の確認
```javascript:main.js
  // -- 音声の確認 --
  function stopMicTest() {
    if (micTestRecorder) {
      micTestRecorder.stop();
      console.log("stop mic test");
    }
    micTestRecorder.onstop = function(evt) {
      console.log('micTestRecorder.onstop(), so playback');
      micTestRecorder = null;
      playMicTest();
    };

    // マイクテスト再生
    function playMicTest() {
      // Blobの作成
      const micTestBlob = new Blob(micTestChunks, { type: "audio/webm" });
      // 再生できるようにURLを生成
      micBlobUrl = window.URL.createObjectURL(micTestBlob);
      if (micBlobUrl){
        playbackMicTest.src = micBlobUrl;
        // 再生終了時
        playbackMicTest.onended = function() {
          playbackMicTest.pause();
          playbackMicTest.src = "";
        };
        // 再生
        playbackMicTest.play();
      }
    };
  }

```
ボタンを押すと、録音が停止してテスト音声が再生される設定を行います。
まず、[MediaRecorder.stop](https://developer.mozilla.org/ja/docs/Web/API/MediaRecorder/stop)で記録を停止します。MediaRecorder.onstopで、停止したらマイクテスト再生用の関数「playMicTest」を起動するようにします。
playMicTest関数では、音声データを再生するためにBlobとURLを設定します。
音声データが格納されているchunksからBlobデータを作成し、createObjectURLでURLを作成します。
このURLをaudioタグが設定されている「playbackMicTest」のソースに設定すれば完了です。
ちなみにonendedメソッドで、再生終了時にデータがリセットされるように設定しています。

#ソースコード
以上まとめると下記になります。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>マイクテスト</title>
</head>
<body>
  <h1>マイクテスト</h1>
  <button onclick="startAudio()">音声取得</button>
  <button onclick="startMicTest()">録音開始</button>
  <button onclick="stopMicTest()">音声確認</button><br>
  <audio id="playback_mictest" controls></audio>
</body>
<script>
  // 録音の準備
  let audioStream = null;
  let micTestStream =  null;
  let micTestRecorder =  null;
  let micBlobUrl = null;
  let micTestChunks = [];
  const playbackMicTest =  document.getElementById('playback_mictest');
  // -- 音声の取得 --
  function startAudio() {
    if (!audioStream){
      // getUserMediaはpromise を返す
      navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true
      })
      .then(function (audio) { // promiseのresultをaudioStreamに格納
        audioStream = audio;
      })
      .catch(function (error) { // error
        console.error('mediaDevice.getUserMedia() error:', error);
        return;
      });
    }
  }
  // -- 録音開始 --
  function startMicTest() {
    micTestStream = new MediaStream();
    micTestStream.addTrack(audioStream.getAudioTracks()[0]);
    const micTestOptions = {
      mimeType : 'audio/webm; codecs=opus'
    };
    micTestRecorder = new MediaRecorder(micTestStream, micTestOptions);
    micTestChunks = []; // 格納場所をクリア
    // 一定間隔で録音が区切られて、データが渡される。
    micTestRecorder.ondataavailable = function(evt) {
      console.log("type=" + evt.data.type + " size=" + evt.data.size);
      micTestChunks.push(evt.data);
    };
    micTestRecorder.start(1000);
    console.log('start mic test');
  }
  // -- 音声の確認 --
  function stopMicTest() {
    if (micTestRecorder) {
      micTestRecorder.stop();
      console.log("stop mic test");
    }
    micTestRecorder.onstop = function(evt) {
      console.log('micTestRecorder.onstop(), so playback');
      micTestRecorder = null;
      playMicTest();
    };

    // マイクテスト再生
    function playMicTest() {
      // Blobの作成
      const micTestBlob = new Blob(micTestChunks, { type: "audio/webm" });
      // 再生できるようにURLを生成
      micBlobUrl = window.URL.createObjectURL(micTestBlob);
      if (micBlobUrl){
        playbackMicTest.src = micBlobUrl;
        // 再生終了時
        playbackMicTest.onended = function() {
          playbackMicTest.pause();
          playbackMicTest.src = "";
        };
        // 再生
        playbackMicTest.play();
      }
    };
  }
</script>
</html>
```


#参考
[WebRTCハンズオン MediaRecorder編](https://qiita.com/massie_g/items/ecfc16018210441051b4)
[getUserMediaとMediaRecorder APIを使ってブラウザから録音](https://qiita.com/ryan_iwa/items/c2a333327f73e5432249)
[Javascriptでマイクから生データを取り出す](https://qiita.com/norimy/items/dadf229237c6bdf158d9)
[web audio api を使い、audioタグから取得した音声の音波を表示する ( javascript )](https://qiita.com/keeykeey/items/a12aee437a253b0b12e0)