Capturing Audio & Video in HTML5

HTML5 Rocks

This article discusses APIs that are not yet fully standardized and still in flux. Be cautious when using experimental APIs in your own projects.

はじめに

長い間、Audio/Video capture はウェブ開発にとって「到達困難な目標」でした。何年にもわたり、ジョブを実行するにはブラウザ プラグイン(Flash または Silverlight)を使用する必要がありました。勘弁してもらいたい!

これを救うのが HTML5 です。目立たないかもしれませんが、HTML5 の登場によってデバイス ハードウェアへのアクセスが急増しました。Geolocation(GPS)、Orientation API(加速度メーター)、WebGL(GPU)、Web Audio API(自動車ハードウェア)がわかりやすい例です。これらの機能は驚くほど強力で、システムの基盤になるハードウェア機能の上に置かれる高水準の JavaScript API が公開されています。

このチュートリアルでは、新しい API である navigator.getUserMedia() を紹介します。この API を使用すると、ウェブ アプリケーションでユーザーのカメラとマイクにアクセスできます。

getUserMedia() への道

歴史をご存じない方のために言っておくと、getUserMedia() API には興味深い物語があります。

この数年の間に、「Media Capture API」のいくつかの変形が生まれました。多くの人が、ウェブ上のネイティブ デバイスにアクセスできる機能の必要性を認識していました。それは、さまざまな考えを合わせて 1 つの新しい仕様を作る動きになりました。そこで事態が混乱したため、W3C は最終的に作業グループを設けました。その唯一の目的は、混乱を収拾することです。Device API ポリシー(DAP)作業グループが、大量の提案の統合と標準化を行ってきました。

2011 年に何が行われたかを要約してみます。

ラウンド 1: HTML Media Capture

HTML Media Capture は、ウェブ上のメディア キャプチャ標準化に関する DAP の最初の正式認定でした。この API は <input type="file"> をオーバーロードし、accept パラメータに新しい値を追加することで機能します。

ユーザーがウェブカメラで自分のスナップショットを撮影できるようにする場合は、capture=camera によって可能です。

<input type="file" accept="image/*;capture=camera">

動画または音声の記録も同様です。

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

これはいいですね。特に、ファイル入力を使用するところがいいと思います。セマンティックの面でもかなり意味があります。この特定の「API」に足りない点は、リアルタイムの効果です(たとえば、ライブ ウェブカム データを <canvas> に表示し、WebGL フィルタを適用する)。HTML Media Capture では、時間に合ったメディア ファイルの記録またはスナップショットの取得のみが可能です。

サポート:

HTML Media Capture は、上記のモバイル ブラウザのいずれかを対象にしているのでない限り、使用を避けることをおすすめします。ベンダーは getUserMedia() に移行しつつあります。長期的に見て、他の方が HTML Media Capture を実装する見込みはないと考えられます。

ラウンド 2: device 要素

多くのユーザーは、HTML Media Capture は制限が多すぎると感じました。そこで、あらゆる種類の(将来の)デバイスをサポートする新しい仕様が登場しました。予想どおり、その設計では新しい要素が要求されていました。それは getUserMedia() の後継となる <device> という要素です。

Opera は、<device> に基づくビデオ キャプチャの初期の実装を作成した最初のブラウザの 1 つです。そのすぐ後(正確には同じ日)に、WhatWG は <device> タグを捨て去り、別の有望なタグを優先することを決定しました。今度は navigator.getUserMedia() と呼ばれる JavaScript API です。1 週間後、Opera は、更新された getUserMedia() 仕様に対応した新しいビルドを公開しました。その年の後半、Microsoft は新しい仕様をサポートした Lab for IE9 をリリースして仲間に加わりました。

<device> は次のようになっていたはずです。

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

サポート:

残念ながら、これまでリリースされたブラウザの中に <device> を組み込んだものはありません。これで悩まされる API が 1 つ減ったというものです。しかしながら、<device> には確かによいところが 2 つありました。1.)意味がわかるように作られていたこと、2.)音声/動画デバイス以外もサポートするように簡単に拡張できたことです。

一息つきましょう。変化が速すぎます。

ラウンド 3: WebRTC

<device> 要素は最終的に絶滅の道をたどりました。

WebRTC(Web Real Time Communications)と呼ばれる大規模な取り組みのおかげで、適切なキャプチャ API を探すペースは、最近の数か月で加速しました。仕様は W3C WebRTC 作業グループによって監督されています。現在、Google、Opera、Mozilla、その他いくつかのブラウザが実装の作業中です。

getUserMedia() は WebRTC に関係しています。WebRTC が一連の API への入り口であるためです。WebRTC はユーザーのローカル カメラ/マイク ストリームにアクセスする手段を提供します。

サポート:

WebRTC は、Chrome 18.0.1008 以降で about:flags によって有効化することができます。

いよいよ始動

