MediaRecorder APIを使った簡単なオーディオキャプチャ

MediaRecorder APIはNavigator.getUserMedia()内で使用されるシンプルな構造のAPIです。これを使えばユーザの入力デバイスからメディアストリームを手軽に録音し、即座にWebアプリケーションで利用することができます。今回の記事ではMediaRecorderの使用方法について、基本的な使い方を書いてみようと思います。MediaRecorderはFirefox Desktop、Mobile 25、FirefoxOS 2.0上でサポートされています。

他のオプションは?

Firefox OS上でメディアをキャプチャするのは案外手間がかかるものです。getUserMedia()を単独で使う場合raw PCMデータを生成するので、ストリームするのには問題ありません。しかし、音声やビデオをキャプチャして、PCMデータ上で手作業でエンコーディングしようと思うと、とたんに複雑になってきます。

Firefox OSのCamera APIを利用する手もあります。しかし、Certified APIだったのが最近Privilegedに格下げされました。

またWeb activitiesではCameraなどのアプリケーションを使ってメディアキャプチャできるものが提供されています。

上記2つの方法でやっかいなのは、音声トラックとビデオを同時にキャプチャするしかない点です。音声トラックだけが欲しい時は切り離さなくてはなりません。MediaRecorderを使えば、音声だけを簡単にキャプチャすることができます(とりあえず音声のみで、ビデオは後で追加できます)。

サンプルアプリケーション:Web Dictaphone

MediaRecorder APIの基本的な使用方法をデモするためにWebベースの音声レコーダを作ってみました。音声のサンプルデータなどを録音し、再生することができます。更に、Web Audio APIを使って装置のサウンド入力の視覚化もしてくれます。ここでは録音と再生の機能についてのみ触れます。

実際のデモアプリはここで、Githubのソースコードはこちらから取得できます (直接zip形式のファイルをダウンロードするにはここからどうぞ)。

CSSの機能

このアプリのHTMLはとても分かりやすいので、ここでは詳細は触れません。CSSについては説明しがいがありそうなので、この後の項で少し解説します。CSSに興味がなく、すぐJavaScriptについて知りたい方はここを飛ばして「アプリケーションの基本的なセットアップ」の項に進んでくださいね。

Calc() を使って、デバイスの高さにかかわらず、ビューポートにフィットしたインターフェースを保つ

calc関数は、CSSの便利なユーティリティの1つです。最初はそれほど感じなくても、そのうちに「どうして今まで無かったんだろう? CSS2のレイアウトは何て使い勝手が悪かったんだろう」と思うでしょう。この関数を使うことで、プロセス内の異なる単位を組み合わせて、CSSユニットの値を決める計算が可能となります。

例えば、Web音声レコーダには、縦方向に並んだ、メインとなる3つのユーザ・インターフェースのエリアがあります。まず、最初の2つ(ヘッダとコントローラ)の高さを指定します。

header {
  height: 70px;
}

.main-controls {
  padding-bottom: 0.7rem;
  height: 170px;
}

次に3番目のエリア(再生可能な3つの録音サンプルが格納されています)について、左側の余白やデバイスの高さに左右されずに、設定を行いたいと思います。Flexboxを使う方法がありますが、この程度の簡単なレイアウトに使うのは大げさです。その代わりに、3番目のエリアの高さを親要素の100%とし、他の2つのエリアの高さと余白をマイナスすることで解決できます。

.sound-clips {
  box-shadow: inset 0 3px 4px rgba(0,0,0,0.7);
  background-color: rgba(0,0,0,0.1);
  height: calc(100% - 240px - 0.7rem);
  overflow: scroll;
}

注:calc()はモダンブラウザのほとんどがサポートしています。Internet Explorer 9でさえもサポートしています。

チェックボックスの表示/非表示の仕掛け

これに関してはすでに、かなり詳しい解説がありますが、ここではチェックボックスの仕掛けについて説明したいと思います。これは、チェックボックスのラベルをクリックすることで、チェックボックスのチェック有無を操作できる機能を利用したものです。Web音声レコーダでは、ヘッダの右端にある?マークをクリックすると表示/非表示を切り替えられる、インフォメーション画面に活用できます。最初に、

label {
    font-family: 'NotoColorEmoji';
    font-size: 3rem;
    position: absolute;
    top: 2px;
    right: 3px;
    z-index: 5;
    cursor: pointer;
}

次に、インフォメーション画面(<aside>要素に含まれています)の仕様を決めます。位置を固定することで、レイアウトあちこちに現れて、メインのユーザ・インターフェースの邪魔にならないようにします。表示したい位置に現れるように初期設定でtransformを指定し、さらにスムーズに表示/非表示ができるようにtransitionを指定します。

aside {
   position: fixed;
   top: 0;
   left: 0;
   text-shadow: 1px 1px 1px black;  
   width: 100%;
   height: 100%;
   transform: translateX(100%);
   transition: 0.6s all;
   background-color: #999;
    background-image: linear-gradient(to top right, rgba(0,0,0,0), rgba(0,0,0,0.5));
}

最後に、チェックボックスがチェックされたとき(クリック、もしくはポインタを合わせたとき)の仕様を記述します。<aside>要素に続く部分は、水平方向へ移動して、スムーズに遷移することを表しています。

input[type=checkbox]:checked ~ aside {
  transform: translateX(0);
}

アプリケーションの基本的なセットアップ

録音したいメディアストリームをキャプチャするために、getUserMedia()を使います(gUMと省略します)。そして、MediaRecorder APIでストリームを録音し、その結果を生成された<audio>要素に与えることで、再生が可能となります。

まず、ベンダープレフィックスを問わずにgUMを動作させるために、分岐を組み込みます。そうすることで、将来的に他のブラウザがMediaRecorderのサポートを開始すれば、そのブラウザ上でもアプリケーションを簡単に動作させることができるようになります。

