【お詫びと訂正】iOS11でついにSafariからカメラにアクセスできるようになりました。(コピペで動くサンプルコード付き)

お詫びと訂正:(2017/06/08 23:32)
本記事内に掲載しているスクリーンショットが、
Apple.Incの開発者利用規約(APPLE BETA SOFTWARE PROGRAM AGREEMENT APPLE INC.)に抵触しているというご指摘をいただきました。
Apple.Incの開発者利用規約から、掲載内容を不適切と判断し、該当箇所を削除させていただきました。

Apple.Inc及び読者の皆様に深くお詫び申し上げます。

追記:(2019/02/17)
Navigator.getUserMediaが非推奨となったため、MediaDevices.getUserMediaを使うように修正しました。

Navigator.getUserMedia - Web API | MDN
MediaDevices.getUserMedia() - Web API | MDN

ざっくり1行でまとめると

  • iOS11のSafariがカメラにアクセスできるようになったから試しました。^ ^

f:id:kimizuka:20170608231415p:plain

はじめに

こんにちは。フロントエンジニアの @ki_230 です。
WWDC、盛り上がりましたね。寝不足の方も多いのではないでしょうか。

iMac Pro、iPad Pro 10.5インチモデル、HomePodとハードウェア系の発表もてんこ盛りでしたが、
個人的には、マークアップを担当することが多いのでiOS11のSafariの性能がとても気になっていました。

早速、手元の検証機をiOS11にしてSafariの設定画面を確認してみると。。。


f:id:kimizuka:20170608231415p:plain

むむむ。

「カメラとマイクのアクセス」という項目が!



Safari 11.0の新機能を確認してみると。。。

New in Safari 11.0 – Camera and microphone access.
 Added support for the Media Capture API.
 Websites can access camera and microphone streams from a user's device (user permission is required.)

https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Safari_11_0/Safari_11_0.html より引用


むむむむむ。

どうやらカメラとマイクにアクセスできるようになった模様です!

個人的には特にカメラがうれしい。
FileAPIで撮影した写真にアクセスしたりするのではなく、
カメラにリアルタイムにアクセスできるとなると夢が広がりますね。
なので、今回は早速カメラにアクセスしてサイト上でプレビューするコードを書いてみましょう。
※ 簡単に試すためにマイクにはアクセスしていません。

ちなみに、httpsの環境でないと動作しないので注意が必要です。


ソースコード

ちなみに今回のコード(と、試行錯誤した様子のコミットログ)は こちら にアップされています。

github.com


それでは順に解説していきましょう。

1. getUserMediaでstreamを取得してvideoタグでプレビューする

まずは単純にカメラにアクセスしてブラウザでリアルタイムにプレビューすることを目指します。

HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Video</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" />
  <link rel="stylesheet" href="index.css" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
  <video id="video" autoplay playsinline></video>
  <script src="index.js"></script>
</body>
</html>
JavaScript
const medias = {
  audio : false,
  video : true
};
const video = document.getElementById("video");
const promise = navigator.mediaDevices.getUserMedia(medias);

promise.then(successCallback)
       .then(errorCallback);

function successCallback(stream) {
  video.srcObject = stream;
};

function errorCallback(err) {
  alert(err);
};
CSS
body {
  margin: 0;
  background: #000;
}

#video {
  display: block;
  width: 100%;
}


なにも考えなければ、これでOKです。

サンプルURLとQRコード

https://goo.gl/ijYwQs
f:id:kimizuka:20170606161103p:plain

ちなみにgetUserMediaのmediasのvideoにtrueを渡すとフロントカメラを取得するようです。
もしもリアカメラを取得したい場合は、

const medias = {audio : false, video : true},

の部分を、

const medias = {audio : false, video : {
    facingMode : {
      exact : "environment"
    }
  }},

に変更すればリアカメラにアクセスできます。

ただし、リアカメラのストリームをvideoタグにいれるとポートフォリオで見たときに、何故か天地が逆転してしまったので、CSSでvideoタグをひっくり返しておきました。

追記:(2019/02/17)
iOSのバグだったようでいつのまにか反転しなくなってました。

Navigator.getUserMedia - Web API | MDN
MediaDevices.getUserMedia() - Web API | MDN

サンプルURLとQRコード

https://goo.gl/rQLDt9
f:id:kimizuka:20170606161304p:plain

f:id:kimizuka:20170608231415p:plain

リアルタイムプレビューできましたね。^ ^
(もっと良い動画が取れると良かったんですが。。。)

フロントカメラを起動すると自分の顔ばかりが映っておもしろくないので、
これからのサンプルはリアカメラを起動していこうと思います。

