Tech Racho エンジニアの「?」を「!」に。
  • 開発

WebRTC の getUserMedia の現状

FreeBSD は多少関係ないですが,デーモン君だけは登場させます.

WebRTC

ブラウザを使って,リアルタイムコミュニケーション (Real-Time Communication) をしようと言うのが,WebRTC だと思っています.

要素技術的には,Peer-to-Peer Communication や動画・音声をブラウザ上で扱う必要が出てくるわけですが,今回はその中の,動画・音声のための APIについて,スマホでの動作を確認しました.(結果は失敗.まだ次期尚早.)

動機

社内には,Bookan と呼ばれる書籍管理ツールがあるのですが,書籍を登録するには,ISBN または Amazon の ASIN を手で入力しなければなりません.

ちょうめんどくさいです.

スマートフォンのカメラでバーコードを読み込ませられたら,めんどくさがりやの僕でも登録するかもしれません.

さらに,本を登録するためだけにアプリケーションをインストールするのも面倒です.写真をとって,アップロードするのも面倒です.できれば,Bookanの登録ページから直接カメラを起動して,バーコードを読み込ませられれば,最高です.FreeBSD 上では graphics/zbar という ports がありました.画像を食わせればバーコードを解析してくれそうです.

なので,今回は,Web ブラウザからスマホのカメラを起動し,写真をサーバに送りたいのです.

getUserMedia

ブラウザからカメラを起動するには,getUserMedia という Javascript APIがあるようです.WebRTC のためのものなのかもしれませんが,これで十分な気がします.

getUserMedia の利用方法については,色々なウェブサイトで紹介されています.WebRTC getUserMedia 簡単Webカメラ が,サンプルコードとしては,僕は気に入っています.

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

こんな感じで,ブラウザ間の差異を吸収させます.WebKit ベースや Mozillaベースのブラウザでも,navigator.getUSerMedia で扱えるようにしています.

以下のようなコードで,ブラウザ上にカメラからの動画を表示できるようです.

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL;

function getMedia() {
    navigator.getUserMedia(
        {video: true},
        function(localMediaStream) {
            var myVideo  = document.getElementById('myVideo');
            myVideo.src = window.URL.createObjectURL(localMediaStream);
            myVideo.play();
        },
        function(err) {
            alert('カメラから映像を取得することができませんでした。');
            console.log(err);
        }
    );

とりあえず,これで PC 上の Chrome では動作しました.

動作環境

次に,スマホのブラウザでどれくらい動作するのかについて調査しました.

Can I use getUserMedia/Stream API ここが良くまとまっています.

良く見ると,iOS はまだ対応していないようですね(泣

Android も標準ブラウザは対応してませんが,Chrome for Android やFirefox for Android,Opera Mobile などは対応している模様です.

社内の書籍登録の簡単にしたいだけなので,社内に大量に転がっているAndroid 端末を使えばいいやと判断.次に進むことにします.

Android 端末上での動作

先ほどのサンプルコードを Android で動作させてみると,当然のように画面と同じ方向にあるカメラが利用されてしまいました.

今回は,バーコードをとりたいので,反対側のカメラが利用したいです.

調べてみると,getSources というメソッドがあって,複数のカメラに割り当てられている ID を取得することができるようです.この ID がブラウザによって割り振られているのか,OS などのシステム関連の ID なのかまでは,現段階では調査していないです.

getSources を利用した,デバイスの選択

サンプルコードを以下に示します.

<div id="debu"></div>
    var debug = document.getElementById("debug"); 
    MediaStreamTrack.getSources(function (media_sources) {
        for (var i=0; i < media_sources.length; i++) {
            debug.innerHTML += "label: " + media_sources[i].label + ", kind: " + media_sour\
ces[i].kind + ", id: "+ media_sources[i].id + "<br>";
        }
    })

getSources を呼ぶと,callback の引数にデバイス情報が渡されるみたいです.上記の例では media_sources です.media_sources[0].id みたいにすると,カメラデバイスの ID がとれるみたいです.

また,media_sources[0].label には,"audio","video" のどちらかが入っていました.

確かに,デバイスが複数ある環境では,複数の video が表示され,デバイスID も表示されています.

今回は,Android の Chrome beta (v35) を利用してみました.が,インナーカメラまでは利用できるのですが,外側のカメラを利用しようとすると,うまくいかないです.Firefox mobile では,そもそもカメラデバイスのリストも出てこなかったです.

うーーーむ.残念….ということで,今回は失敗に終りました.半年くらい寝かせてから,再度やってみたいと思います.

サンプルコード

参考になるか分かりませんし,きれいにしてもいないですが,HTML,
Javascript 共に,掲載しておきます.

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" id="iphone-viewport" content="width=320,initial-scale=1.0,user-scalable=yes,maximum-scale=0.6667" />
  <link rel="stylesheet" href="bootstrap/css/bootstrap.css" type="text/css">
  <link rel="stylesheet" href="bootstrap/css/bootstrap-responsive.css" type="text/css">
  <script src="js/camera.js">
  </script>
</head>
<body>
  <header>
    <h1>getUserMediaサンプル</h1>
  </header>
  <button onclick="getMedia()">キャプチャ開始</button><br/>
  <video id="myVideo"></video><br/>
  <button id="picture">写真撮影</button>

  <hr>
  <p>
  写真は以下に示すよ.<br>
  <canvas id="canvas"></canvas>

  <hr>
  デバッグ領域だよ.
  <div id="debug"></div>
</body>
</html>
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL;

if (!navigator.getUserMedia){
    alert('getUserMediaがサポートされていません');
}

function getMedia() {
    var ids = new Array(); 

    MediaStreamTrack.getSources(function (media_sources) {
    debug = document.getElementById("debug"); 
    debug.innerHTML += "user agent: " + navigator.userAgent + "<br>"; 
    for (var i=0; i < media_sources.length; i++) {
        debug.innerHTML += "label: " + media_sources[i].label + ", kind: " + media_sources[i].kind + ", id: "+ media_sources[i].id + "<br>"; 
        if (media_sources[i].label == "video") {
        ids.push(media_sources[i].id); 
        }
    }
    })

    navigator.getUserMedia(
    {video: {optional: [{sourceId: ids[1]}]}}, /* debug 時には,ここの添字を 0/1 適当に変更して実行.*/
    function(localMediaStream) {
        var myVideo  = document.getElementById('myVideo');
        myVideo.src = window.URL.createObjectURL(localMediaStream);
        myVideo.play();
    },
    function(err) {
        alert('カメラから映像を取得することができませんでした。');  
        console.log(err);
    }
    );

    var pic_btn = document.getElementById("picture");
    var canvas = document.getElementById("canvas"); 
    var context = canvas.getContext("2d"); 
    pic_btn.addEventListener("click", function(){
    context.drawImage(myVideo, 0, 0, canvas.width, canvas.height); 
    }); 
}

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。