DEV Community

Cover image for How to Implement WebCam Recording on the Web Using JavaScript
DrPrime01
DrPrime01

Posted on

How to Implement WebCam Recording on the Web Using JavaScript

Although videoconferencing technologies have existed for decades, they became more popular in 2020 due to the COVID-19 pandemic. Due to the lockdown restrictions, workplaces, public institutions, and even governmental institutions adopted video calls and meetings. Zoom, a popular videoconferencing application, saw a massive user increase during the pandemic and became a household name. Since then, almost every software application built for public and private institutions has had webcam recording as an essential feature.

Webcam recording is a useful software application feature because it is feasible for videoconferencing (as mentioned above), content creation, security (such as biometric authentication), and even monitoring purposes, such as an online exam or patient monitoring in telehealth applications. It is also found in many applications today, including Zoom, Loom, Microsoft Teams, Google Meet, YouTube, Facebook, Instagram, Zendesk, Freshdesk, Teladoc, and Amwell, among many others.

In this article, you’ll learn how to implement webcam recording in a web application using HTML, CSS, and JavaScript, access and use the MediaRecorder API, record and save videos, add audio, and handle multiple videos and audio options.

Prerequisites

  • Basic understanding of HTML, CSS, and JavaScript
  • Familiarity with modern web development tools
  • A development environment (e.g., VS Code, browser console)

Setting Up the Project

Create a Basic HTML Structure

  • Open your code editor and create an HTML5 boilerplate
  • Add the necessary elements
    • Buttons to start camera, start recording, and stop recording
    • Select elements to choose your preferred video and audio devices
    • A link to download the video after recording
  • Add the link tag for the styles in the head section and a script tag for JavaScript in the body element
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Webcam Recording</title>
        <link rel="stylesheet" href="./style.css" />
      </head>
      <body>
        <main class="main">
          <div>
            <h1>Webcam Recording</h1>
            <div class="webcam-container">
              <div class="red-dot"></div>
              <video id="webcam" autoplay muted></video>
              <div><button id="start-camera">Start Camera</button></div>
              <div class="media-devices">
                <select
                  name="video-devices"
                  id="video-devices"
                  class="video-devices"
                >
                  <option>Select video device</option>
                </select>
                <select
                  name="audio-devices"
                  id="audio-devices"
                  class="audio-devices"
                >
                  <option>Select audio device</option>
                </select>
              </div>
              <div>
                <button id="start-record" disabled>Start Recording</button>
                <button id="stop-record" disabled>Stop Recording</button>
              </div>
              <a id="download-link" style="display: none">Download Recording</a>
            </div>
          </div>
        </main>
        <script src="./script.js"></script>
      </body>
    </html>
Enter fullscreen mode Exit fullscreen mode