2. getUserMediaでstreamを取得してvideoタグでプレビューし、CanvasにdrawImageする

次にカメラのリアルタイムの映像をCanvasに書き出し続けてみます。
videoタグをdisplay: noneにすると動かなかったので、Canvasの上にワイプのように表示してみました。

HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Canvas</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" />
  <link rel="stylesheet" href="index.css" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
  <canvas id="canvas"></canvas>
  <div id="videobox">
    <video id="video" autoplay playsinline></video>
  </div>
  <script src="index.js"></script>
</body>
</html>
JavaScript
const medias = {
  audio: false,
  video: {
    facingMode: {
      exact: "environment"
    }
  }
};
const video = document.getElementById("video");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const promise = navigator.mediaDevices.getUserMedia(medias);

promise.then(successCallback)
       .catch(errorCallback);

function successCallback(stream) {
  video.srcObject = stream;
  requestAnimationFrame(draw);
}

function errorCallback(err) {
  console.log(err);
  alert(err);
}

function draw() {
  canvas.width  = window.innerWidth;
  canvas.height = window.innerHeight;
  ctx.drawImage(video, 0, 0);

  requestAnimationFrame(draw);
}
CSS
body {
  margin: 0;
  background: #000;
}

#videobox {
  position: absolute;
  top: 10px; left: 10px;
  transform-origin: left top;
  transform: scale(.1);
}

#video {
  display: block;
  transform: rotateZ(180deg); /* なぜか天地反転するので天地反転し返す */
}

#canvas {
  display: block;
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}


サンプルURLとQRコード

https://goo.gl/AhKN6t
f:id:kimizuka:20170606162607p:plain

f:id:kimizuka:20170608231415p:plain

(見た目上はわからないですが)Canvasになりましたね。^ ^

3. getUserMediaでstreamを取得してvideoタグでプレビューし、CanvasにdrawImageしつつ、モノクロにする

折角Canvasにしたのでピクセルデータにアクセスして色を変更してみました。

HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Monochrome</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" />
  <link rel="stylesheet" href="index.css" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
  <canvas id="canvas"></canvas>
  <div id="videobox">
    <video id="video" autoplay playsinline></video>
  </div>
  <script src="index.js"></script>
</body>
</html>
JavaScript
const medias = {
  audio: false,
  video: {
    facingMode: {
      exact: "environment"
    }
  }
};
const video  = document.getElementById("video");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const promise = navigator.mediaDevices.getUserMedia(medias);

let imgData;
let data
let ave;

promise.then(successCallback)
       .catch(errorCallback);

function successCallback(stream) {
  video.srcObject = stream;
  requestAnimationFrame(draw);
};

function errorCallback(err) {
  alert(err);
};

function draw() {
  canvas.width  = window.innerWidth;
  canvas.height = window.innerHeight;
  ctx.drawImage(video, 0, 0);

  imgData = ctx.getImageData(0, 0, canvas.width,  canvas.height);
    data = imgData.data;

    for (let i = 0; i < data.length; i += 4) {
      ave = (data[i + 0] + data[i + 1] + data[i + 2]) / 3;

      data[i + 0] = 
      data[i + 1] = 
      data[i + 2] = (ave > 255 / 2) ? 255 : (ave > 255 / 4) ? 127 : 0;
      data[i + 3] = 255;
    }

  ctx.putImageData(imgData, 0, 0);
  requestAnimationFrame(draw);
}

CSS

body {
  margin: 0;
  background: #000;
}

#videobox {
  position: absolute;
  top: 10px; left: 10px;
  transform-origin: left top;
  transform: scale(.1);
}

#video {
  display: block;
  transform: rotateZ(180deg); /* なぜか天地反転するので天地反転し返す */
}

#canvas {
  display: block;
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}


サンプルURLとQRコード

https://goo.gl/huZORf
f:id:kimizuka:20170606163232p:plain

f:id:kimizuka:20170608231415p:plain

モノクロになりましたね。^ ^

おわりに

今回はとにかく手っ取り早くカメラのリアルタイムプレビューを試したかったので非常に簡単なサンプルをつくるにとどまりましたが、
Canvasに取り込みさえすればこっちのものなので、簡単なARアプリぐらいはつくれるかもしれません。
iOS11のリリースまではまだ時間がありそうなので、引き続き追っていこうと思います!
(後回しにしたマイクアクセスも追っていこうと思います)

カヤックでは一緒に新しいことにチャレンジしてくれるエンジニアも募集しているので、一緒に追ってくれる方絶賛お待ちしています!
www.kayac.com
www.kayac.com

svg / canvas出力ができるbodymovinの紹介

