Media​Stream Recording API の使用

MediaStream Recording API を使用すると、音声や動画のストリームを簡単に記録できます。 navigator.mediaDevices.getUserMedia() と一緒に使用すると、ユーザーの入力デバイスから記録して結果を即座にウェブアプリで使用するための簡単な方法が提供されます。 音声と動画の両方を別々にまたは一緒に記録することができます。 この記事では、この API を提供する MediaRecorder インターフェースの使用方法に関する基本的なガイドを提供することを目的としています。

サンプルアプリ: ウェブディクタフォン

ウェブディクタフォンのサンプルアプリの画像 - 正弦波のサウンドの視覚化、次に録音と停止ボタン、そして再生可能な録音済みトラックの音声ジュークボックス。

MediaRecorder API の基本的な使い方を説明するために、ウェブベースのディクタフォン(dictaphone)を作りました。 それは音声の断片を録音してからそれらを再生することを可能にします。 Web Audio API を使用して、デバイスのサウンド入力を視覚化することもできます。 この記事では録音と再生の機能に集中します。

このデモがライブで実行されているのを見ることも、GitHub でソースコードを入手することもできます。

CSS のおいしいところ

このアプリでは HTML は非常に単純なので、ここでは説明しません。 言及する価値がある、もう少し興味深い CSS がいくつかありますので、それらについて以下で説明します。 CSS に興味がなく、JavaScript に直行したいのであれば、基本的なアプリの設定のセクションに進んでください。

calc() で、デバイスの高さに関係なく、インタフェースをビューポートに制限

calc 関数は、CSS でまとめられた便利で小さなユーティリティ機能の1つで、最初はあまり見かけませんが、すぐに「うわー、以前これがなかったのはなぜ? CSS2 のレイアウトが厄介だったのはなぜ?」とあなたに考えさせ始めます。 それはプロセスで異なる単位を混合して、CSS 単位の計算値を決定するための計算をすることを可能にします。

例えば、ウェブディクタフォンには、3つの主要な UI 領域が縦に積み重ねられています。 最初の2つ(ヘッダーとコントロール)の高さを固定したいと思いました。

header {
  height: 70px;
}

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

ただし、デバイスの高さに関係なく、3番目の領域(再生できる録音済みサンプルが含まれている領域)に、残っているスペースをすべて確保したいと考えました。 フレックスボックスがここでの答えかもしれませんが、それはそのような単純なレイアウトのために少しやり過ぎです。 代わりに、次のように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 に戻っても十分にサポートされています。

表示/非表示のチェックボックスのハック

これはすでにかなりよく文書化されていますが、チェックボックスのハックについて言及したいと思います。 これは、チェックボックスの <label> をクリックしてオン/オフを切り替えることができるという事実を乱用します。 ウェブディクタフォンでは、これにより情報画面が表示され、この画面は、右上隅にある疑問符アイコンをクリックすると表示/非表示になります。 まず最初に、次のように <label> を好きなようにスタイルして、他の要素の上に常に収まるように十分な z-index があり、したがってフォーカス可能/クリック可能になるようにします。

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

次に、次のように実際のチェックボックスを非表示にします。 これは、UI を混乱させたくないためです。

input[type=checkbox] {
   position: absolute;
   top: -100px;
}

次に、(<aside> 要素で囲まれた)情報画面に希望のスタイルを設定し、レイアウトフローに表示されずメイン UI に影響しないように固定の位置を指定し、デフォルトで収まって欲しい位置に変換し、スムーズな表示/非表示のための遷移を与えます。

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() を使用します。 その後、MediaRecorder API を使用してストリームを記録し、記録された各スニペットを生成された <audio> 要素のソースに出力して、再生できるようにします。

次のように録音ボタンと停止ボタン、および生成された音声プレーヤーを含む <article> の変数をいくつか宣言します。

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

このセクションの最後に、次のように基本的な getUserMedia 構造体を設定します。

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

      // Success callback
      .then(function(stream) {
 
        
      })

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

他のことを実行する前に、すべてが getUserMedia がサポートされているかどうかをチェックするテストに包まれています。 次に、getUserMedia() を呼び出し、その内部で次のように定義します。

  • constraints: 音声だけがディクタフォンにキャプチャされます。
  • Success callback: このコードは、getUserMedia の呼び出しが正常に完了した後に実行されます。
  • Error callback: 何らかの理由で getUserMedia の呼び出しが失敗した場合、このコードが実行されます。

: 以下のコードはすべて getUserMedia の Success callback 内にあります。

メディアストリームのキャプチャ

getUserMedia がメディアストリームを正常に作成したら、MediaRecorder() コンストラクタを使用して新しい Media Recorder インスタンスを作成し、それに直接ストリーム(stream)を渡します。 これが MediaRecorder API を使用するためのエントリポイントです。 これで、ストリームをブラウザーのデフォルトのエンコード形式で Blob にキャプチャする準備ができました。

var mediaRecorder = new MediaRecorder(stream);

