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>
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;
}
}
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;
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 ofMediaRecorder
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);
}
}
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
andmap
: 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);
}
}
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());
}
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 },
});
});
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();
});
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;
});
This event listener starts the recording when the "Start Record" button is clicked.
Explanations
-
MediaRecorder(currentStream, { mimeType: "video/webm; codecs=vp9" })
: Creates aMediaRecorder
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
andvideoElement
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 = [];
};
});
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);
}
});
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)
Wow this is impressive
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?