Styling the Interface

  • In the CSS file, add the styles that match the elements’ classes
  • Remember to make them responsive
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    body {
      width: 100%;
      height: 100%;
    }
    .main {
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 80px 0;
    }
    .main > div {
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    .main > div > h1 {
      text-align: center;
    }
    .webcam-container {
      min-height: 500px;
      max-width: 340px;
      width: 100%;
      border: 1px solid #cccccc;
      padding: 16px;
      display: flex;
      flex-direction: column;
      gap: 20px;
      position: relative;
    }
    .media-devices {
      display: flex;
      flex-direction: column;
    }
    .red-dot {
      height: 10px;
      width: 10px;
      border-radius: 99999px;
      z-index: 1;
      background-color: red;
      position: absolute;
      right: 28px;
      top: 28px;
      visibility: hidden;
    }
    .show {
      visibility: visible;
    }
    .red-border {
      border: 2px solid red;
    }
    video {
      height: 80%;
      width: 100%;
      border: 1px solid #aaaaaa;
      object-fit: fill;
    }
    select {
      padding: 8px;
      width: 100%;
    }
    button {
      padding: 8px 12px;
      border: 1px solid black;
      background-color: rgba(0, 0, 0, 0.8);
      color: white;
      font-size: 14px;
      font-weight: 500;
      cursor: pointer;
      width: 100%;
    }
    button:hover {
      background-color: rgba(0, 0, 0, 1);
    }
    button:disabled {
      background-color: rgba(0, 0, 0, 0.5);
    }
    .webcam-container > div {
      display: flex;
      align-items: center;
      gap: 12px;
    }
    @media screen and (min-width: 768px) {
      .webcam-container {
        max-width: 700px;
      }
      .media-devices {
        display: flex;
        flex-direction: row;
      }
    }
Enter fullscreen mode Exit fullscreen mode

The MediaRecorder and MediaDevices API

The MediaRecorder API is an interface of the MediaStream Recording API that allows you to record media using its MediaRecorder() constructor. You can create a MediaRecorder instance using the provided constructor and save it in a variable. The instance will receive a stream from the MediaDevices.getUserMedia() method. From this variable, you’ll have access to the start() and stop() methods to start and stop a recording session, respectively. The MediaRecorder instance also lets you access the stop and dataavailable events which you will see how they are used in the code block.

The MediaDevices API is an interface of the Media Capture and Streams API that allows a user to access connected media devices like cameras and microphones or any hardware source of media data. It provides 5 methods and an event. However, you will need only 2 methods for this project; getUserMedia() to access the user’s media devices and return a media stream, and enumerateDevices() to obtain an array of media device options connected to the system. This will give the user different media device options to stream from.

Implementing the Recording Functionality Step-By-Step

Variable Declarations and Initial Setup

    const videoElement = document.querySelector("#webcam");
    const startButton = document.querySelector("#start-record");
    const startCameraBtn = document.querySelector("#start-camera");
    const stopButton = document.querySelector("#stop-record");
    const downloadLink = document.querySelector("#download-link");
    const videoSelect = document.querySelector("#video-devices");
    const audioSelect = document.querySelector("#audio-devices");
    const redDot = document.querySelector(".red-dot");

    let mediaRecorder;
    let recordedChunks = [];
    let currentStream = null;
Enter fullscreen mode Exit fullscreen mode

This section initializes the DOM elements and variables needed for the webcam recording functionality.

Explanation

  • videoElement: The HTML <video> element where the webcam stream will be displayed.
  • startButton, stopButton, startCameraBtn: Buttons to control the recording and camera.
  • downloadLink: A link element used to download the recorded video.
  • videoSelect, audioSelect: Dropdowns to select video and audio input devices.
  • redDot: A visual indicator to show when recording is active.
  • mediaRecorder: An instance of MediaRecorder for handling the recording.
  • recordedChunks: An array to store the recorded video chunks.
  • currentStream: The current media stream from the webcam.

Populating Media Devices

    async function populateDevices() {
      try {
        const devices = await navigator.mediaDevices.enumerateDevices();

        // Clear and populate video devices
        videoSelect.innerHTML = devices
          .filter((device) => device.kind === "videoinput")
          .map(
            (device) =>
              `<option value="${device.deviceId}">${
                device.label || `Video Device ${videoSelect.options.length + 1}`
              }</option>`
          )
          .join("");

        // Clear and populate audio devices
        audioSelect.innerHTML = devices
          .filter((device) => device.kind === "audioinput")
          .map(
            (device) =>
              `<option value="${device.deviceId}">${
                device.label || `Audio Device ${audioSelect.options.length + 1}`
              }</option>`
          )
          .join("");
      } catch (error) {
        console.error("Error accessing devices:", error);
      }
    }
Enter fullscreen mode Exit fullscreen mode

This function retrieves and populates the available video and audio input devices into the respective dropdowns.

Explanation

  • navigator.mediaDevices.enumerateDevices(): Retrieves a list of available media input and output devices.
  • filter and map: Filters and maps the devices to create <option> elements for the dropdowns.
  • device.label: Uses the device label if available, otherwise provides a default label.

Getting Media Stream

    async function getMedia(constraints) {
      try {
        if (currentStream) {
          stopTracks(currentStream);
        }
        currentStream = await navigator.mediaDevices.getUserMedia(constraints);
        videoElement.srcObject = currentStream;
        startButton.disabled = false;
        startCameraBtn.disabled = true;
      } catch (error) {
        console.error("Error accessing media:", error);
      }
    }
Enter fullscreen mode Exit fullscreen mode

This function requests a media stream from the user's webcam and microphone based on the provided constraints.

Explanations

  • navigator.mediaDevices.getUserMedia(constraints): Requests a media stream with the specified constraints.
  • videoElement.srcObject = currentStream: Displays the stream in the <video> element.
  • stopTracks(currentStream): Stops the current stream if it exists before starting a new one.

Stopping Media Tracks

    function stopTracks(stream) {
      stream.getTracks().forEach((track) => track.stop());
    }
Enter fullscreen mode Exit fullscreen mode

Stops all tracks in the given media stream.

Explanations

  • stream.getTracks(): Retrieves all tracks in the stream.
  • track.stop(): Stops each track.

Device Selection Handlers

    videoSelect.addEventListener("change", async () => {
      await getMedia({
        video: { deviceId: videoSelect.value },
        audio: { deviceId: audioSelect.value },
      });
    });

    audioSelect.addEventListener("change", async () => {
      await getMedia({
        video: { deviceId: videoSelect.value },
        audio: { deviceId: audioSelect.value },
      });
    });
Enter fullscreen mode Exit fullscreen mode

These event listeners handle changes in the video and audio device dropdowns.

Explanations

  • When a user selects a different device, the getMedia function is called with the new constraints.

Initial Device Setup

    startCameraBtn.addEventListener("click", async () => {
      await getMedia({
        video: { deviceId: videoSelect.value },
        audio: { deviceId: audioSelect.value },
      });
      populateDevices();
    });
Enter fullscreen mode Exit fullscreen mode

This event listener starts the webcam and populates the device dropdowns when the "Start Camera" button is clicked.

Explanations

  • Calls getMedia to start the webcam.
  • Calls populateDevices to refresh the device dropdowns.

Recording Controls

    startButton.addEventListener("click", () => {
      recordedChunks = [];
      mediaRecorder = new MediaRecorder(currentStream, {
        mimeType: "video/webm; codecs=vp9",
      });

      mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0) recordedChunks.push(event.data);
      };

      mediaRecorder.start();
      redDot.classList.add("show");
      videoElement.classList.add("red-border");
      startButton.disabled = true;
      stopButton.disabled = false;
    });