やっはろー(= ゚ω ゚)ノ 今年の3月、カヤックに中途入社した、 カヤックエンジニアの中で1番プログラムできないおじさんの町田(@machida-yousuke)と申します。 今回は、「映像業界?フロントエンド業界?が注目しているオープンソースライブラリ bodymovin」について紹介します。 ちなみに私は、元某インターネット広告代理店の映像クリエイター兼フリーランスのモーショングラフィッカーでした。

※この記事はAfterEffects(以下「AE」と称する)の基本的な操作、用語を理解している前提で進めていきます。 ※bodymovinのバーションは V 4.6.10、AEのバーションはadobe AfterEffects cc 2017で進めていきます。 (V 4.6.10から使用言語によっておきるバグの修正がされています。) ※使用PCはMacです。

bodymovinpromo

bodymovinとは

AEで制作したアニメーションデータをJSON形式で吐き出してくれるエクステンションでもあり、JSONファイルを元に、web上にsvg / canvas形式で出力することができるライブラリでもあります。 また、bodymovinで吐き出されたJSONファイルは、LottieというAirbnbがリリースしたアニメーションライブラリに使用することができます。 Airbnb Lottie

※bodymovinの読み方は「ボディーモビン」なのか「ボディームビン」なのかは不明ですが、 私は「ボディーモビン」と勝手に読んでます。(※多分、正しい読み方は「ボディームビン」です。)

bodymovinの導入

・Option 1

bodymovin をダウンロードします。 AEの拡張ファイルが'/build/extension'の中に、bodymovin.zxpがあります。 このzxpファイルを開くために ZXP installer をインストールすることで、 bodymovin.zxpをインストールできるようになります。

・Option 2

次にregistry keyを編集し、PlayerDebugModeを起動しろと書いてありますが、 ZXP installer以外の方法でインストールした場合にのみやる必要があるみたいです。 今回は、ZXP installer使用したので、この手順は必要ありません。

詳しいことはここら辺を見ればいいとおもいます。

・Option 3

AEを起動して環境設定→一般設定→スクリプトによるファイルへの書き込みとネットワークへのアクセスを許可にチェックを入れてください。 ついでに初期設定の空間補間法をリニアにするにもチェックを入れましょう。空間補間法がベジェからリニアに変わります(これ大事)。 あとは、キャッシュや自動保存の設定を自分好みに設定してください。

これで準備は終わりです。bodymovinを起動させましょう。 ウィンドウ→拡張機能→bodymovinで起動します。

AEでアニメーションを作ろう

今回は、kayacロゴをつかって簡単なモーションを元に、アニメーションのJSON書き出しと、 どこまでAEの機能に対応しているのか説明します。
と、思いましたが、実際作ったら特殊なことしない、アニメーションができてしまった…

今回作ったのがこちら…

はい。
シェイプのパスをいじって、キーフレームつけただけですね。
作りながらもっと実用的なものの方がいいんじゃないか?とか思いながら作ってました。

映像自体は60fps 解像度500px ✕ 500でpx制作しています。
容量は JSONファイル 74KB とbodymovin(light var)の 138KB で 212KB です(しかもsvgなので解像度は関係ない)
もしgifにしたら(カラー4色、ループ設定) 258KB
もしmp4(H.264)にしたら 807KB

コードについて

jsに書くコードは基本的にこれだけで動きます。

var animation = bodymovin.loadAnimation({
  container: document.getElementById(''), //dom要素の選択
  renderer: 'svg',                        //レンダリング設定
  loop: true,                             //ループ設定
  autoplay: true,                         //オート設定
  path: 'data.JSON'                       //JSONのパス
})

再生のスピードなどのeventを使いたい場合は、addEventListenerで紐付けることができます。

var logo_DOM = document.getElementById('svg');
var animData = {
    container: logo_DOM,
  renderer: 'svg',
  loop: true,
  autoplay: true,
  animationData: animationData
};
var kayac_logo = bodymovin.loadAnimation(animData);

//クリックしたらdom要素ごと破壊します
logo_DOM.addEventListener('click', function(){
    kayac_logo.destroy()
    console.log("滅びのバーストストリーム")
});

//ホバーしたらスローにします
logo_DOM.addEventListener('mouseover', function(){
    kayac_logo.setSpeed(0.01)
    console.log("ザ・ワールド(時は止まってない)")
    logo_DOM.addEventListener('mouseout', function(){
        kayac_logo.setSpeed(1)
        console.log("そして時は動き出す")
    });
});

レンダリング形式をsvgにするとcssのfillで色を変更することができます。
JSONファイルを直接いじって大きさなど一応変更できますが、fpsは変更しても意味がないのでAEのコンポ設定を変えて書き出ししましょう。
画像を使用した場合、書き出し時にimagesフォルダができるので、画像置き換えることもできます。
出力された画像データは、勝手にリネームされます。(img_0.pngとかに)

