ブラウザでWebcamを扱えるテストツールを作った話。キャプチャも可。

# サマリ
ブラウザでアクセスするだけで、Webcamの動画ストリーミングにアクセスできるツールを作成しました。任意のタイミングでキャプチャし、PNG画像として保存することもできます。
<img width="800" alt="screenShot.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542654/f552671c-3442-e773-b9e1-29f49e3fbc64.png">

# 成果物
[こちら](https://tetunori.github.io/HTML5WebcamTester/)にアクセスするだけで使えます。[ソースコード](https://github.com/tetunori/HTML5WebcamTester)もOSSでどうぞ。

# 開発モチベーション
日頃開発をしていると、机の上の状況をPC上で画像として貼り付けたくなる事が多々あります。当然スマホで撮影することが多いのですが、PC側で扱うにはクラウドサービスを経由したり、Directな無線通信やケーブルで接続したりと転送に少し手間がかかります。
そんな時Webcamが常設されている環境であればキャプチャが出来るはずですが、気軽にキャプチャできるツールが無かったので、作成した次第です。

# 使い方
1. PCにWebcamを接続します。
2. [このツール](https://tetunori.github.io/HTML5WebcamTester/)を開いてください. Google Chromeを強く推奨します。
3. 自動的に以下のダイアログがでてくるので、カメラを使うことを許可してください。(「許可」を押します。)  
<img width="300" alt="permission.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542654/86b5a508-5c38-6139-3ea7-6a94904d9dc7.png">
4. 動画のストリーミングが始まった後に`CAPTURE`ボタンを押すと、その時点の画像データがPNGファイルとして即ダウンロードされます。

# SWのポイント
## HTML5 ビデオストリーミングの開始
10数行で実現出来る。いい世の中になりまりました。

```HTML:index.html
<video autoplay="true" id="video"></video>
```

```Javascript:index.js
// On Streaming
const startStreamingVideo = () => {
  const video = document.querySelector( "#video" );
  if( navigator.mediaDevices.getUserMedia ){
    navigator.mediaDevices.getUserMedia( { video: true } )
    .then( ( stream ) => {
        video.srcObject = stream;
    } );
  }
}
startStreamingVideo();
```

## 静止画のキャプチャ
少しトリッキーな事をしています。
`CAPUTRE`ボタンが押されたら、見えない`canvas`にその時点のデータを描画します。その後`canvas`の内容をDataURL形式に変換し`link`と紐付け、ダウンロード可能にした状態で最後に`link`を`click()`します。
このあたりは、[こちらの記事](https://qiita.com/iwaimagic/items/1d16a721b36f04e91aed)を参考にさせていただきました。

```HTML:index.html
<div id="hiddenContainer">
  <a id="hiddenLink"></a>
  <canvas id="hiddenCanvas" width="500" height="375"></canvas>
</div>
```

```Javascript:index.js
const btCapture = document.getElementById( 'btCapture' );

btCapture.addEventListener( 'click', () => {
  
  // Capture: draw to hidden canvas
  const hiddenCanvas = document.getElementById( 'hiddenCanvas' );
  const ctx = hiddenCanvas.getContext('2d');
  const WIDTH = 500;
  const HEIGHT = 375;
  ctx.drawImage( video, 0, 0, WIDTH, HEIGHT );
  
  // Download: load DataURL and convert to png
  const link = document.getElementById( 'hiddenLink' );
  link.href = hiddenCanvas.toDataURL();
  // document.getElementById('hiddenCanvas').src = hiddenCanvas.toDataURL();
  link.download = getYYYYMMDD_hhmmss( true ) + ".png";
  link.click();

});

btCapture.disabled = false;
```

## JavascriptでYYYYMMDD_hhmmss
なんか毎回こんな関数をつくっているような気がしますが、今回は下記の通りで実装。
[こちら](https://gist.github.com/froop/962669)を参考にさせていただきました。

 ```Javascript:index.js
const getYYYYMMDD_hhmmss = ( isNeedUS ) => {

  const now = new Date();
  let retVal = '';

  // YYMMDD
  retVal += now.getFullYear();
  retVal += padZero2Digit( now.getMonth() + 1 );
  retVal += padZero2Digit( now.getDate() );
  
  if( isNeedUS ){ retVal += '_'; }
  
  // hhmmss
  retVal += padZero2Digit( now.getHours() );
  retVal += padZero2Digit( now.getMinutes() );
  retVal += padZero2Digit( now.getSeconds() );

  return retVal;

}

// padding function
const padZero2Digit = ( num ) => {
  return ( num < 10 ? "0" : "" ) + num;
}
```

# 所感と考察
- Videoデータの取り扱い、楽になったものだなぁ。
- ~~Captureした後のデータサイズは決め打ちにしてしまっているのですが、videoのMAX解像度を知ることができると画像もキレイに保存できて良いなと。ただ、ちょっと調べたくらいでは出来なかったので今回は断念しています。~~ Version1.1.0で解消しました。単にWidth/Heightをこちらで指定してしまっているのが原因でした。指定がなければブラウザ側でよきに計らってサイズ設定されました。(2020/2/16追記)
- 今回の本筋とは関係ないのですが、本ツールはVSCodeのLiveServerを使って実装しました。めちゃめちゃ楽でQoD爆上がり。詳しくは[こちらの記事](https://qiita.com/kn339264/items/e6f6a69f70a96e61fc11)をどうぞ。

# 追記(20200216)
Version 1.1.0および1.2.0をリリースしました。1.2.0の機能については別途記事書きます。
1.1.0では、下記の機能を追加しています。

 - iOS(Safariのみ)からも使えるように。
 - リア/フロント カメラの切り替え

## iOSで使えるように
残念ながら本日(20200216)時点ではChromeでは非対応で、Safariでしか確認できませんが、iOSでも動作するようになりました。具体的には、iOSでautoplayとするには下記のように`playsinline`及び`muted`を入れる必要がありました。これで自動再生できるようになりました。

```HTML:index.html
<video playsinline muted autoplay id="video"></video>
```

## リア/フロントカメラの切り替え
そもそも何も設定しないとセルフィ―スタイルの方のカメラ(facingMode: user)しか起動せず困っていたので、初期設定を外側を向いているカメラ(facingMode: environment)との切り替えを用意しました。
解決方法は簡単で、`getUserMedia`したときの引数に、`facingMode: environment`などを指定してあげるだけで終了です。

```Javascript:index.js
// Camera facing mode = flip mode
const FACING_MODE_ENVIRONMENT = "environment";
const FACING_MODE_USER = "user";

let gCurrentCameraFacingMode = FACING_MODE_ENVIRONMENT;
navigator.mediaDevices.getUserMedia( 
  { video: { facingMode: gCurrentCameraFacingMode } } 
)

// Flip Cmera
filpCameraElem.addEventListener( "click", async ev => {
  switchCamera();
});

// Flip camera
const switchCamera = () => {

  if( gCurrentCameraFacingMode === FACING_MODE_ENVIRONMENT ){
    gCurrentCameraFacingMode = FACING_MODE_USER;
  }else{
    gCurrentCameraFacingMode = FACING_MODE_ENVIRONMENT;
  }
  startStreamingVideo();

}

```