MediaRecorder インターフェイスには、メディアストリームの記録を制御できる一連のメソッドがあります。 ウェブディクタフォンでは、その内の2つを利用して、いくつかのイベントをリスンしています。 まず、録音ボタンを押すと MediaRecorder.start() を使用してストリームの録音を開始します。

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" の値を返します。

録音が進むにつれて、音声データを収集する必要があります。 mediaRecorder.ondataavailable を使用してこれを行うためのイベントハンドラを登録します。

var chunks = [];

mediaRecorder.ondataavailable = function(e) {
  chunks.push(e.data);
}

ブラウザーは必要に応じて dataavailable イベントを発生させますが、この間隔を制御するために start() メソッドを呼び出すときにタイムスライス(例えば start(10000) )を含めることも、必要なときに MediaRecorder.requestData() を呼び出してイベントを発生させることもできます。

最後に、停止ボタンが押されたときに MediaRecorder.stop() メソッドを使用して録音を停止し、アプリの他の場所で使用できるように Blob を完成させます。

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

メディアストリームが終了すると(例えば、曲のトラックを入手してトラックが終了した場合や、ユーザーがマイクの共有を停止した場合)、録音も自然に停止することがあることに注意してください。

blob を入手して使う

録音が停止すると、state プロパティは "inactive" の値を返し、stop イベントが発生します。 mediaRecorder.onstop を使用してこれのイベントハンドラを登録し、受け取ったすべてのチャンク(chunks)から blob を確定します。

mediaRecorder.onstop = function(e) {
  console.log("recorder stopped");

  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 blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
  chunks = [];
  var audioURL = window.URL.createObjectURL(blob);
  audio.src = audioURL;

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

上記のコードを見て、何が起こっているのかを見てみましょう。

まず、ユーザーにクリップに名前を付けるように求めるプロンプトを表示します。

次に、次のような HTML 構造を作成し、それをクリップコンテナに挿入します。 これは <article> 要素です。

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

その後、録音した音声チャンクから結合された Blob を作成し、それを指すオブジェクト URL を window.URL.createObjectURL(blob) を使用して作成します。 次に、<audio> 要素の src 属性の値をオブジェクト URL に設定して、音声プレーヤーの再生ボタンが押されたときに Blob を再生するようにします。

最後に、削除ボタンに onclick ハンドラを設定して、クリップの HTML 構造全体を削除する関数にします。

仕様

仕様 状態 コメント
MediaStream Recording 草案 初期定義

ブラウザー実装状況

MediaRecorder

Update compatibility data on GitHub
デスクトップモバイル
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung Internet
MediaRecorderChrome 完全対応 47Edge 未対応 なしFirefox 完全対応 25
補足
完全対応 25
補足
補足 Prior to Firefox 58, using MediaStream.addTrack() on a stream obtained using getUserMedia(), then attempting to record the resulting stream would result in only recording the original stream without the added tracks (severe bug).
IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 47Chrome Android 完全対応 47Firefox Android 完全対応 25
補足
完全対応 25
補足
補足 Prior to Firefox 58, using MediaStream.addTrack() on a stream obtained using getUserMedia(), then attempting to record the resulting stream would result in only recording the original stream without the added tracks (severe bug).
Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
MediaRecorder() constructorChrome 完全対応 47Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 47Chrome Android 完全対応 47Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
audioBitsPerSecond
実験的
Chrome 完全対応 49Edge 未対応 なしFirefox ? IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android ? Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
error eventChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android ?
ignoreMutedMedia
実験的
Chrome 未対応 49 — 57Edge 未対応 なしFirefox ? IE ? Opera 完全対応 36Safari ? WebView Android 未対応 49 — 57Chrome Android 未対応 49 — 57Firefox Android ? Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
isTypeSupportedChrome 完全対応 47Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 47Chrome Android 完全対応 47Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
mimeTypeChrome 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Chrome Android 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 未対応 なし
ondataavailableChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onerrorChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onpauseChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 65IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 65Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onresumeChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 65IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 65Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onstartChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onstopChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
onwarning
非推奨
Chrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android ?
pauseChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
requestDataChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
resumeChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
startChrome 完全対応 47Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 47Chrome Android 完全対応 47Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0
stateChrome 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Chrome Android 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 未対応 なし
stopChrome 完全対応 49Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 あり
streamChrome 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Edge 未対応 なしFirefox 完全対応 25IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49
完全対応 49
未対応 47 — 49
補足
補足 Prior to Chrome 49, only video is supported, not audio.
Firefox Android 完全対応 25Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 未対応 なし
videoBitsPerSecond
実験的
Chrome 完全対応 49Edge 未対応 なしFirefox ? IE ? Opera 完全対応 36Safari ? WebView Android 完全対応 49Chrome Android 完全対応 49Firefox Android ? Opera Android 完全対応 36Safari iOS ? Samsung Internet Android 完全対応 5.0

凡例

完全対応  
完全対応
未対応  
未対応
実装状況不明  
実装状況不明
実験的。動作が変更される可能性があります。
実験的。動作が変更される可能性があります。
非推奨。新しいウェブサイトでは使用しないでください。
非推奨。新しいウェブサイトでは使用しないでください。
実装ノートを参照してください。
実装ノートを参照してください。

関連情報