In this chapter, we're going to go ahead and continue using Mux webhooks to populate some more information in our video databases records. Let's start by creating the UI part first, so you can understand why we need the video asset ready event. I think that's going to make a bit more sense. So let's start by going inside of the public gist that I have prepared, where I told you that you can find the logo for the application. Well, now I've prepared a little placeholder for you which we're going to use when a video doesn't have a thumbnail set.
There are two ways you can obtain this. You can of course use any image you want or you can simply download one from my gist here or if you have access to the source code just find the public folder and you will find a file called placeholder. So the way I'm going to put it in my project is I will literally copy the svg. If you want you can just drag and drop the file. There we go.
That's it. That's my file. So what we're going to do now is the following. We're going to go inside of source, inside of modules, videos, and we're going to create the components, my apologies, UI, and then inside we're going to create components like this. And then finally you can create the video thumbnail.tsx like this.
Let's go ahead and export const video thumbnail here. Let's return a div with a class name of relative. Let's go ahead and add a comment here. This will be our umnail wrapper, I guess. So this will be that div.
And then below here, I'm gonna add a comment, duration box, video duration box, right? We're gonna have that here. You do add video duration box, Just for you to understand why we're going to have this wrapper first and then a thumbnail wrapper again. So both of these will need to be relative. So let's go ahead and add relative here again.
Pull width, overflow hidden. Transition all. Like this. Actually, we might not need all of these. So let's just add Overflow hidden here, Rounded, Extra large, and aspect video.
Later on, we're gonna add some more classes here, but it will make more sense once we can actually, I'm kind of keeping information from you because I want to naturally build this. But since I already know how this is going to look like, basically, we're going to have two image elements inside. One will be a normal static image and the other one will be a GIF, right? An animation. So when we hover over this element, we're going to hide the static image and show the animation playing so we can show a few snippets from the video to the user without actually loading the video.
So that's why I'm telling you that we're going to need to have some additional classes here in the future but for now all I want to do is just render this placeholder which I just put in my project. So now let's add an image from NextImage. Let's give it a source of slash placeholder.svg, an alt of video or a placeholder, actually a thumbnail, and let's give it a fill property like this. We're also gonna give it some class names here. Let's give it size full which yeah if you don't know size dash full is the same thing as writing height full and width full So whichever one you prefer.
And let's also add object cover here, like this. And I think this will be enough to try out the video thumbnail. So now what I want to do is I want to go inside of my studio UI sections, video section. And inside of the first table cell where we rendered the video title, we are now going to render that thumbnail. So add a div here with a class name, flex items center, gap four.
Another div here with a class name of Relative, AspectVideo, width of 36 and shrink 0, and then a video thumbnail inside. Now the video thumbnail will be imported from Modulus Videos UI Components, video thumbnail. And if you try it out you should now see the empty thumbnails here because none of our videos at the moment have thumbnails so of course we are using just the normal slash placeholder SVG. So just double check that instead of your public folder you have the placeholder .svg here. Great!
So what our job is to do now is to use the handle video asset ready webhook right here and actually populate the thumbnail. So let's go ahead back inside of our route for the video inside of source app folder, API videos route. And we have video asset created. So what we're gonna do next is we're gonna do case video asset ready, like this. Now inside of here, what we have to do is we have to find the asset which we previously updated so now we can use the asset ID if we want to.
We can use either max upload ID because I think both will be preserved in the data. So first things first, when we handle a new event, we have to get the data. So let's copy the data from here, data, and let's use video asset ready webhook event. So you should have this imported as well. And now you have the new data here.
And this is what we're going to do. First, let's get the playback ID. We can get the playback ID from data, playback IDs. And by the time we get to video asset ready we should almost definitely have it like this but as you can see it's still throwing some errors so we do have to use the optional chaining methods here like this actually it looks like we don't need it here. We just need it here.
So we have to put it like this. And then what we can do here is if we have no playback ID, we can immediately stop this, right? Or we can just return a new response, missing playback ID, because without this, there is nothing much we can do for this video. So I'm just going to throw back an error here. Then what we are going to do now is the following.
We can easily get the thumbnail URL by using the playback ID. So the thumbnail URL is made by going to image.mux.com, playback ID, and then thumbnail.jpg. That's how you create a thumbnail using Mux and the playback ID. And we have to wait until the video asset is ready And only then can we obtain the playback ID because the playback ID does not exist here. As you can see, they have made it optional because there's a chance it doesn't exist.
But I'm pretty confident that it will always exist here because in this webhook event, yeah, I think they didn't, you can see it when I hover over data here, they use the same asset object for all of them. So they didn't exactly modify how the asset will look like on different webhook events but we know how it's going to look like but either way we have protection in case it's missing here. Great and now we have the thumbnail URL which is cool but One thing that we are missing is inside of our schema videos, we don't have the thumbnail URL here. So let's go ahead and go after mux track status. Let's add thumbnail URL.
And I'm not gonna prefix this with mux because we will be able to upload our URLs as well, right? We will also be able to use AI to generate a thumbnail. Great. So when I added this, I'm now going to do a Bonnex Drizzle Kit push, just so the new thumbnail URL is pushed to my database. Then what I can do in the route here is I can update my video.
Let's do await database, update the videos schema.set. Now let's update the max status to new data status, because at this point, the status will be ready. So it would be nice to indicate that to the user. We will have the playback ID. We will have the mux asset ID to be data.id and we will have the thumbnail URL like this.
And then we use where equals videos mux upload ID matches data upload ID. It looks like data upload ID is also optional at this point. So I'm going to check the same thing here. If we have no data upload ID, there is no way for us to know which video we are supposed to upload, modify, so we're just gonna say missing upload ID and a status of 400 here as well. There we go And now we don't have to worry about that.
Great. And let me just add a break here. And let me confirm. Do I have a break here? Yeah, a lot of people prefer, you know, using this switch cases for webhooks.
I don't know. I've always preferred if clauses. I always find them to be more readable. But yeah, okay, let's continue using the switch case here. Just make sure you added a break here.
And I think that now this should work. Let's go ahead and let's try it out. Let's go inside of our video thumbnail component and let's create an interface video thumbnail props and let's give it a image URL which will basically be a string but it can also be optional in case it doesn't work, in case the video is not ready yet. So video, thumbnail props, image URL. What we're going to do is the following.
We are going to check if we have the image URL or we're going to use slash placeholder svg like this. So by default I think nothing should really change here right But let's go ahead and try this out now. So make sure that you have bun run dev all running so you should have your ngrok running as well so the mux webhook can communicate. And I'm going to add a new asset here and I'm going to go ahead and upload my demo video. Now I'm not sure if this will work on free tier but we're going to see either way so let me go ahead inside of my mux here I have my assets inside of my YouTube development here.
This is the most recent one that I've added here. 10 second, okay. Playback and thumbnails. This is the playback ID, right? And I think that if I click on create thumbnails, they teach you how to do that here.
For example, this is exactly what we did on the back end, right? Image.mox.com and then we used the playback ID, which is exactly what we have here. And then just thumbnail and then .png or something else. So I think this should work. Let's see.
Looks like we got a few post requests inside of our webhook here so that could mean that there is a webhook, that there is a video. Looks like I don't have this running. Let me just run my DrizzleKit Studio here. So that should now mean that in my database I should have at least one video with a thumbnail URL. Let's see if that's true.
There we go! Looks like one of them has a thumbnail URL. So let's go ahead and try this out now. I'm gonna go ahead and refresh. Maybe they're not ordered correctly but looks like no, looks like it's not working.
I think I know why it's not working because we forgot one thing instead of modules studio UI sections videos section I never passed the image URL to be video.thumbnail URL And let's modify this to accept string or null. There we go. So no errors now. Let me refresh. All right, an error.
An error is because image.mox.com is not configured under images in your next.config.js. So just confirm that this is the hostname you have, image.mux.com, or if you have any other hostname, that's okay. You just have to know what your hostname is. In order to fix that, we have to go inside of next.config.js, And we now have to add images, remote patterns, radical, HTTPS, and hostname, imagemux.com. And the change of the environment will automatically restart the server.
So you just have to wait a bit longer this time because this counts as a server reload. And let's wait a second and there we go. You can see that we have automatically generated thumbnails coming from Mux. This is really, really cool. So from now on, for every video you upload, you will have an equivalent thumbnail being generated.
Great! But that's not all we can do. We can also create animated GIFs, as you can see. For example, let's wait for this to load. You can see how this video is slightly moving, right?
It's a very short GIF, but it is an animation and we can do that as well. So let's go ahead and go inside of our schema here and besides thumbnail URL, let's add preview URL as well, like this. And let's go ahead and do Bonnex Drizzle Hit Push and then let's go back inside of our videos webhook here and what we're gonna do now is we're gonna create the preview URL in the same way so const preview URL will be the same thing https and this will be image of mox.com again playback ID, and this will be animated.gif, like this. And then you can pass in the preview URL here as well. Great!
So let's go ahead and check that out. So this is how we're going to do that. We can't really see this anywhere now. So what you have to do is you have to upload another video again. For example, I'm going to upload another video and I'm going to verify with DrizzleKit Studio.
Let me just find the proper commands. So DrizzleKit Studio, I'm going to go ahead inside of here again and I will refresh my videos. And you know, when these webhooks hit again, that probably means that the video asset already has been added here and I should now have a preview URL for at least one of my videos here. There we go. So what I'm gonna do now is I'm gonna force the code to display the GIF instead.
So I'm going to go inside of modules, studio, UI, sections, video section, instead of thumbnail URL, I will now use the preview URL like this. And let me just refresh this entire thing. And now you can see that this is a GIF. It is changing, right? You can see how, you can see me scrolling in here.
Great, so that is a GIF, Amazing. And now this is what we're going to do. We're going to wrap up our video thumbnail by making it behave properly. So we're going to pass in for the image URL, the image URL, and then we're going to have the preview URL. Video preview URL.
And this is thumbnail URL, right? So we're gonna have these two, but besides that, we will also pass some more things here. We're also gonna pass the title, which will be video.title. Now let's go inside of the video thumbnail and let's modify the props so it accepts those as well. So the title will be a required string.
We're going to have the preview URL which is going to have the same properties. And now we can extract the title and the preview URL from here. And now we're going to go ahead and do our little trick with the video thumbnail here. So this is how we're going to do it. We're going to give this whole element a group like this.
And then what we're going to do for this first image here is the following. Let me just go ahead and collapse all of these elements here. In here, what I'm going to do is I'm going to set the, when the group hover is active, I'm going to set the opacity of this image to zero. Right, So right now, if I hover, it disappears. And what I'm gonna do is I'm going to copy this image element.
This one will use the preview URL, and it can also fall back to the placeholder, That's fine. And I'm just gonna reverse the logic. So when we hover, this will be fully visible. And by default, this one will not be visible. So now when I hover, it activates a GIF.
So I can kind of preview what's going on. You can see, for example, this one does not have a preview. So it just falls back to our placeholder, like something's wrong, right? Because it doesn't have the preview. Great.
And one more thing we need to do is the duration here. We need to kind of show a little box here in the bottom showing exactly how long this video lasts. So let's go ahead and do that and also we can now change the alt to both of this to be the title like this. There we go. So let's go ahead inside of our schema first and we're gonna go ahead and add a duration.
So after preview URL add duration which will be integer from drizzle pg core duration like this. Make sure you have imported the integer from result.rmpg core. And now we're going to go back inside of our API or videos webhook and in the same place where we generate the thumbnail and the preview URL, we can now generate the duration. So we're going to do const duration will be duration, my apologies, data.duration, question mark, math, round, data, duration, times a thousand because it uses milli units to store the duration or if for any reason we can't load the data duration we will just fall back to zero and passing the duration here. There we go.
So now after a video has been processed we're also going to know exactly how long the video lasts. So let's go ahead and go back inside of our video thumbnail here. And let's add a prop for the duration. So the duration will always be required. And what we're going to do is we're going to destructure the duration here.
And we're going to go ahead here at the bottom where we added the to-do and add a div here with a class name absolute bottoms2 right2px1py of 0.5 rounded background color of black with an 80% opacity text white, text extra small and font medium. And then inside of here, we're going to render the duration like this. So let's go inside of videos section here and let's render the duration to be video.duration. Now this will cause an error because duration can sometimes be not defined, so we can just set it to be zero as default. And now our, let's just see the errors, Column duration does not exist.
Did I push my changes? I did not push my changes. Don't forget to push the changes so you have an error like I do. Let's refresh now. There it goes.
So now you can see that I have this weird zero number here. So let's go ahead and make this a little bit prettier by creating a format duration util. So I'm gonna go ahead inside of my lib, inside of utils, right here, inside of utils. Oh, I have to go inside. My apologies, I was already opened.
Let's export cons format duration and let's accept the duration, which is a type of number. Let's get the seconds to be math floor, use the duration, use the modulus operator with 60 000 inside and then divide that by a thousand. Let's get the minutes using a similar formula, math floor, duration divided by 60, 000. And Then we can just return the string how it's supposed to look like. Minutes, the string, add start to, with zero in the string as the second argument.
And then we're gonna add a column, And then we're going to represent the seconds. So seconds, the string, add start, two and zero as the string, like this. There we go. So now go back instead of the video thumbnail and you can import format duration not from date FNS from our local util right make sure that's the one you import And now it should have a nicer displayed duration. But still, as you can see, these previous videos of ours don't have any duration.
So let's try this again. I'm gonna go ahead and upload. And this time I should have a proper duration from that video asset ready webhook. So now you can see I have no thumbnail here and the duration is zero zero zero zero. But if I refresh, there we go.
I can see the duration of 10 seconds here because mox limits the video to 10 seconds and I have the thumbnail and I have the preview URL. Excellent! So what we're going to do now to wrap up this chapter is the following. Let's keep track of everything we did. We updated the video schema, we pushed the database changes, we handled the video asset ready event, we assign the thumbnail and the preview and now we have to handle these two right here and these two right here.
And we also, also what I want to do is I want to wrap up the UI on this part, right? There's nothing we can do about a few of these, like we can't handle the views, the comments and the likes, but we can handle the status and the date and some additional information here. So how about we do that? Let's go inside of the videos section here and what we're going to do outside of this div, which encapsulates the video thumbnail, let's add a new div, which will have a class name. Blex, BlexColumn, overflow hidden and gap y of one and then inside of here a span which will render the video title.
So now we have the video title here but let's give this video title a class name of text small and line clamp one. In case it's too long it will collapse. Now you can go ahead and copy this span. This one will have a video description. Though the description is optional, so we are gonna add a fallback here to no description.
Like this. This one will have text extra small and it will have text muted foreground. There we go. Untitled and no description. Great!
So now what we're going to do is we're going to find the table cell which says status. And what we're going to do right here is the following. We're gonna add a div with the class name, blacks and items center. And inside we're gonna render videos max status. Like this.
So now you can see that we have the status which says ready, ready, ready. And this one says waiting because this one's never received their respective video, right? We didn't have the webhook ready at that time yet. But let's make this status appear a bit nicer. How about we go back inside of our utils here where we added the format duration and let's add one more util called snake case to title.
The string it will accept will very simply be used for some regex replacement here. So add a forward slash underscore forward slash g representing global go ahead and add a space in an empty string and then another replace forward slash backwards slash b backwards slash w forward slash g find the character and do character to uppercase like this and now that we have snake case to title we can go ahead and wrap this inside of that snake case to title like this Make sure you have imported snake case to title from libutil. I'll just move it here. Do I have an error? It seems like I have an error here.
So snake case to title max status which can be empty. So I'm just gonna fall back this to be an error right in case we haven't managed to assign any mux status at all something's obviously gone wrong. But there we go now you can see we display a nice capitalized ready and in case the status ends up being because the statuses are stored like this they will be you know video ready or video error or asset ready so when we pass that through snake case to title it will transform to video ready. Right and we can just fall back to error in this case. Great!
We can also handle the date while we are here. So let's go ahead and let's import format from date fms. We've never installed this explicitly but I'm pretty sure it came from Shatzian UI because we have a calendar package or I don't know some other component which uses DateFNS. In case you don't have it you can just do bun add or npm install DateFNS. Once you get the format from DateFNS you can go ahead and find the date here and you can just use format new date video created at and then pass in any format you prefer.
Great. And I'm going to leave it at this. Let's just go ahead and do one more thing. So I'm going to give this table cell a class name of text small and truncate in case it goes too far. But yeah, I think these are all the information we currently are able to give to the user.
And we can also store the visibility while we are here just so we can wrap up everything that currently our data allows us to do. We can't do views, comments or likes because that requires a whole another database entity. But we can do visibility, we just don't have it yet. So let's go ahead and do visibility. In order to do that, we're going to go inside of our schema and we have to create above the videos an enum.
So export const video visibility pg enum video visibility and you have to import pg enum from pg core like this And then just open up an array and add private or public, like this. And now what we're gonna do is we're going to add the visibility here, which will be video visibility. Visibility. Default will be private and it will be required. Like this.
And if you want to, for the duration, you could also do the same thing. You could set the default here to be zero and also make it not null. So it's kind of always required if you want to do it that way. I would not recommend doing the same thing for thumbnail and preview URL, simply because we are using local relative paths here. Right, We are using slash placeholder.sbg.
So if you were to do inside of your schema here, for example, a default of that, I'm not sure that's a good practice, right? I think you should aim at having it at least be hosted somewhere like this maybe, but we don't even know what our domain is gonna be or anything yet so I'm just not gonna do that. And in my original source code I didn't do this either but you know if you want to explore for yourself, maybe this is a solution you prefer rather than fall back into zero here. I think that's a solution I prefer as well. So this is what I'm going to do.
I'm going to go ahead and see, can I? My Drizzle Studio is not working. All right, so this is what I'm gonna do. I'm going to do Bionics Drizzle Kit push, and I probably will get some conflicts now because I have a bunch of data here. Yeah, it's throwing me some errors here.
The column Duration of relation videos contains null values. Okay, so this is what we're gonna do then. I'm not going to add the duration. My apologies, I'm not going to modify the duration. I will just add the video visibility here.
But I think that might also throw some errors because my other video visibility does not exist. Oh no, that's different because there are no conflicts. None of them have the video visibility, so I think that all of them will now receive video visibility. Let me just open my Drizzle Kit Studio. Nope.
Like this. So I think that all of my videos now should receive the video visibility. Let's see. And all of them should be private. There we go.
So all of them just got the visibility to be private. So what I'm gonna do now is the following. I'm gonna go ahead and select all of my videos and I'm going to delete them. And you have to do this as well if you want to modify the duration. If you're fine with this solution, okay, you don't have to.
But let's go ahead and add a default of zero and not null. So I pretty much always expect the duration to be something. And let's go ahead and do this. Inside of our route, for videos, we also have to be careful then. Okay, it's okay because we always fall back to zero.
If something's wrong we will just fall back to zero. That's fine. It's just important that this can never be null because then this will error. Great. So now let me just confirm inside of my schema.
I have modified this to be not null. So what I'm gonna do is I'm gonna do drizzle kit push. So let's ensure that that will work. There we go, changes applied. Great, and now let's go inside of the route And let's just wrap it up by adding some more events that we need to handle.
So a very simple one here will be if the video has error. So very similarly to case video asset ready, after the break here, we're gonna have case, I apologize here, case video asset error. Let me just confirm, did I do this correctly? Yeah, okay. We can also add space between this if that makes it easier for you to look at.
I really, really prefer if clauses over this. Alright, so now we have the data element as well. Payload data as video asset error and get the data inside. We can go ahead and do the same thing that we did above. If we have no upload ID, we have no idea what video has actually errored, so no need to even try something.
And then let's just do await database, update the videos, set max status to be data status here, which will most likely be an error, where equals videos upload max upload ID is data upload ID. And Let's break. Great. We have the video asset errored. And now let's go ahead and let's do Ace video asset and I believe I did not add this one.
So video asset deleted webhook event. So I'm going to go ahead and add this as well, video asset deleted. This is going to be particularly useful while you are in the free tier where videos are deleted after 24 hours. So you won't have mismatching data where, you know, one thing is deleted in Mux, but available in your database. If something deleted, if after 24 hours, the video gets deleted in Mux because of the free tier, it's also gonna be deleted on your backend, I mean, your database.
So there are no weird states here. So we can do the same thing as we just did above here. I think I can copy this entire thing. This will be not errored, but it will be deleted. There we go.
Then we can do await database delete. What's the argument here? Videos where equals videos max upload ID is equal to data upload ID. It's not camel case it's pascal case like this or snake case my apologies. Alright so that's if the video asset has been deleted.
And I'd like to immediately try this one out. I want to see if it's working. So I'm going to do Bundle Run Dev All here. And I'm going to go ahead and have my assets here. Make sure you are inside of the proper project.
And I really do want to clear them all out. So what I'm going to do is I'm going to delete them one by one. And since I recently deleted all the videos inside of my database I wouldn't be surprised if the webhooks are failing now because it should not be able to find any of these assets related. Right? So let's see.
It looks like it did hit my webhook a lot of times. No errors, which is, well, I guess, okay, right? Because this basically just does its job. It cannot find anything, and that's it. So let's see.
Does this work or not? I'm going to add a little console log here. Console log, deleting video Upload ID will be Data Upload ID. Like this. Let's try it out.
And let's add the equivalent creating video. How about we do that in the Video Asset Created. We're going to have creating video and we're going to have deleting video. So let's go ahead here. Let's refresh this.
We should have no elements here because we clear them all out with our studio. Let me click create, select files. And now I'm going to keep waiting here. There we go. Creating video with this upload ID.
That's great. I have my Drizzle Studio open so I'm gonna refresh the videos here And I think that now by default, I should have, yeah, I should have refreshed sooner. But the one thing we changed is that my duration was zero in the beginning. But great, we now also have the visibility and the duration here, the preview URL, the thumbnail URL. These two fields are still missing, so we will do that as well.
But great, we definitely have this one video here. And if I refresh, I should see all the updated information here. Sometimes when the GIF is not loaded yet, when you hover, you're gonna see like an invisible image because it's still loading that GIF. So now let's go ahead, instead of the assets here, let's refresh. As always, ensure you're in the proper project.
And Let's try and delete a video. So this is something that should happen after 24 hours, and I'm hoping that it will also fire a webhook. As you can see, this works, deleting video by upload ID. I think that if I refresh here, there we go. No video here.
Perfect. So we fully synchronized our assets on the mux and our assets for our database. Excellent. That works and I'm seeing a little warning here. The requested source which is our animated GIF is an animated image so it will not be optimized.
Consider adding the unoptimized property to image right away. Let's go ahead inside of our video thumbnail. This is the place where we use the preview URL. And we're just going to add an optimized. And we can in fact only add it if we don't have the preview URL.
Because otherwise, it's just going to be a placeholder. So if we have the preview URL, we will mark this as unoptimized. Great. And then we should no longer be receiving this warning here. Great, that works.
And now, the last thing we have to do is the track error. So we handled this, we handled this, we handled this, we handled this. These two are left. Now, here's the trick. This will only fire if you have audio inside of your videos.
And also if you enable the subtitles in the first place. So we're going to try this now. I'm going to go ahead and I'm going to... I forgot to break here. And I'm going to add video asset track ready.
So that's what I'm going to add. I never know how to write these cases. Let's go ahead and get the data here. There we go. This will be video asset track ready webhook event.
We should do what we always do, which is check for the upload ID. Oh, the upload ID does not exist on type track. Okay, so by the time the track is ready, I'm pretty sure that the video asset has already been completed which means that we have the asset ID stored so we no longer have to rely on the upload ID. I think we can use the asset ID. Let's see what do I have here.
I'm gonna have to take a peek at my source code to remind myself what is the proper way of solving this. So the asset ID comes from the following, data asset underscore ID. But here's the trick. It says that the asset ID does not exist on type frac. And I can see in my original source code, I added a little comment saying that that's not true.
Asset ID exists in here, but for some reason, they put a TypeScript object here, which says that it doesn't exist. So what you can do is the following. You can add and asset underscore ID string. And you can add a little comment here. Type script incorrectly says that asset ID does not exist.
Like this. And let's also assign our track ID, which will be data.id. Let's also get our new status, which will be data.status. And then what we're going to do is in case we're missing the asset ID, we're just going to throw an error, missing asset ID. Great.
And now what we can do is we can go ahead and simply update the video so await database.update videos set Mux track ID will be our new track ID. Mux track status will be our status here. Like this, where equals videos, mux asset ID matches the asset ID, and break. Now, we should also handle this, and I'm now going to add a console log here, track ready. The reason I'm adding this here is because I want to see this track fire, but I'm 90% certain that it's not going to fire now.
I'm going to add some space here and I will upload a video here. Demo.mp4. So this video has audio, but I think that since I've never enabled transcription, it will never have the asset track ready. Keep in mind that the asset track ready does take some time to fire. It's not as fast as our other webhooks.
So I'm gonna give it some time, but it's a 10 second video. I'm pretty sure that already it should have been here. So this is what we're gonna do now. If you actually go inside of your videos here and just look at your new asset you will see that it doesn't have the subtitles function here at all. So you can retroactively add subtitles but the proper way of doing it from the start would be going back inside of our videos module, our procedures here, right here in the upload, in the new asset settings.
So what we have to do here is we have to add below the playback policy, add an input like this, generated subtitles, language code will be English and the name will be English. So that's why I told you if you can find, you know, any English audio, because then you will be able to have generated subtitles. So now what happened is that my track ready event never fired, but I'm pretty sure that now when I add another video here, it's going to transcribe the video. So I'm going to have it uploaded and let's wait. So it created the video and let's just wait for some time.
Hopefully I did this correctly. And there we go. Now we have track ready. What does that mean? Well, now, if you go into your assets and select your proper one and go into the newest one, And if you go ahead and click play this time, you can see that you now have subtitles, right?
I'm just going to mute this so you don't have any audio feedback, but you can see the subtitles here clear as day, which means that the track is ready, which in turn should mean that inside of my Drizzle Studio, I should now have two records, one which doesn't have the track and the other one which has the track. There we go. One has the track status as ready and the other one does not have it at all. Perfect. So that is it for the webhooks.
I will come back a bit to this track status. I mean, this track ID, this is something that we will need. But the track status, it's not exactly something that we need other than displaying to the user like, hey, the captions will come in a moment, they are still being processed. But if you upload a video which has no subtitles, in that case, I'm pretty sure that this event will never fire right so we will explore if we can improve the behavior on that part so let's just wrap it up by properly displaying the visibility since both of them now have their visibility so we can go back to our videos section for that and let's find the visibility table cell let's add a div with a class name flex and items center if video visibility is private in that case we're going to render lock icon from Lucid React and give it a class name of size 4 and an MR of 2. Otherwise, we're going to render a globe 2 icon from Lucid React with the same class name.
And then below that, we can use Snake Case to title video visibility. Even though we know there's only two options, but why don't we reuse our util here? Make sure you have added the lock icon and the globe icon from Lucid React here. So now, there we go. You should see this saying private, or if it's public, it will have a globe icon and it will say public.
Great! So we now have the nice preview, we even have subtitles, which is not something we can see yet. We can see the duration of the video, we have completely synchronized what's going on with our 24-hour deletion. Basically, everything is now synchronized in a nice way. But still, keep in mind, you might forget that the videos are deleted after 24 hours, so always have a small number of videos, like one or two, so you can easily detect that.
Don't have like 50 videos and then you try and demo it to someone and everything's broken because the videos have been deleted. But keep in mind, if you have the ability to add a credit card, you can just click upgrade. Make sure you select the pay as you go tier and you will be able to finish this tutorial completely for free. You know I didn't manage to spend a single cent and you will have full benefits right you will have everything unlocked. I think that's simpler if this is causing any problems for you.
Great, I think we can now finally check two of these as well. Great, we finished all the Mux webhooks that we need and what we can do in the next part is we can create a proper form and some video player so we can actually see this video in our app. Great, great job.