bodymovinが対応してる機能

まず、エフェクトはほとんど対応していません!将来対応予定らしい。

エクスプレッションは生のjsを書けば、対応します。
wiggle(100,100)とか書いてもエラーがでます。
エクプレッション使えないのかよって感じですが、書き出し時にキーフレーム変換をすれば解決します。

その他いろいろ対応機能はgithubのwikiにかいてあります。
https://github.com/bodymovin/bodymovin/wiki

ブラウザで見てみよう

ブラウザで見るには、ローカルサーバーを立てる必要があります。
もしローカルサーバーを立てるのが面倒な場合は、bodymovinの書き出し設定の、
DemoExports an html for local preview
にチェックをいれると、JSONとbodymovin.jsとJSONとhtmlが合体したものが生成されます。

また、lottieには LottieFiles というコミュニティがあり、
他人のAfterEffectsProjectファイルと、JSONファイルをダウンロードすることができます。
しかも、JSONファイルをドラッグ&ドロップしてプレビューすることもできます!!すばらっ
※画像を使用したアニメーションの場合、LottieFilesは描画できません。

SVG形式で出力した場合 作成したアニメーションによって、ブレンドモードやマスクなどのCSSが必要になるので注意してください。
ブレンドモードを多用すると重くなります。
画像を使うこともできますが、canvas形式のほうが軽い場合があります。

canvas形式で出力した場合 canvas形式で出力した場合、SVGに対応していたものが効かない場合があります。
トラックマットは対応していないので、パスで切り抜きましょう。
既存のcanvasを使うことができます。

html形式で出力した場合 svgとあまり変わりありませんが、3dレイヤーを使用することができます。

対応表はここに書いてあります。
https://github.com/bodymovin/bodymovin/wiki/Features

bodymovinと相性のいいと思うAEプラグインとフリースクリプト

NEWTON
物理シミュレーションを実現する
2D物理エンジンプラグイン
物理演算結果を1フレームごとシェイプレイヤーにキーをうってるだけなので、bodymovinでJSONを書き出せるはず。

LayerGenerators
プラグインをもっていないので、未確認ですが、チュートリアルを見たところ、
平面レイヤーにパスをつけてるだけだったので使えると思います。

Pastiche
プラグインをもっていないので、未確認ですが,ソースレイヤーに指定したレイヤーをその他のレイヤーで埋めてコラージュして、アニメーションさせることができます

Duik
IKやアニメーションを簡単につけることができるフリースクリプト
これがあれば、2Dキャラクターアニメーションは簡単につくることができます。
IKを設定すると動きの制御は全てエクスプレッションなので、書き出す時にキーフレームに変換する必要があります。
Duikを使えば簡単にキャラの髪の毛を揺らしたりできるのですが、
bodymovinがパペットピンツール未対応なので、メッシュでキャラの髪の毛を動かすとかはまだ先の話。

その他After Effectsをサポートしているライブラリ

Marcus Eckert
Squall
Marcus Eckertさん制作のライブラリ、iOS Android向け

Keyframes
facebook制作のライブラリ、こちらもiOS Android向け

まとめ

bodymovinはAirbnbのライブラリで使われてるので、信用できるライブラリだと思います。
実際に案件での使用ができていませんが、この先、使用する可能性は大いにあると個人的に思っています。

bodymovinの詳しい例が知りたい場合
http://codepen.io/collection/nVYWZR/
で見ることができます。

AnimateCCとの住み分けは議論の余地があるかと思いますが個人的には
画像を扱ったり、インタラクティブなことをするならwebglで書き出せるAnimateCC
ベクター(svg)のみならbodymovinだと思います。
画像をイラレの画像トレースでパス化してAEで動かしたら、どれだけ重くなるのか実験してみたいですね。

今後、bodymovinはAEのエフェクトに対応するらしいので、
bodymovinがwebgl,GLSL書き出しできるまでそう遠くないと思います。

フロント向けなのか、デザイナー向け、なのかわからない記事になってしまいましたが、
昨今、クリエイターのスキルの境界は曖昧になってきており、
プログラム技術力もあって、デザインもできるみたいな、マルチタスククリエイターが多くなっています。
フロントエンジニアもデザイナーも新しいことを始めるのもいいかもしれないですね。
カヤックでは一緒に新しいことにチャレンジしてくれるエンジニアも募集しています!(https://www.kayac.com/recruit/fresh )

関連記事 techblog.kayac.com

それでは、みなさん arrivederci!