Screen Recorder With Nextjs

Eugene Musebe

Introduction

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.

Codesandbox

The final project can be viewed on Codesandbox.

You can find the full source code on my Github repository.

Prerequisites

To be able to follow along with this tutorial, entry-level knowledge and understanding of Javascript and React are required.

Setting Up the Sample Project

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

Cloudinary Credentials Setup

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 :

1CLOUDINARY_API_KEY =
2
3CLOUDINARY_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.

1var cloudinary = require("cloudinary").v2;
2
3cloudinary.config({
4 cloud_name: process.env.CLOUDINARY_NAME,
5 api_key: process.env.CLOUDINARY_API_KEY,
6 api_secret: process.env.CLOUDINARY_API_SECRET,
7 upload_preset: process.env.CLOUDINARY_UPLOAD_PRESET,
8});

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.

1export const config = {
2
3 api: {
4
5 bodyParser: {
6
7 sizeLimit: "20mb",
8
9 },
10
11 },
12
13};

The following step will be to include a handler function to handle the backend's POST request.

1export default async function handler(req, res) {
2
3 let uploaded_url = ""
4
5 const fileStr = req.body.data;
6
7
8
9 if (req.method === "POST") {
10
11 try {
12
13 const uploadedResponse = await cloudinary.uploader.upload_large(fileStr,
14 {
15
16 resource_type: "video",
17 chunk_size: 6000000,
18
19 }
20 );
21
22 uploaded_url = uploadedResponse.secure_url
23
24 } catch (error) {
25 res.status(500).json({ error: "Something wrong" });
26 }
27 res.status(200).json({ data : uploaded_url });
28
29 }
30
31}

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’.

Recording video output

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:

1import React, { useRef, useState } from "react";
2
3import useScreenRecorder from "use-screen-recorder";
4
5import 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.

1function Recorder(){
2 const videoRef = useRef();
3 const [link, setLink] = useState();
4 let arr = []
5
6 return(
7 <div>UI coded here</div>
8 )
9}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.

1const {
2
3 blobUrl,
4
5 pauseRecording,
6
7 resetRecording,
8
9 resumeRecording,
10
11 startRecording,
12
13 status,
14
15 stopRecording,
16
17} = 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.

1async function handleVideo() {
2
3 let blob = await fetch(blobUrl).then(r => r.blob());
4
5 var reader = new FileReader();
6
7 reader.readAsDataURL(blob);
8
9 reader.onloadend = function () {
10
11 var base64data = reader.result;
12
13 handleUpload(base64data);
14
15 }
16
17}

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.

1html, body {
2 height: 100%;
3 background: #e7e5e4;
4 max-width: 1440px;
5 margin: 0 auto;
6 padding: 16px 64px;
7 font-family: 'Unica One', sans-serif;
8}
9h1 {
10 font-family: "Avant Garde", Avantgarde, "Century Gothic", CenturyGothic, "AppleGothic", sans-serif;
11 font-size: 50px;
12 padding: 20px;
13 text-align: center;
14 text-transform: uppercase;
15 text-rendering: optimizeLegibility;
16}
17h1.elegantshadow {
18 color: #131313;
19 background-color: #e7e5e4;
20 letter-spacing: 0.15em;
21 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;
22}
23
24.status {
25 background-color: #eeeeee;
26 border-radius: 0.5rem;
27 padding: 0.4rem 1rem;
28 margin-right: 0.5rem;
29 font-size: 1.5rem;
30 font-weight: 10;
31}
32video {
33 width: 100%;
34 border-radius: 1em;
35 margin: 1em 0;
36 margin-bottom: 2em;
37 box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px;
38}

Back to our record component's return statement, paste the following codes:

1return (
2 <div>
3 <h1 className='elegantshadow'>Screen Recorder</h1>
4 <div className='status'>
5 Status: {status}<br /><br />
6 Video Link: {blobUrl || "Waiting..."}
7 </div>
8 <div>
9 <video
10 ref={videoRef}
11 src={blobUrl}
12 controls
13 autoPlay
14 />
15 </div>
16 <div className='buttons'>
17 {(status === "idle" || status === "error") && (
18 <Button onClick={startRecording} variant='contained' color='primary' >Start recording</Button>
19 )}
20 {(status === "recording" || status === "paused") && (
21 <Button onClick={stopRecording} variant='contained' color='primary'>Stop recording</Button>
22 )}{' '}
23 {(status === "recording" || status === "paused") && (
24 <Button
25 onClick={() =>
26 status === "paused" ? resumeRecording() : pauseRecording()
27 }
28 variant='contained' color='primary'
29 >
30 {status === "paused" ? "Resume recording" : "Pause recording"}
31 </Button>
32 )}
33 {status === "stopped" && (
34 <Button
35 onClick={() => {
36 resetRecording();
37 videoRef.current.load();
38 }}
39 variant='contained' color='primary'
40 >
41 Reset recording
42 </Button>
43 )}
44 </div>
45 </div>
46 )

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!

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.