navigator.getUserMedia = ( navigator.getUserMedia ||
                       navigator.webkitGetUserMedia ||
                       navigator.mozGetUserMedia ||
                       navigator.msGetUserMedia);

次に、録音開始と終了ボタンと、生成されたオーディオプレーヤーを含む<article>要素の、変数を宣言します。

var record = document.querySelector('.record');
var stop = document.querySelector('.stop');
var soundClips = document.querySelector('.sound-clips');

セクションの最後に、gUMの基本的なセットアップを行います。

if (navigator.getUserMedia) {
   console.log('getUserMedia supported.');
   navigator.getUserMedia (
      // constraints - only audio needed for this app
      {
         audio: true
      },

      // Success callback
      function(stream) {


      },

      // Error callback
      function(err) {
         console.log('The following gUM error occured: ' + err);
      }
   );
} else {
   console.log('getUserMedia not supported on your browser!');
}

アプリケーションを実行する前に、gUMがサポートされているかどうかをチェックし、実行のコードはネスト内に記述します。次に、getUserMedia()を呼び出し、その内部で定義を行います。

  • 制約:音声のみキャプチャ。MediaRecorderでは、どちらにせよ、現状は音声のサポートのみです。
  • 成功時のコールバック:gUMの呼び出しが完了した場合に実行されます。
  • エラー/失敗時のコールバック:gUMの呼び出しが、なんらかの理由で失敗した場合に実行されます。

:以下のコードはすべてコールバック成功時のgUM内に設定されています。

メディアストリームをキャプチャする

一度、gUMがメディアストリームを取得できたら、MediaRecorder()によってMedia Recorderのインスタンスが生成され、直接ストリームを渡せます。これでthe MediaRecorder APIを使用するエントリポイントまで来ました。ストリームはBlobにそのままキャプチャされる準備ができ、ブラウザの初期値のエンコーディング・フォーマットになります。

var mediaRecorder = new MediaRecorder(stream);

MediaRecorderのインターフェースでは、利用可能な一連のメソッドを提供しており、メディアストリームの録音を操作できます。Web 音声レコーダでは、2つのメソッドを活用します。1つ目がMediaRecorder.start()です。レコードボタンを押すと、ストリームがBlobに録音され始めます。

record.onclick = function() {
  mediaRecorder.start();
  console.log(mediaRecorder.state);
  console.log("recorder started");
  record.style.background = "red";
  record.style.color = "black";
}

MediaRecorderで録音しているとき、MediaRecorder.stateプロパティは”recording ” の値を返してくるでしょう。

2つ目は、MediaRecorder.stop()メソッドです。ストップボタンを押して録音を停止し、アプリケーション上で使えるBlobへの格納を完了します。

stop.onclick = function() {
  mediaRecorder.stop();
  console.log(mediaRecorder.state);
  console.log("recorder stopped");
  record.style.background = "";
  record.style.color = "";
}

録音が終了すると、プロパティは”inactive “の値を返してきます。

Blobへの格納が完了し、利用可能となるケースとしては、他に以下のものが挙げられます。

  • メディアストリームが終了した場合(例えばトラックを取得している時や、録音している曲が終了した場合)に、Blobへの格納が完了します。
  • MediaRecorder.requestData()メソッドを呼び出した場合、Blobへの格納が完了しますが、新しいBlobには、そのまま録音され続けます。
  • タイムスライスプロパティが含まれていれば、start()メソッドを呼び出した場合、例えばstart(10000)など、それぞれ1000分の1秒ごとに新しいBlobへの格納が完了します(新たに録音がスタートします)。

blobを取得し利用する

上記で述べたようにblobへの格納が完了し、利用可能になると、dataavailable イベントが発生し、mediaRecorder.ondataavailableハンドラによって扱えるようになります。

mediaRecorder.ondataavailable = function(e) {
  console.log("data available");

  var clipName = prompt('Enter a name for your sound clip');

  var clipContainer = document.createElement('article');
  var clipLabel = document.createElement('p');
  var audio = document.createElement('audio');
  var deleteButton = document.createElement('button');

  clipContainer.classList.add('clip');
  audio.setAttribute('controls', '');
  deleteButton.innerHTML = "Delete";
  clipLabel.innerHTML = clipName;

  clipContainer.appendChild(audio);
  clipContainer.appendChild(clipLabel);
  clipContainer.appendChild(deleteButton);
  soundClips.appendChild(clipContainer);

  var audioURL = window.URL.createObjectURL(e.data);
  audio.src = audioURL;

  deleteButton.onclick = function(e) {
    evtTgt = e.target;
    evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
  }
}

上記のコードを試して、どうなるか見ていきましょう。

まず、クリップ名をつけるようユーザに対してプロンプトを表示します。

次に、下記のようなHTML ストラクチャを生成し、クリップコンテナへ書き込んだものが、<section>要素になります。

<article class="clip">
  <audio controls></audio>
  <p><em>your clip name</em></p>
  <button>Delete</button>
</article>

この後に、window.URL.createObjectURL(e.data)を使用し、イベントデータの属性を示すオブジェクトのURLを生成します。この属性は録音されたオーディオのBlobを含んでいます。それからオブジェクトのURLを表す、<audio>要素のsrc特性の値をセットすると、オーディオプレーヤーの再生ボタンが押された時に、Blobを再生します。

最後にすべてのHTMLストラクチャを消去する機能を持たせたデリートボタンにonclickハンドラをセットします。

まとめ

このように、MediaRecorderを使えばアプリケーションにおけるメディアの録音を快適にできます。実際に試してみて、ぜひ感想を教えてください。楽しみにしてますよ!