Record And Download Video In Your Browser Using Javascript
Videos have become very popular in our daily activities on the internet. Do you wonder how difficult a website needs to do to support video? It was tough for companies to bring Media technologies to their tech product since it required knowledge from both Media and Software development. But things had changed in the past few years; when WebRTC was introduced as an open standard for browsers and mobile devices, software developers can easily use its APIs without the need for advanced Media knowledge.
This post will introduce a simple example of recording Video and Audio via your browsers, then play it back or download it to your local drive. The code will support most of the major browsers (Chrome, Firefox, Opera, Edge) except Safari.
I’ve put the code to an example using ReactJS at my github repos, but you can freely integrate the snippets on any Javascript Framework as your favorite.
The main work flow for the app is: initialize media stream
> record media stream to blobs
> combine recorded blobs into single video blob
> play or download video
> clean up
.
Initialize Media Stream
The first part is to initialize a MediaStream. A stream can contain different tracks. In this sample, I request a stream contains video and audio from user’s device.
The initMediaStream
function will request video and audio from user devices follow our spec in the constraints
object. The browser may ask user to gain permission before returning a stream. If user doesn’t allow it, the code will throw an exception. I will handle exceptions at the caller and don’t need to worry about it here.
const initMediaStream = async () => {
const constraints = {
audio: {
echoCancellation: { exact: true },
},
video: {
with: 1280,
height: 720,
},
};
const stream = await navigator.mediaDevices.getUserMedia(
constraints,
);
return stream;
};
Record MediaStream to Blobs
To record a MediaStream, we will need to use a MediaRecorder. It supports different video codecs. Currently, the latest one is vp9
for video and opus
for audio. The function detectMimeType
below is to detect which type of codec we’re going to use. We don’t have to specify mimeType, but setting the best one can help us have the best quality and size. As you might have noticed in the code, the output video type will be webm.
const detectMimeType = () => {
const mimeTypes = [
'video/webm;codecs=vp9,opus',
'video/webm;codecs=vp8,opus',
'video/webm',
];
for (let mimeType of mimeTypes) {
if (MediaRecorder.isTypeSupported(mimeType)) {
return mimeType;
}
}
return '';
};
Now it’s time to create a MediaRecorder to record our video. In the function beginRecord
below, we will:
- Initialize a stream (by calling
initMediaStream
as described in the previous section) - Create a MediaRecorder object, using the stream as input, and provide
mimeType
as detected bydetectMimeType
function. - Support two callbacks,
onStreamReady
andonFinished
, so the caller can trigger some actions for those events, such as display video on screen during recording or capture recorded data into memory. - Return MediaRecorder object.
With this implementation, for every data received from the stream, the Media Recorder will put it into an array of Blob. The reason we need an array here is that the stream could be interrupted for many reasons. Every time it becomes available again, MediaRecorder will record a new Blob.
export const beginRecord = async (onStreamReady, onFinished) => {
const stream = await initMediaStream();
onStreamReady(stream);
const options = { mimeType: detectMimeType() };
const recordedBlobs = [];
const mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
}
};
mediaRecorder.onstop = () => {
onFinished(recordedBlobs);
stopMediaStream(stream);
};
mediaRecorder.start();
return mediaRecorder;
};
Combine Blobs into a Single Video
Below is a simple snippet for you to combine an array of Blobs into a single Blob. The output object is ready to download or playback.
const combineBlobs = (recordedBlobs) => {
return new Blob(recordedBlobs, { type: 'video/webm' });
};
Play And Download Video
To play or download a Video Blob, we need to create an URL for it.
const createBlobURL = (blob) => {
const url = window.URL.createObjectURL(blob);
return url;
};
To play the recorded blobs, we need to:
- Combine those blobs into a single blog (
combineBlobs
) - Create an URL for it (
createBlobURL
) - Set the URL as the source of a video element on your website
- Play the video element
export const playRecordedBlobs = (videoElement, recordedBlobs) => {
const blob = combineBlobs(recordedBlobs);
const url = createBlobURL(blob);
stopPlaying(videoElement);
videoElement.controls = true;
videoElement.src = url;
videoElement.play();
};
I use file-saver
, a third-party library, to download the recorded Blob as in the function download
below. You can also use plain Javascript code instead.
import FileSaver from 'file-saver';
export const download = (
recordedBlobs,
fileName = 'RecordedVideo.webm',
) => {
const blob = combineBlobs(recordedBlobs);
return FileSaver.saveAs(blob, fileName);
};
If you want to play stream directly in real-time, put it as a srcObject
of a video element.
export const playStream = (videoElement, stream) => {
stopPlaying(videoElement);
videoElement.srcObject = stream;
videoElement.play();
};
Clean Up
The clean-up steps are not mandatory but are recommended to do so, especially to stopMediaStream
. Otherwise, your website will keep accessing video and audio on user’s device.
const stopMediaStream = async (stream) => {
stream.getTracks().forEach((track) => track.stop());
};
export const stopPlaying = (videoElement) => {
videoElement.pause();
videoElement.src = null;
videoElement.srcObject = null;
};
Putting Things Together
As described earlier in the post, I’ve put things together in a ReactJS app. The app has three buttons: Record
, Play
, and Download
. These buttons have handlers, as described in the snippet below. You can customize it on your own to best fit with your JS framework.
Handler for Record
button:
const btnRecord_onClick = async () => {
try {
if (!recorder) {
const mediaRecorder = await beginRecord(
(stream) =>
playStream(recordingVideoEl.current, stream),
(recordedBlobs) => setData(recordedBlobs),
);
setRecorder(mediaRecorder);
} else {
recorder.stop();
stopPlaying(recordingVideoEl.current);
setRecorder(undefined);
setRecorded(true);
}
} catch (err) {
console.error(err);
}
}
Handler for Play
button:
const btnPlay_onClick = () => {
try {
if (!playing) {
setPlaying(true);
playRecordedBlobs(playingVideoEl.current, data);
} else {
stopPlaying(playingVideoEl.current);
setPlaying(false);
}
} catch (err) {
console.error(err);
}
}
Handler for Download
button:
const btnDownload_onClick = () => {
try {
download(data);
} catch (err) {
console.error(err);
}
}
Conclusion
In this post, I’ve given a small example of recording and downloading a Video using plain Javascript API. In my opinion, it’s pretty straightforward using built-in functions without the need for any other third-party API. While the script doesn’t support Safari yet, it does support all other major browsers and already covers a big partition of users.