
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
👉 Bunny (create your free account and get one extra free month): https://jsm.dev/jsm-bunny
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, the focus is on adding a screen recording feature to the application, enhancing its main functionalities like database, authentication, and video upload. The tutorial covers how to create a custom hook for screen recording, manage its state, and implement a user interface for controlling the recording process.
useScreenRecording
is introduced to manage screen recording states and behaviors.getDisplayMedia
method from the Navigator
JavaScript object for both video and audio.RecordScreen
is created for the user interface, integrating functionality to record the screen and audio.00:00:02 Okay, so now we're getting to the main functionalities of the app.
00:00:06 I mean, you could technically say that the main functionalities have been implemented.
00:00:11 Database, authentication, video upload, but it's this little part that will now be built on top of all of the infrastructure that we have implemented that'll
00:00:22 truly make it shine.
00:00:23 this little pop-up that allows you to record your screen and voice and then share it super easily.
00:00:32 So, let's make it happen.
00:00:34 Within the video kit down below, I shared the useScreenRecording hook, which is a hook that'll help us make it happen.
00:00:42 So just copy it and then create a new file under lib hooks and call it useScreenRecording.ts and then paste what we just copied.
00:00:57 If we collapse a couple of functions that we have here, such as this useEffect, handleRecordingStop, startRecording and stopRecording,
00:01:05 as well as the resetRecording, I think you'll very easily see what this hook is all about.
00:01:12 it manages the state of the recording, such as whether we are actively recording or not, the recorded blob, which is the actual video that has been recorded
00:01:22 up to this point, the video URL, if we have uploaded it, and then the recording duration.
00:01:28 And then to keep track of all of it, we're actually using references.
00:01:31 Then we're taking a look at the state of the recorded video, whether it's currently recording or not, and we can either start it,
00:01:39 stop it, or reset it.
00:01:42 For example, to start the recording, we first want to stop the previous one, of course.
00:01:47 Get access to the media streams.
00:01:49 So this is what I want to share with you.
00:01:52 Get media streams leads us to this utility function where you can see how we are making this happen.
00:01:58 First, we have to get a display stream.
00:02:01 This is the one for the video.
00:02:04 And you do it by using the Navigator JavaScript object to access the media devices.
00:02:10 And then you call a method called getDisplayMedia.
00:02:14 And you do the same thing for audio.
00:02:17 Once you have those, you just connect them together and we get access to the audio, which we then combine with the video.
00:02:24 which means that we are actively recording until we press stop.
00:02:29 So here we have the stop recording right here, where we do some cleanup and setState to isRecording is false.
00:02:37 So now we can create a new screen called recordScreen, which is going to be under components, recordScreen.dsx.
00:02:50 And we can run RAFCE.
00:02:52 We can add this record screen right within the header component.
00:02:57 Here, where we have the record a video button, you can see it here.
00:03:02 Well, we'll actually take this div, copy it, and replace it with a record screen, self-closing component like this.
00:03:12 And then within it, we can actually render this div that renders the record button.
00:03:19 kind of separating the logic of record into its own component.
00:03:22 So now if you come back here, this button is actually within the record component, allowing us to deal with all of the logic right here.
00:03:31 So first things first, we have to create a state for a model.
00:03:36 Yep, once we click record, we want to open up a model to allow users to choose some options.
00:03:42 So obviously this will have to be a client render component.
00:03:47 So I'll give it a use client directive right at the top.
00:03:50 Then I'll create a new state by using the use state snippet.
00:03:55 And I'll say is open set is open at the start set to false.
00:04:00 We'll also have to get access to the ref of this video that we're recording.
00:04:04 So I'll say const video ref is equal to use ref import that from react.
00:04:11 of a type HTML video element and at the start equal to null.
00:04:17 That's how it's going to look like.
00:04:19 And we'll also need to navigate once we actually record it.
00:04:22 So I'll say const router is equal to use router coming from next navigation.
00:04:28 Once you import all of these right at the top, We're good to go.
00:04:32 Right here on this button, alongside just a class name of PrimaryBTN, I'll give it an onClick, which is going to call a callback function and it'll set
00:04:43 isOpen to true, opening up our modal.
00:04:46 Next, we can go below this button and we can say if isOpen, In that case, we can render a new section, and this section will have a class name equal to dialogue.
00:05:00 And we can render a div that'll have a class name of overlay record.
00:05:07 And on click of that overlay, we actually want to close the model.
00:05:12 So let's create a quick function const closeModel, which is equal to a regular function where we set isOpen to false.
00:05:23 Oh, and here we also want to reset the recording.
00:05:27 And thankfully, we have already created a ResetRecording function, which is coming from the UseScreenRecording hook.
00:05:34 So I'll say const, destructure something, and make it equal to the call of the UseScreenRecording hook.
00:05:42 And here we get access to the ResetRecording functionality.
00:05:48 As a matter of fact, we can get everything that we're exposing right here from this Use Screen Recording, and that is the entirety of the state,
00:05:57 which means recorded blob, video URL, and duration, all of these things right here.
00:06:04 as well as the functions to start, stop, and reset the recording.
00:06:09 So, let's get access to all of those.
00:06:11 The first one is isRecording, recordedBlob, if we're done with the recording, recordedVideoURL, recordingDuration, then we have the startRecording,
00:06:26 stopRecording, and finally, resetRecording.
00:06:31 coming from useScreenRecording.
00:06:34 So if we close the model, we can then reset the recording, but we can also maybe create a function to handle the start of the recording.
00:06:42 So that'll be equal to handleStart, which is an async function.
00:06:46 Which simply calls the start recording function and maybe we want to start recording again So if that is the case, I'll say const record again is equal
00:06:58 to an async function Where we first reset the recording and just start it again.
00:07:04 So wait start recording and And if a recorded video URL and the videoRef.current exist, then we want to attach that video URL to that videoRef by saying
00:07:23 videoRef.current.source or src is equal to recorded video URL, just to record over it one more time.
00:07:30 Perfect.
00:07:31 So now we have all of these functions, which we can use right within our code.
00:07:35 I'll just collapse them so it's easier to see.
00:07:38 And then on click of this overlay record, we can simply call the close modal function.
00:07:44 Then within it, we can display the actual dialogue content.
00:07:49 So that's a div that says dialogue content.
00:07:52 And within it, we can display a figure that'll have an H3.
00:07:57 and it'll simply say screen recording.
00:08:00 So if we collapse it and click record a video, you should already be able to see a little screen recording pop-up appear at the top.
00:08:08 And if you click outside, it'll actually go away.
00:08:12 So this is good, and we can continue styling it.
00:08:15 Right below this screen recording, I'll render a button.
00:08:19 And this button will simply allow us to close the model.
00:08:22 So I'll give it a source of icons.close with an alt tag of close, a width of 20, and a height of 20 as well.
00:08:34 So now if you open it, you can close it.
00:08:37 At least you'll be able to once we give it an on click of close model.
00:08:43 And there you have it.
00:08:44 Now we can close it or open it.
00:08:46 But of course, what matters is below this screen recording header.
00:08:51 So let's head below the figure, and let's render a section.
00:08:56 If isRecording is true, in that case, we want to render an article or a div that'll have some empty div for some spacing,
00:09:06 and then a span that'll say recording in progress.
00:09:11 Else if we're not currently recording, but if I recorded video URL exists, that means that we have already recorded a video.
00:09:20 In that case, we just want to render a video tag with a ref equal to video ref.
00:09:27 And a source equal to recorded video URL and give it some controls so we can move it around.
00:09:34 But if we're not currently recording and we don't have a recorded video URL, then we can just display something else, which is a p tag that says click record.
00:09:45 to start capturing your screen.
00:09:50 And if I save this, of course, that's what you can see right now because we're neither recording nor we have finished the recording.
00:09:56 So below this section, let's create a div that'll have a class name equal to RecordBox.
00:10:06 And only if it's not isRecording and it's not recordedVideoURL, in that case, we want to display a button that'll have an onClick that simply handles the
00:10:21 start of the recording.
00:10:22 It'll also have a class name to make it unique called recordStart.
00:10:27 And within it, we can simply display an image that'll have a source of icons.
00:10:32 dot record with an alt tag of record a width of 16 a height of 16 and after the image we can just say record there we have it nice record button but now
00:10:46 just below it i'll say if is recording is true then we want to change it instead i want to show a button another one as a matter of fact i can copy this
00:10:59 button right here and paste it below.
00:11:03 This button will stop the recording and it'll say record stop and it can render the same icon.
00:11:10 That's okay.
00:11:11 And still within that button, below the image, we can say stop recording.
00:11:18 And let's make sure to properly end that button right here.
00:11:23 Perfect.
00:11:24 Oh, I think we are not closing something properly.
00:11:28 We have this record box.
00:11:30 Yep.
00:11:30 We're closing that here.
00:11:31 This button is being closed as well.
00:11:34 The image.
00:11:35 Oh, I think this has to be below the button.
00:11:37 There we go.
00:11:38 So now it says record.
00:11:40 And if I press it, take a look at this.
00:11:42 It's actually asking me what I would like to share.
00:11:46 Maybe one of my tabs, maybe the entire window or maybe the entire screen.
00:11:52 Perfect.
00:11:53 I'm not going to do it yet though, and you can see that there's one issue happening right here with the permission denied.
00:11:59 But first, I want to finish off the rest of the setup right here.
00:12:02 Below the Is Recording, I want to show what happens if we have recorded the video.
00:12:09 So here I can say if a recorded video URL exists, then I want to render an empty react fragment within which I'll have the button that'll say on click
00:12:23 record again, if they want to, with a class name equal to record again.
00:12:29 And this one will simply say record again.
00:12:33 And below this button, I'll render another button.
00:12:38 that'll have an onClick equal to goToUpload with a class name of RecordUpload, and it can simply render an image within it with a source of icons.upload
00:12:55 with an alt tag of upload, a width of 16, a height of 16 as well, and we can say continue to upload.
00:13:04 Of course, we have to create this goToUpload function.
00:13:08 So let me just create it right above const goToUpload.
00:13:13 For now, I will leave it empty, just like this.
00:13:16 There we go.
00:13:17 So what do you say that we test it out?
00:13:20 First things first, I'll head over to the desktop mode, as you're actually supposed to record your screen on desktop, not on mobile.
00:13:27 And we want to center this model, of course.
00:13:30 Currently, it's stuck at the top left.
00:13:32 So let's see how we're positioning it right here.
00:13:34 Record screen is being used within the header, and it's here below the link within the aside.
00:13:41 I think it's at the top.
00:13:43 Let's check it out in code.
00:13:44 If you head over to the record screen and scroll a bit up from where we were, you can see that this overlay record was supposed to be an absolute class,
00:13:53 which means that we don't have to put anything within it.
00:13:56 It's just there.
00:13:57 So we can actually self-close this div right here, and then we don't need to close it at the bottom or put any elements within it.
00:14:05 So I'll remove one of the closing divs right here, and that'll actually move it to the middle of the screen.
00:14:11 Perfect.
00:14:13 Now, if I click Record, you'll see that it actually gives me the possibility to record a tab, a window, or the entire screen.
00:14:21 In this case, I'll select this Figma tab and click Share.
00:14:25 Now, on my browser, I see this overlay, and it actually brought me to this screen immediately.
00:14:31 So I can go ahead and share some stuff about it.
00:14:34 And then if I go back, it actually says recording in progress and I can click stop recording.
00:14:41 Take a look at this.
00:14:43 It actually shows me the video that I have recorded right here and it also caught the volume.
00:14:49 So it was also recording my audio.
00:14:51 And if I'm not mistaken, if you share the tab, not the browser or an app or the entire desktop, then it's also going to pick the audio of your app,
00:15:02 which is pretty cool.
00:15:04 So yeah, now we can choose whether we want to record again.
00:15:06 There we go.
00:15:08 It automatically clears it and allows you to do another recording.
00:15:13 or you can just continue over to upload.
00:15:17 So this is the last thing that now we have to do, and that is to implement this continueToUpload function.
00:15:23 The goal of that function is, of course, to navigate us over to our upload form, and we can do that very easily just by doing a router.push pointing to
00:15:33 the upload.
00:15:34 But we also need to store the current blob or the current video that we have recorded up to this point somewhere in the browser so we can then use it later on.
00:15:45 So I'll say if there is no recorded blob, which is going to exit this function.
00:15:52 There's nothing for us to upload.
00:15:54 But if there is, we want to extract the URL of that created recorded blob by saying URL.createObjectURL of the recorded blob.
00:16:06 Then we want to set it into the session storage.
00:16:09 So I'll say sessionStorage.setItem.
00:16:13 I'll call it a recorded video and then we can provide all of the details within it.
00:16:20 such as json.stringify, an object, or we have a URL, a name of the video, which I'll call screen-recording.webm, a type,
00:16:32 which will be a recorded blob.
00:16:35 dot type, a size of recorded blob dot size, and duration, which will be equal to recording duration or maybe zero if something went wrong,
00:16:50 just so we know it's a number.
00:16:51 Now, in this case, we just provide the StringifyJson object as a second parameter.
00:16:56 We don't need to wrap it in yet another object.
00:16:59 So I can just remove it from here.
00:17:01 And you can see that now the warnings are gone.
00:17:04 And after we push, we just want to close the modal.
00:17:07 So let's give it a shot.
00:17:09 I'll go back here and I'll click continue to upload.
00:17:12 Currently, we get an issue saying cannot close a closed audio context.
00:17:17 So let's go back and let's give it one more shot to see whether that error happens again.
00:17:24 I'll try to record the Figma screen for a second once again and stop it in a second and click continue to upload.
00:17:33 Now we get redirected, but the issue is still there.
00:17:37 Cannot close a closed audio context.
00:17:41 To fix it, head over to the use screen recording file and then check right here where we are closing the audio context ref.
00:17:50 So we only need to close it if it's not already closed.
00:17:55 So I'll check if audioContextRef.current?state is not equal to closed.
00:18:04 In that case, we can call the audioContextRef.current.close and then catch an error if there is one.
00:18:13 And we can also set the audioContextRef.current to be equal to null.
00:18:20 Perfect.
00:18:22 Now we can try to record one more video.
00:18:25 I'll still go for the Figma here and stop it and upload.
00:18:32 And you can see that now we're not trying to close an audio that has already been closed.
00:18:37 Perfect.
00:18:38 So with that in mind, hey, where is the video that we have just recorded?
00:18:43 Well, we are saving it somewhere, but now on the upload page, we have to get it from the session storage.
00:18:50 So head over to upload, and right here, we can do it maybe below this current use effect.
00:18:57 We'll add another use effect.
00:19:00 That will be called whenever the video changes.
00:19:04 Here, we can create a new asynchronous function called checkForRecordedVideo.
00:19:14 It's going to be equal to an asynchronous function like this.
00:19:19 Now, why are we defining a function within a use effect?
00:19:22 That's because you cannot define the use effect itself as asynchronous.
00:19:27 This will not work.
00:19:28 So instead, you have to define an asynchronous function and then immediately call it like this.
00:19:34 Perfect.
00:19:35 So within it, we can open up a new try and catch block.
00:19:40 And in the cache, if we have an error, we simply want to say console.error and then put that error within there and say error loading recorded video.
00:19:54 But if everything is going well so far, we can try to fetch that video by saying const stored is equal to sessionStorage.getItem and we can get the recorded video.
00:20:10 After that, if stored doesn't exist, we'll simply return to exit out of it.
00:20:16 But if it does, we know that it is a stringified object.
00:20:20 So we can try to destructure some things out of it, such as a URL, name, type, and duration.
00:20:28 And that's going to be equal to json.parse.
00:20:32 Next, we have to get a video blob out of this by saying blob is await fetch.
00:20:40 URL dot then, and we get the result that gets resolved with a blob.
00:20:46 And here in JSON parse, we actually have to parse something and that something is this stored object that we have fetched from the session storage.
00:20:55 So now that we have the blob, we have to create a file out of it.
00:20:59 So we'll say const file is equal to new file to which we pass a blob, a name, a type, and last modified, which we can set to date.now,
00:21:15 just like this.
00:21:17 And now that we have everything, we can start the data transfer.
00:21:21 Believe it or not, all of this can be done programmatically.
00:21:24 You don't have to manually upload a video.
00:21:26 Here, we're going to do that programmatically by saying if video.inputRef.current exists, Then we want to say const data transfer is equal to new data transfer.
00:21:43 And this data transfer doesn't have to be imported anywhere.
00:21:46 Believe it or not, it is built into the DOM.
00:21:49 So you can see, you can just create a new instance of it and upload something.
00:21:54 Then we can say data transfer dot items.
00:21:58 And you can add the file that we have, and then you can set the video.inputRef.current.files to be equal to datatransfer.files.
00:22:11 After that, you want to get access to a change event, which is equal to new event of change.
00:22:19 And you want to say bubbles is set to true.
00:22:23 Now, what are these bubbles?
00:22:25 Bubbling is a concept within something known as event propagation.
00:22:30 And in this case, we have to simulate a user triggering file input change.
00:22:35 This is necessary because programmatically setting the files property of an input does not automatically trigger a change event.
00:22:43 So we have to dispatch a custom change event.
00:22:46 And then we can set the video.inputRef.current and we can dispatch this event.
00:22:56 A lot of this is very weird custom code because this pertains to specific functionalities of video and audio upload and screen recording.
00:23:07 You don't do this every day, but if you do, you're going to have to end up with a code that looks a bit like this.
00:23:13 Finally, we can say video dot handle file change, where the target is files.
00:23:21 And then we set the data transfer dot files right here.
00:23:26 And we can define this as change event, specifically change event to HTML input element.
00:23:34 Also, if we have, cut that.
00:23:37 Also, if we have a duration, we can also set the video duration to be equal to that duration.
00:23:45 And finally, we can remove the item from session storage by saying remove item, recorded video, And we can revoke that object because we no longer have
00:23:59 to store that video within that URL.
00:24:02 We can just remove it because we have uploaded it to this input.
00:24:06 So now if I save this, check this out.
00:24:09 The video we have recorded appeared right here.
00:24:12 This is amazing.
00:24:14 So now let's test that one more time.
00:24:16 If I head over here to record a video, I click record.
00:24:21 I'll go for a window and maybe let's take a look at the code in this case.
00:24:26 I'll share something from the demo saying, oh, take a look at this code.
00:24:32 and I'll stop the recording.
00:24:34 And you can see that it actually picked it up very nicely.
00:24:38 I'm going to mute myself in this case, and I'll say continue to upload.
00:24:43 Just like that, the video has been populated right here, and we can enter a title, such as sharing my code, building a screen recording software,
00:24:57 You'll need to upload your thumbnail as well.
00:24:59 So let's do that.
00:25:00 I'll select any random thumbnail from here and I'll click upload a video.
00:25:05 It says uploading.
00:25:07 And this time we get a new error saying, cannot the structure property of user intermediate value.
00:25:14 It looks like I have once again mistaken the bunny ID with the ID of that video stored in the database.
00:25:21 So if I head back over to the homepage, I think the upload went through.
00:25:26 And if I click on it, it works well, but it was just the redirect that I messed up.
00:25:32 So if we head over to where that redirect is happening, I believe that is within the upload.
00:25:39 Let's see.
00:25:40 Do we have a router here?
00:25:43 Yep, router.push video ID.
00:25:46 This is incorrect.
00:25:47 It should be pointing to the ID like this.
00:25:51 So instead of pointing to that video details page, what makes sense is also just to point to home page so we can see our new video uploaded right there.
00:26:00 So I'll just switch this over to homepage so that the next time we upload something, we'll be able to see it automatically on there.
00:26:07 Okay, but what matters most is that now we were able to super simply, within literally a couple of clicks, record our screen,
00:26:16 record our voice, and get a shareable link to that video deployed, stored, and shareable on the internet.
00:26:25 That's what this app is all about.
00:26:27 And that means that we're getting very close to the end of this course.
00:26:31 In the next lesson, let's switch over our profile page from displaying dummy videos to displaying the actual videos that we have uploaded.