navigator.getUserMedia() では、最終的にウェブカメラとマイクの入力をプラグインなしで扱えるようになりました。カメラへのアクセスは、インストールではなく呼び出しによって実現可能になりました。ブラウザに直接書き込まれます。すばらしいことではないでしょうか。

有効化

getUserMedia() API は、まだ非常に新しく、デベロッパー ビルドにこの API を組み込んでいるのは Google と Opera のみです。Chrome 18 以降では、この API は about:flags にアクセスして有効化できます。

Chrome の about:flags ページでの getUserMedia() の有効化

Opera の場合、実験的な Android 用ビルドとデスクトップ用ビルドのいずれかをダウンロードすることができます。

機能検出

機能検出は、navigator.getUserMedia が存在するかどうかの簡単なチェックです。

function hasGetUserMedia() {
  // Note: Opera builds are unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

入力デバイスへのアクセス権の取得

ウェブカメラやマイクを使用するには、アクセス許可を要求する必要があります。getUserMedia() の最初のパラメータでは、アクセスするメディアのタイプを指定します。たとえば、ウェブカメラを要求する場合、最初のパラメータは "video" になります。マイクとカメラの両方を使用するには、"video, audio" を指定します。

<video autoplay></video>

<script>
  var onFailSoHard = function(e) {
    console.log('Reeeejected!', e);
  };

  // Not showing vendor prefixes.
  navigator.getUserMedia('video, audio', function(localMediaStream) {
    var video = document.querySelector('video');
    video.src = window.URL.createObjectURL(localMediaStream);

    // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
    // See crbug.com/110938.
    video.onloadedmetadata = function(e) {
      // Ready to go. Do some stuff.
    };
  }, onFailSoHard);
</script>

先に進みましょう。何が起きているのでしょうか。メディア キャプチャは、新しい HTML5 API の連携動作の優れた例です。<audio> および <video> という他の HTML5 タグと連携して動作します。<video> 要素に src 属性を設定したり <source> 要素を指定するのではないことに注意してください。メディア ファイルへの URL に動画をフィードするのではなく、ウェブカメラを表す LocalMediaStream オブジェクトから取得した Blob URL にフィードします。

また、<video>autoplay を指示します。これを指示しないと、最初のフレームでフリーズします。controls を追加しても、期待どおりに動作します。

注: Chrome にはバグがあり、「audio」のみを渡しても無効です(crbug.com/112367)。Opera でも <audio> を動作させることはできませんでした。

Opera と Chrome のどちらも、実装している仕様のバージョンが異なります。このため、実際の使い方は少し難しくなります。

Chrome の場合:

このスニペットは Chrome 18 以降(about:flags で有効化)で動作します。

navigator.webkitGetUserMedia('audio, video', function(localMediaStream) {
  var video = document.querySelector('video');
  video.src = window.webkitURL.createObjectURL(localMediaStream);
}, onFailSoHard);

Opera の場合:

Opera デベロッパー ビルドは、更新されたバージョンの仕様に対して有効です。このスニペットは Opera で動作します。

navigator.getUserMedia({audio: true, video: true}, function(localMediaStream) {
  video.src = localMediaStream;
}, onFailSoHard);

主な相違点は次のとおりです。

  • getUserMedia() はプレフィックスなしです。
  • オブジェクトは文字列リストではなく第 1 引数として渡されます。
  • video.src は、Blob URL ではなく LocalMediaStream オブジェクトに直接設定します。Opera はいずれこれを更新して、Blob URL が必須になるようです。

共通:

ブラウザ間で動作する(ただし非常に不安定)機能が必要な場合は、以下を試してみてください。

var video = document.querySelector('video');

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = stream;
  }, onFailSoHard);
} else if (navigator.webkitGetUserMedia) {
  navigator.webkitGetUserMedia('audio, video', function(stream) {
    video.src = window.webkitURL.createObjectURL(stream);
  }, onFailSoHard);
} else {
  video.src = 'somevideo.webm'; // fallback.
}

Mike TaylorMike RobinsongUM Shield を確認してください。ブラウザ実装間の不統一をうまく「標準化」しています。

セキュリティ

将来的には、ブラウザは getUserMedia() の呼び出し時点で情報バーを破棄する可能性があります。これにより、カメラやマイクへのアクセスを許可または拒否するオプションがユーザーに与えられます。セキュリティに関しては、残念ながらめぼしい仕様はありません。現時点で、アクセス許可バーは実装されていません。

フォールバックの提供

getUserMedia() をサポートしていないユーザーについて、1 つのオプションは、API がサポートされていない場合や呼び出しが何らかの理由で失敗する場合は、既存の動画ファイルにフォールバックすることです。

// Not showing vendor prefixes or code that works cross-browser:

function fallback(e) {
  video.src = 'fallbackvideo.webm';
}

