@Satoh_D no blog

大分にUターンしたので記念に。調べたこととか作ったこととか食べたこととか

Nuxt.jsでWebカメラで映像を撮影 & 画像の取得をしてみる

案件でブラウザからWebカメラにアクセスし、画像を取得する実装を刷ることになったのでサンプルを作ってみた。
Nuxt.js初めて使ったけど学習コスト低くていいですね。もっと勉強したい。

環境

  • MacOS: 10.15.6
  • node.js: v14.12.0
  • yarn: v1.22.5
  • Nuxt.js: v.2.14.6

手順

1. Nuxt.jsでプロジェクトを作成する

$ yarn create nuxt-app webrtc-sample

yarn create v1.22.5
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-nuxt-app@3.3.0" with binaries:
      - create-nuxt-app

create-nuxt-app v3.3.0
✨  Generating Nuxt.js project in webrtc-sample
? Project name: webrtc-sample
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: Buefy
? Nuxt.js modules: Axios
? Linting tools: 
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Version control system: Git

🎉  Successfully created project webrtc-sample

プロジェクトができたら yarn dev で起動 これで http://localhost:3000 でブラウザで表示ができるようになる この時点ではデフォルトのページが表示される

$ yarn dev

ソースコード

新規でページを作成し、そこにソースコードを記述する(index.vueを編集しても可)
今回は /pages/webrtc.vue を作成

<template>
  <section class="section">
    <div class="columns">
      <div class="column"><video id="localvideo" ref="localVideo" autoplay v-bind:srcObject.prop="localstream" width="500" height="500"></video></div>
    </div>
    <div class="columns">
      <div class="column"><canvas id="localPic" ref="localPicCanvas" width="640" height="350"></canvas></div>
    </div>
    <div class="buttons">
      <b-button @click="getUserMedia">映像を取得する</b-button>
      <b-button @click="getUserPic">写真を撮る</b-button>
    </div>
  </section>
</template>

<script>
export default {
  data: function() {
    return {
      localstream: undefined,
      canvas: undefined,
      movie: undefined,
      streamConst: {
        video: {
          width: 1280,
          height: 700
        }
      }
    }
  },
  mounted: function() {
    this.canvas = this.$refs.localPicCanvas;
    this.movie = this.$refs.localVideo;
  },
  methods: {
    getLocalMediaStream: function(mediaStream) {
      this.localstream = mediaStream;
    },
    handleLocalMediaStreamError: function(error) {
      console.error(`navigator.getUserMedia error: ${error}`);
    },
    getUserMedia: function() {
      navigator.mediaDevices
        .getUserMedia(this.streamConst)
        .then(this.getLocalMediaStream)
        .catch(this.handleLocalMediaStreamError);
    },
    getUserPic: function() {
      const ctx = this.canvas.getContext('2d');
      ctx.drawImage(this.movie, 0, 0, 640, 350);
    }
  }
}
</script>

Webカメラで撮影する部分と写真を撮る部分は以下の通り

Webカメラで撮影する部分

<template>
  ...
      <div class="column"><video id="localvideo" ref="localVideo" autoplay v-bind:srcObject.prop="localstream" width="500" height="500"></video></div>
  ...
      <b-button @click="getUserMedia">映像を取得する</b-button>
  ...
</template>

<script>
export default {
  data: function() {
    return {
      localstream: undefined,
      streamConst: {
        video: {
          width: 1280,
          height: 700
        }
      }
    }
  },
  ...
  methods: {
    getLocalMediaStream: function(mediaStream) {
      this.localstream = mediaStream;
    },
    handleLocalMediaStreamError: function(error) {
      console.error(`navigator.getUserMedia error: ${error}`);
    },
    getUserMedia: function() {
      navigator.mediaDevices
        .getUserMedia(this.streamConst)
        .then(this.getLocalMediaStream)
        .catch(this.handleLocalMediaStreamError);
    },
    ...
  }
}
</script>

ブラウザからWebカメラにアクセスするには navigator.mediaDevices.getUserMedia() を利用する
navigator.mediaDevices.getUserMedia()promiseが利用できるのでthen(), catch()を利用することができる
ストリームが取得できた場合、video要素にバインドすると映像が表示できるようになる

このとき、ストリームをv-bind:srcで指定してもよいが(ストリームをURL.createObjectURLで変換する必要あり)、URL.createObjectURLが廃止予定のメソッドであるためv-bind:srcObject.propで指定するほうが良いらしい

写真を撮る部分

<template>
  ...
      <div class="column"><canvas id="localPic" ref="localPicCanvas" width="640" height="350"></canvas></div>
  ...
      <b-button @click="getUserPic">写真を撮る</b-button>
  ...
</template>

<script>
export default {
  data: function() {
    return {
      canvas: undefined,
      movie: undefined,
    }
  },
  mounted: function() {
    this.canvas = this.$refs.localPicCanvas;
    this.movie = this.$refs.localVideo;
  },
  methods: {
    ...
    getUserPic: function() {
      const ctx = this.canvas.getContext('2d');
      ctx.drawImage(this.movie, 0, 0, 640, 350);      
    }
  }
}
</script>

撮影する部分はcanvas要素を利用して描画するのみ setIntervalを利用すれば指定ミリ秒後に写真を取り続けるといったことも可能になる

参考サイト

Vue.js&Nuxt.js超入門

Vue.js&Nuxt.js超入門