A screen recorder is a software that turns screen output into a video. It’s usefully applicable in use cases such as making videos of screen sequences to log results for troubleshooting, narration during capture, and creating software tutorials.
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
To be able to follow along with this tutorial, entry-level knowledge and understanding of Javascript and React are required.
Using your terminal, generate a Next.js by using the create-next-app CLI :
npx create-next-app screenrecorder
Go to your project directory by using the following command :
cd screenrecorder
Then, install all the required dependencies. We will use materialize to style our app’s buttons, cloudinary for our app’s cloud storage, and dotenv to handle cloudinary environment variables:
npm install cloudinary dotenv @materialize-ui/core
We’re going to be using Cloudinary for media upload and storage. It’s really easy to get started and it’s free as well. Get started with a free account at Cloudinary and then navigate to the Console page. Keep note of your Cloud name
API Key
and API Secret
.
Create a file named ‘.env’ in your project root directory and paste in the following :
CLOUDINARY_API_KEY =
CLOUDINARY_API_SECRET=
Complete the above with information from your cloudinary account.
Our last cloudinary setup phase will involve setting up our project’s backend. Inside the api directory, create a file named upload.js. We’ll use this component to upload the recorded. We begin by configuring the environment keys and libraries to avoid code duplication.
var cloudinary = require("cloudinary").v2;
cloudinary.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
upload_preset: process.env.CLOUDINARY_UPLOAD_PRESET,
});
The next step is optional. In the same upload.js file, you can include the body-parser to control the maximum request body size. You can use the sizeLimit to change the body size to your preferred maximum limit.
export const config = {
api: {
bodyParser: {
sizeLimit: "20mb",
},
},
};
The following step will be to include a handler function to handle the backend’s POST request.
export default async function handler(req, res) {
let uploaded_url = ""
const fileStr = req.body.data;
if (req.method === "POST") {
try {
const uploadedResponse = await cloudinary.uploader.upload_large(fileStr,
{
resource_type: "video",
chunk_size: 6000000,
}
);
uploaded_url = uploadedResponse.secure_url
} catch (error) {
res.status(500).json({ error: "Something wrong" });
}
res.status(200).json({ data : uploaded_url });
}
}
In the above code, inside the handler function, we declare a variable uploaded_url
which we will assign the url string received from cloudinary, and a constant fileStr
which we assign the request body received from the frontend. When a post function is fired, the request body is uploaded and the uploaded video’s url assigned to the variable uploaded_url
. All these are achieved using a try-catch method which ensures the handling of exceptions due to data or coding errors from the upload process. The function finally returns a response to the frontend containing the uploaded video’s url.
Our backend is complete. Let us now figure our project’s frontend. Create a folder named ‘components’ in your project root directory and inside it create a file named ‘record.js’.
We begin configuring our record.js component by installing the following:
use-screen-record which is a react hook for recording screen using MediaStream APIs.
Materialize-ui used to design our UI buttons
Include the above using the following imports:
import React, { useRef, useState } from "react";
import useScreenRecorder from "use-screen-recorder";
import Button from "@material-ui/core/Button"
After the imports, create a function Recorder
. Inside the function, we declare the const videoRef
which we will use to reference our video component, a hook that allows having state variables in our component, and an array arr
which we will use to handle our backend’s response.
function Recorder(){
const videoRef = useRef();
const [link, setLink] = useState();
let arr = []
return(
<div>UI coded here</div>
)
}export default Recorder
At this point, we can now introduce the use-screen-recorder
react hook, to record the screen using MediaStream APIs. Remember to include the prop audio: true
which is a boolean value indicating if the audio track should be added.
const {
blobUrl,
pauseRecording,
resetRecording,
resumeRecording,
startRecording,
status,
stopRecording,
} = useScreenRecorder({ audio: true });
Next, we introduce a handleVideo
function to encode the recorded video to base64 format and pass the encoded video to the handle upload function.
async function handleVideo() {
let blob = await fetch(blobUrl).then(r => r.blob());
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
var base64data = reader.result;
handleUpload(base64data);
}
}
The handle upload function uploads the recorded video to the backend where it’s uploaded to cloudinary and returns a response containing the respective cloudinary link used to store the video. We will use the array arr
to retrieve the video link from the response and sent it to our state variable link
.
The return statement returns the UI. Before we begin, add the following codes to the ‘styles/global.css’ directory to style our UI.
html, body {
height: 100%;
background: #e7e5e4;
max-width: 1440px;
margin: 0 auto;
padding: 16px 64px;
font-family: 'Unica One', sans-serif;
}
h1 {
font-family: "Avant Garde", Avantgarde, "Century Gothic", CenturyGothic, "AppleGothic", sans-serif;
font-size: 50px;
padding: 20px;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
}
h1.elegantshadow {
color: #131313;
background-color: #e7e5e4;
letter-spacing: 0.15em;
text-shadow: 1px -1px 0 #767676, -1px 2px 1px #737272, -2px 4px 1px #767474, -3px 6px 1px #787777, -4px 8px 1px #7b7a7a, -5px 10px 1px #7f7d7d, -6px 12px 1px #828181, -7px 14px 1px #868585, -8px 16px 1px #8b8a89, -9px 18px 1px #8f8e8d, -10px 20px 1px #949392, -11px 22px 1px #999897, -12px 24px 1px #9e9c9c, -13px 26px 1px #a3a1a1, -14px 28px 1px #a8a6a6, -15px 30px 1px #adabab, -16px 32px 1px #b2b1b0, -17px 34px 1px #b7b6b5, -18px 36px 1px #bcbbba, -19px 38px 1px #c1bfbf, -20px 40px 1px #c6c4c4, -21px 42px 1px #cbc9c8, -22px 44px 1px #cfcdcd, -23px 46px 1px #d4d2d1, -24px 48px 1px #d8d6d5, -25px 50px 1px #dbdad9, -26px 52px 1px #dfdddc, -27px 54px 1px #e2e0df, -28px 56px 1px #e4e3e2;
}
.status {
background-color: #eeeeee;
border-radius: 0.5rem;
padding: 0.4rem 1rem;
margin-right: 0.5rem;
font-size: 1.5rem;
font-weight: 10;
}
video {
width: 100%;
border-radius: 1em;
margin: 1em 0;
margin-bottom: 2em;
box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px;
}
Back to our record component’s return statement, paste the following codes:
return (
<div>
<h1 className='elegantshadow'>Screen Recorder</h1>
<div className='status'>
Status: {status}<br /><br />
Video Link: {blobUrl || "Waiting..."}
</div>
<div>
<video
ref={videoRef}
src={blobUrl}
controls
autoPlay
/>
</div>
<div className='buttons'>
{(status === "idle" || status === "error") && (
<Button onClick={startRecording} variant='contained' color='primary' >Start recording</Button>
)}
{(status === "recording" || status === "paused") && (
<Button onClick={stopRecording} variant='contained' color='primary'>Stop recording</Button>
)}{' '}
{(status === "recording" || status === "paused") && (
<Button
onClick={() =>
status === "paused" ? resumeRecording() : pauseRecording()
}
variant='contained' color='primary'
>
{status === "paused" ? "Resume recording" : "Pause recording"}
</Button>
)}
{status === "stopped" && (
<Button
onClick={() => {
resetRecording();
videoRef.current.load();
}}
variant='contained' color='primary'
>
Reset recording
</Button>
)}
</div>
</div>
)
The codes above render a page with the title Screen reorder
, a div to contain status information that watches for the uploaded video’s link. On the first time run, the Status will be idle
while the link will display a text waiting...
. There is also a video tag where the recorded video will be displayed and the respective buttons that relatively act as a step-by-step guide to recording a video. Each button is set to appear under specific status conditions relevant to the status of the video.
That’s it! We’ve successfully created a screen recorder.
Happy coding!