function success(stream) {
  video.src = window.URL.createObjectURL(stream);
}

if (!navigator.getUserMedia) {
  fallback();
} else {
  navigator.getUserMedia({video: true}, success, fallback);
}

基本的なデモ

スクリーンショットの取得

<canvas> API の ctx.drawImage(video, 0, 0) メソッドにより、<canvas> への <video> フレームの描画が簡単に行えるようになりました。もちろん、getUserMedia() から動画入力を受け取るようになったことで、リアルタイムの動画を使って写真撮影ブースのようなアプリケーションを簡単に作成できます。

<video autoplay></video>
<img src="">
<canvas style="display:none;"></canvas>

var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var localMediaStream = null;

function snapshot() {
  if (localMediaStream) {
    ctx.drawImage(video, 0, 0);
    // "image/webp" works in Chrome 18. In other browsers, this will fall back to image/png.
    document.querySelector('img').src = canvas.toDataURL('image/webp');
  }
}

video.addEventListener('click', snapshot, false);

// Not showing vendor prefixes or code that works cross-browser.
navigator.getUserMedia({video: true}, function(stream) {
  video.src = window.URL.createObjectURL(stream);
  localMediaStream = stream;
}, onFailSoHard);

効果の適用

CSS フィルタ

CSS フィルタは、現在のところ WebKit ナイトリー ビルドと Chrome 18 以降でサポートされています。

CSS フィルタを使用して、キャプチャしながらすばらしい効果を <video> に適用できます。

<style>
video {
  width: 307px;
  height: 250px;
  background: rgba(255,255,255,0.5);
  border: 1px solid #ccc;
}
.grayscale {
  +filter: grayscale(1);
}
.sepia {
  +filter: sepia(1);
}
.blur {
  +filter: blur(3px);
}
...
</style>

<video autoplay></video>

<script>
var idx = 0;
var filters = ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', 'hue-rotate',
               'hue-rotate2', 'hue-rotate3', 'saturate', 'invert', ''];

function changeFilter(e) {
  var el = e.target;
  el.className = '';
  var effect = filters[idx++ % filters.length]; // loop through filters.
  if (effect) {
    el.classList.add(effect);
  }
}

document.querySelector('video').addEventListener('click', changeFilter, false);
</script>

動画をクリックすると、CSS フィルタが順に切り替わります

WebGL テクスチャ

動画キャプチャの優れた使用例の 1 つは、ライブ入力を WebGL テクスチャとしてレンダリングすることです。WebGL については、私はまったく何も(すばらしいものだということ以外)知らないので、Jerome Etienne の チュートリアルデモをご覧になることをおすすめします。getUserMedia()Three.js を使用してライブ動画を WebGL にレンダリングする方法が示されています。

Web Audio API と getUserMedia の使用

このセクションでは、現在の API に対して考えられる今後の改良と機能拡張について説明します。

私の夢の 1 つは、オープンなウェブ テクノロジーだけを使ってブラウザで AutoTune を作成することです。実際、その実現はそう遠くありません。マイク入力用の getUserMedia() は既にできています。Web Audio API にリアルタイム効果のための多少の追加を行えば準備完了です。両者の統合には部品が足りません(crbug.com/112404)。ただし、統合を実現するための暫定提案が進行中です。

マイク入力を Web Audio API にパイプでつなぐ処理は、そのうち次のような形になる可能性があります。

var context = new window.webkitAudioContext();

navigator.webkitGetUserMedia({audio: true}, function(stream) {
  var microphone = context.createMediaStreamSource(stream);
  var filter = context.createBiquadFilter();

  // microphone -> filter -> destination.
  microphone.connect(filter);
  filter.connect(context.destination);
}, onFailSoHard);

Web Audio API に getUserMedia() を組み合わせる必要がある場合は、crbug.com/112404 を確認してください。

まとめ

総じて、ウェブ上のデバイス アクセスは手ごわい問題でした。多くの人たちがトライしましたが、成功した人はほとんどいません。初期の考え方のほとんどは、それ独自の環境以外で支配的になることはなく、広い範囲で採用されることもありませんでした。

本当の問題は、ウェブのセキュリティ モデルがネイティブの世界とは大きく異なることです。たとえば、誰ともわからぬ多くの人のウェブ サイトから自分のビデオ カメラにランダムにアクセスされるのはごめんこうむりたいものです。正しく処理することが難しい問題です。

PhoneGap のようなブリッジ フレームワークは境界を低くするのに役立ちましたが、基盤の問題に対する初期の一時的な解決策にすぎません。ウェブ アプリケーションをデスクトップ アプリケーションと競争できるようにするためには、ネイティブ デバイスにアクセスする必要があります。

getUserMedia() は新しいタイプのデバイスへのアクセスに関する、まさに最初の波です。ごく近い将来に引き続き追加があることを期待します。

その他のリソース

デモ

Comments

0