Enter fullscreen mode Exit fullscreen mode

This event listener starts the recording when the "Start Record" button is clicked.

Explanations

  • MediaRecorder(currentStream, { mimeType: "video/webm; codecs=vp9" }): Creates a MediaRecorder instance with the current stream and specified MIME type.
  • mediaRecorder.ondataavailable: Event listener to collect recorded data chunks.
  • mediaRecorder.start(): Starts the recording.
  • Visual indicators (redDot and videoElement border) are updated to show that recording is active.
    stopButton.addEventListener("click", () => {
      if (mediaRecorder?.state === "recording") {
        mediaRecorder.stop();
        redDot.classList.remove("show");
        videoElement.classList.remove("red-border");
        stopButton.disabled = true;
        startButton.disabled = false;
      }

      mediaRecorder.onstop = () => {
        const blob = new Blob(recordedChunks, { type: "video/webm" });
        downloadLink.href = URL.createObjectURL(blob);
        downloadLink.download = `recording-${Date.now()}.webm`;
        downloadLink.style.display = "block";

        // Cleanup
        recordedChunks = [];
      };
    });
Enter fullscreen mode Exit fullscreen mode

This event listener stops the recording when the "Stop Record" button is clicked.

Explanations

  • mediaRecorder.stop(): Stops the recording.
  • Visual indicators are updated to show that the recording has stopped.
  • mediaRecorder.onstop: Event listener to handle the end of the recording, creating a Blob from the recorded chunks and generating a downloadable link.

Cleanup on Window Close

    window.addEventListener("beforeunload", () => {
      if (currentStream) {
        stopTracks(currentStream);
      }
    });
Enter fullscreen mode Exit fullscreen mode

Ensures that the media stream is stopped when the window is closed.

Explanation

  • stopTracks(currentStream): Stops all tracks in the current stream.

Conclusion

This article provides a complete implementation for webcam recording on the web using JavaScript. It covers device selection, stream management, recording controls, and cleanup, ensuring a smooth and functional user experience.

For a comprehensive list of MediaRecorder and MediaDevices API methods and events, see the MDN documentation.

You can take the project further by adding pause and play and implementing some functionalities and these events.

Live application: https://webcam-recording.vercel.app/
GitHub code: https://github.com/DrPrime01/webcam-recording

Follow me on X @theDocWhoCodes to learn more about what I do and write about.

Cover Photo by ThisIsEngineering

Top comments (2)

Collapse
 
legaciespanda profile image
Ernest Obot

Wow this is impressive

Collapse
 
wass_supp_6e189b8fb3b6d16 profile image
Wass Supp

I really rate how clean and neat this code is, I'd love to see you do something on the performance of what can be heavy rendering js because your code choice and web api explanation is great but there are lots of bad ways to achieve the same result if that makes sense?