Make a Camera App in Web (Part 1): Accessing the webcam
Tags: vue, javascript
Published: 2020-02-06 06:14:47

Last year I have a project that requires me to access device webcam using JavaScript. Since I can't find any good webcam library on Github that builds with Vue, then I decide to try to make it with JavaScript getUserMedia().

The problem with this method is, Internet Explorer doesn't support this, and in Safari, the compatibility is still unknown, both on mobile and desktop. It may be okay for IE because what on earth people still using that slow-problematic browser? But Safari is used by so many people and it may be a problem if you are a good developer who wants the best for the users. Well, it doesn't mean that I am a bad developer (or maybe I am!) for still using it despite the unknown compatibility with Safari. It just that we really need this feature back then. And maybe you are too.

So here it is, the low-key webcam feature that I build past year. You can open it directly on CodePen to run it to prevent any error message.

{% codepen https://codepen.io/ditarahma08/pen/GRRxZLW %}

Before we get started, in the CodePen above I use Vue.js and Bulma for the styling.

The first thing that we want is a button. This button will act as a toggle to open and close the camera div also to turn it on/off so your camera won't active all the time (for the sake of privacy).

```html
<div class="camera-button">
  <button type="button" class="button is-rounded" :class="{ 'is-primary' : !isCameraOpen, 'is-danger' : isCameraOpen}" @click="toggleCamera">
    <span v-if="!isCameraOpen">Open Camera</span>
    <span v-else>Close Camera</span>
  </button>
</div>
```

I put the class condition there. `is-primary` is active when the camera is closed, and `is-danger` gonna replace `is-primary` when the camera is open. These classes are not only to give the button some colors but also to better describe what the button does. And now, take a look at the JavaScript.

```javascript
data() {
  return {
    isCameraOpen: false
  }
},

methods: {
  toggleCamera() {
    this.isCameraOpen = !this.isCameraOpen;
  }
}
```

The `toggleCamera()` would act as a switch to change `isCameraOpen` condition. The default value of `isCameraOpen` is false, and when the true value assigned through the button click, which gonna call `toggleCamera()`, the webcam will be activated. 

Then we need a box which will display the camera. We're going to add `<video></video>` as a streaming media which will show up when `isCameraOpen` is true.


```html
<div v-if="isCameraOpen" class="camera-box">  
  <video ref="camera" :width="450" :height="337.5" autoplay></video>
</div>
```

A little warning here, on the mobile web, the camera would appear vertically, unlike in desktop. So you might want to tweak the width and height a little bit if you want it to be seamlessly responsive.

And then the most important part; creating a method that initiates media input permission which produces a `MediaStream` with tracks containing the requested types of media. 

```javascript
createCameraElement() {
  const constraints = (window.constraints = {
    audio: false,
    video: true
  });


  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(stream => {
      this.$refs.camera.srcObject = stream;
    })
    .catch(error => {
      alert("May the browser didn't support or there is some errors.");
  });
}
```

`constraints` is a parameter describing the media types requested. Since we just want a camera that can only capture an image and not video streaming, so we just going to set the audio permission to false.

Then we call the `mediaDevices.getUserMedia` with the `constraints` as a parameter. From this call, we received a stream that going to be assigned to the `<video></video>` element we created before.

Inside `constraints` parameter, you also can specify where the camera is facing (you may want to use the back-facing camera on mobile). To require the rear camera, you can put this instead 

```javascript
const constraints = (window.constraints = {
  audio: false,
  video: {
    facingMode: {
      exact: "environment"
    }
  }
});
```

We're going to call that method when the user clicks the open button. But since the open and close button is the same, we're going to prevent it to be called when the user clicks the close button. So we also have to modify the `toggleCamera()` a little bit like this.

```javascript
toggleCamera() {
  if(this.isCameraOpen) {
    this.isCameraOpen = false;
  } else {
    this.isCameraOpen = true;
    this.createCameraElement();
  }
}
```

There is still a problem though. We already managed to open the camera, and when we close it, it doesn't call the `createCameraElement()` again. But, the camera indicator on your laptop is still on! That makes the close button didn't do anything except just "hiding" the camera div. What we're going to do next is to really stop the camera from streaming.

```javascript
stopCameraStream() {
  let tracks = this.$refs.camera.srcObject.getTracks();

  tracks.forEach(track => {
    track.stop();
  });
}
```

The method above is going to catch the tracks on the camera element and then stop it. To make it works, let's put it into the close button.

```javascript
toggleCamera() {
  if(this.isCameraOpen) {
    this.isCameraOpen = false;
    this.stopCameraStream();
  } else {
    this.isCameraOpen = true;
    this.createCameraElement();
  }
}
```

That's it! We successfully access the webcam and put the stream inside an HTML tag. The next step is we want to capture the stream and download it as an image. But I'm going to make it in part two of this article.