NaClの佐田です。

今回はブラウザで撮影したカメラの画像をサーバに送信するまでの記事です。 本記事を読まれた方が、カメラの簡単な操作をRailsアプリへ追加できるように紹介します。

本記事をご覧になっている方は、SNSを利用しているでしょうか。 私は携帯で撮影した画像をSNSにアップロードすることがあるので、仕事でカメラを撮影する機能をアプリに追加した時に面白いと感じたので記事にすることにしました。

確認した時の環境は「Chrome バージョン 56.0.2924.87 (64-bit)」です。 画像を撮影するカメラは、タブレット型パソコンにあるカメラ機能を使用しました。

サンプルをGitHubのsada/camera_appリポジトリに置いたので、そのコードを基に紹介していきます。

ブラウザからカメラを使用する許可

ブラウザからカメラを使用する方法は、WebRTC落穂拾い:初心者がつまずきやすいポイントをフォローの記事を参考にしました。 以下のようにカメラを使用するためのJavaScriptを書きました。

var stream;
var video;
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then(function(stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

最初に、JavaScriptでカメラの使用許可を得るため、MediaDevices.getUserMedia()を呼び出します。 パソコンと接続しているカメラをブラウザで使用するには、使用許可がないと操作できません。 MediaDevices.getUserMedia()を呼び出すと、カメラの使用許可をブラウザが求めてきます。 URL先の記事では、chrome://flagsでフラグ設定をする必要があるとありましたが、確認した時のバージョンのChromeではフラグ設定をしなくてもカメラが動作しました。

また、カメラの機能自体が有効になっていないと使用許可を求めてきません。 私はVirtualBoxの仮想マシン上の環境で試しましたが、VirtualBoxの[メニュー]の[デバイス]の[Webカメラ]にある[Front Camera]と[Rear Camera]のどちらかが選択されて有効になっていないとカメラの機能が有効になりませんでした。

もちろんカメラの使用許可を求められた時に拒否するとカメラの映像を取得できません。

引数に指定しているvideoやaudioは、ユーザのカメラやマイクへの使用許可を求めるためのオプションの指定です。 MediaDevices.getUserMedia()の戻り値としてPromiseが返されます。 もしカメラの使用が許可された場合は、then関数内でカメラの映像をvideoタグに読み込ませて再生されます。 もしカメラの使用が拒否された場合は、catch関数内の処理が実行されてブラウザのコンソール上にメッセージが表示されます。

カメラの映像を画像データに変換

カメラの映像を表示することができました。 カメラの映像を画像データに変換するには、canvasにカメラの映像を渡してからHTMLCanvasElement.toBlob()を呼び出してBlobオブジェクトにします。

var stream;
var video;
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then(function (stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
  setTimeout(function() {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var w = video.offsetWidth;
    var h = video.offsetHeight;
    canvas.setAttribute('width', w);
    canvas.setAttribute('height', h);
    ctx.drawImage(video, 0, 0, w, h);
    canvas.toBlob(function(blob) {
      var img = document.getElementById('image');
      img.src = window.URL.createObjectURL(blob);
    }, 'image/jpeg', 0.95);
    stream.getTracks()[0].stop();
  }, 3000);
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

setTimeoutを使用して、3秒後にHTMLCanvasElement.toBlob()でカメラの映像をJPEG形式に変換してからimgタグに渡しています。 setTimeoutでカメラの映像の変換を遅らせるのは、そうしないと、カメラの映像が表示される前の黒い画像で画像データとして表示されてしまうからです。

上記までのJavaScriptでは、カメラのサイズを指定していないので、デフォルトのサイズで表示されています。 カメラのサイズを変更するには、以下のようなオプションを指定します。

var constraints = {
  video: {
    mandatory: {
      maxWidth: 320,
      maxHeight: 240
    }
  },
  audio: false
};
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
  // 省略
});

上記のオプションの指定によって、カメラのデフォルトのサイズが640x480で表示されていたのが320x240のサイズに変更されました。 maxWidthとmaxHeightは、カメラの縦横の幅の最大を指定しています。 オプションで指定した値に近い、使用できるカメラのサイズの規格に変更されています。

画像の保存

ここまでカメラの映像を画像データに変換して表示することを紹介しました。 次に、Railsで立ち上げたサーバに送信してデータベースに画像データを保存します。

先ほどはimgタグのsrc属性に渡していたBlobオブジェクトをFormDataに渡してRailsサーバに送信します。

navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
  stream = stream;
  video = document.getElementById('video');
  video.src = window.URL.createObjectURL(stream);
  video.play();
  setTimeout(function() {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var w = video.offsetWidth;
    var h = video.offsetHeight;
    canvas.setAttribute('width', w);
    canvas.setAttribute('height', h);
    ctx.drawImage(video, 0, 0, w, h);
    canvas.toBlob(function(blob) {
      var img = document.getElementById('image');
      img.src = window.URL.createObjectURL(blob);

      var request = new XMLHttpRequest();
      request.open('POST', '/avatars');
      request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
      var formData = new FormData();
      formData.append('avatar[image]', blob, '<%= Time.now.strftime("%Y%m%d%H%M") %>}.jpeg');
      formData.append('avatar[uuid]', '<%= user.uuid %>');
      request.send(formData);
    }, 'image/jpeg', 0.95);
    stream.getTracks()[0].stop();
  }, 3000);
}).catch(function (error) {
  console.error('mediaDevice.getUserMedia() error:', error);
  return;
});

サンプルのRailsアプリでは、画像を保存するgemパッケージとしてpaperclipを使用しました。

外部への公開について

本記事ではbundle exec rails serverコマンドでサンプルのサーバを立ち上げることを想定しています。

もし外部に公開する場合、ChromeはMediaDevices.getUserMedia()を使用しているならばhttpsに設定しないといけません。 Deprecating Powerful Features on Insecure Originsにて書かれています。

まとめ

MediaDevices.getUserMedia()を使用したカメラの画像を撮影することについて書きました。

参考情報

WebRTC落穂拾い:初心者がつまずきやすいポイントをフォロー

camera_new.html

MediaDevices.getUserMedia()

HTMLCanvasElement.toBlob()

Resolution Constraints in Web Real Time Communications draft-alvestrand-constraints-resolution-00

paperclip

Deprecating Powerful Features on Insecure Origins