In this chapter, we're going to go ahead and implement Mux video service. We're going to start by creating a responsive dialogue. On desktop, this will be a normal model, but on mobile, this is going to turn into a sleek drawer component like this. Let's go ahead and do that first. I'm going to go ahead and go inside of my components here and I'm going to create a responsive dialog.tsx like this.
I'm going to go ahead and import useIsMobile from hooks.useMobile. And then I'm going to import everything I need from my dialog and from my drawer. And you can see that the imports mostly match the dialog and the drawer and then the equivalent content, header and title. Make sure you add all of these imports you should not get any underline here because both of these components should exist in your project. Great!
Now let's go ahead and let's create an interface responsive model rots and let's rename this to responsive model actually. There we go responsive model. We're gonna give it a children of React, ReactNode. We're going to give it open of Boolean, title of string, onOpenChange, which will accept open, and return a void. Let's export const responsive model.
Let's assign these props right here. And let's go ahead and destructure all of them. Children, open title, and unopen change. Now let's use our isMobile hook. Use isMobile.
This hook was automatically generated with sha2xy and UI in case you were wondering where does this come from. Now let's go ahead and do if is mobile return and let's create a drawer we're going to give it an open onOpen onOpenChange onOpenChange and inside drawerContent drawerHeader and drawerTitle and renderTitle inside outside of the header we will render the actual body which will be the children. Otherwise, we're going to return a dialog. We can copy the props open and unopen change here and inside add a drawer content, drawer header, and drawer title, and render the title inside. Outside of the header, you can simply render the children again.
That's it. That's our responsive model. Now that we have the responsive model, let's go ahead and try it out. Looks like I'm not using dialogue content, dialogue header. Yes, my apologies.
So instead of dialogue, I'm supposed to use dialogue here. There we go. Make sure you don't do the same mistake as I did. So in your normal return clause, it's dialogue, dialogue, dialogue, dialogue, dialogue. Instead of isMobile, it's drawer, drawer, drawer, and drawer.
Great. You should have no errors in this code. Now let's go ahead and let's go back inside of our modules, studio, UI components and let's go inside of Studio Upload Model. What we're going to do now is we're going to render this responsive model. So let's wrap this entire thing into a fragment.
By default, nothing should change. If I go inside of my code here, this should still just render a button here. But what I'm gonna do now is I'm gonna go ahead and add a responsive model here from components responsive model like this. And inside of here, I'm simply going to add a paragraph saying this is this will be an uploader and let's go ahead and give this a title upload a video like this and let's give it a value of open for now and let's give it on open change for now to be an empty arrow function. And now as you can see if I just refresh if you're on slash studio basically wherever you render this create button you should be seeing a dialogue but if you switch to mobile mode it will change to a sleek drawer like this.
Great! So our responsive dialog works. Now let's go ahead and let's properly open this. So this is what we are going to do. We are going to leverage from this create method.
Well we can actually, yeah, we don't have to destructure it. What we can just do is the following we are just going to open this if we have the following create.data like this so if we get the data in that case we are going to open it. And onOpenChange here can simply call create.reset like this. So let's try it out, let's see if this will work. So when I create something new, you can see that it will only open a model once my data exists.
And then when I close this, it will simply reset this mutation, meaning that we are no longer going to have any created data from here. So this is kind of a my solution to use derived state. We could of course have created an actual is open state and then change it depending on on success state. But you know, why shouldn't we just use derived state from here, right? So this should work just fine.
Great! And this is pretty much it as to how much we can do without creating a Mux account. So let's go ahead and learn a bit about Mux, my decision to use Mux and so on. So we are now gonna create a free Mux account, which means that credit card is not required. The reason I emphasize this is because I know that some of you are students, some of your credit cards are not accepted depending on where you are in the world, so I try to find a service which allows this kind of access to their product, which is enough for us to complete the tutorial.
And I have some experience with video services. I have a platform of my own, and I actually switched through two video services. I first used Vimeo and then I decided that it's not good enough and it eventually became quite expensive almost $1, 000 a year for not that high of a traffic and then I switched to BunnyCDN which is great. It's a great tool and it's quite cheap, but it's not even close to Mux, I would say. It's a great tool, but I am considering switching to Mux now, especially with all the new features that keep coming to Mux much faster than they are in any other video service.
Besides that, Mux is used globally with extremely, extremely high prestige clients, from NBA to Shopify to all of those big companies that you can find on their website. They truly are the video API of the internet, right? Nothing comes close to them. Chances are, if you ever watched a live stream online, If you ever watched any kind of video platform, they are using some services of Mux. Not only that, it is also the most cost efficient video service platform, right?
So it's not just some random video service that I found. I truly tried to find the best solution for a YouTube clone. And besides that, it will give us everything we need. And the good news about this all is that they don't have a credit card requirement in order to try out their product. They don't even have a limit in a sense of locking your account.
No, it's free forever. But there are some limitations in the free Mux account. So these limitations are the length limit of the video. You will only be able to upload videos which I think are around 30 seconds. You can try and upload a longer video and I think they will just cut the video at 30 seconds.
The second limitation is that the video will be deleted 24 hours since it's been uploaded and you will also have a max watermark. So These are the limitations. None of this will really cause any problems for our tutorial, but keep this in mind in case you want to go into production. So what happens if you want to go into production? What can you expect in regards of pricing?
Well, I will tell you this. I added a credit card for my initial development of this project and I spent exactly $0. And I unlocked the full benefits of MaxWinning, meaning I can upload up to 12 hours. Videos are of course never deleted by themselves and there are no watermarks at all. And I spent $0.
I believe that they have a very, very high threshold until they actually start charging you, which I think will require around 100, 000 views for a video before they start charging you. You can of course visit this calculator.mox.com and I calculated for my platform codewithantonio.com and I think it will come up around $10 a month which is extremely cheap considering that I do have a certain level of traffic and over 170 hours of content. So it's quite cheap considering all that. But in case you don't have a credit card, you can still continue with the tutorial, don't worry. Besides Mux account, you will have to either create or obtain a very short video with English audio.
I would highly recommend finding something with English audio or simply recording a 15 second clip yourself. If you for whatever reason can't do that, you can visit this tinyurl.com slash newtube slash clip. I will, of course, I will open this in a moment and you will see, and then you will find a short demo.mp4 video which lasts 15 seconds. And it's just me speaking into a microphone in English. The reason I emphasize on English audio is because we are going to demonstrate subtitle generation for English videos.
And then we're later on gonna use the transcription for some AI features. So that's why I'm emphasizing this. And 50 seconds is only because that will be a very small video, so you will be able to test faster. You know, you don't have to wait for upload of a megabyte-sized video. And also, remember, length limitation, right?
Great. So we can actually mark our responsive dialog as finished now let's go ahead and let's create a Mux account. So head to Mux.com and go ahead and click on login. Once you're inside of here you should be able to see this screen. Store and stream video files online, create your first asset and all of those things.
And you will also see this right here, upgrade to remove all restrictions. And the restrictions are the watermark, the duration limits, and the fact that videos are deleted after 24 hours. Don't forget about this in case you're using this, you know, for your school project, or want to demonstrate this to someone and 24 hour passes and your videos get deleted. Don't be surprised. This is why it happens.
If you want to upgrade you can go ahead and click upgrade. I chose this option which is pay as you go which means that they will not automatically start charging you anything until you actually start reaching something. As you can see in your plan, it will be included 100, 000 views per month, right? So I think that it's very hard for you to accidentally reach 100, 000 monthly views. So I think you will stay within the free tier.
This is if you want to upgrade, you don't have to. For example, I will not upgrade for this tutorial here. Great, so what I want to do now is I basically want to create this, this uploader right here. And we can do that by first creating an environment. So I'm going to go ahead and create my new environment here and I'm going to call this new tube like this.
And I will select development. So let's call this new tube development like this and I'm gonna go ahead and select all users and click add environment and I should now have new tube development right here and I should be able to click on host and stream video here like this and now how about we go through the docs together and learn how to add the necessary packages and environment variables for Mux. So let's go ahead and start with integrate with your app right here. And I'm going to select new to deployment development and the permissions here. Let's see.
Let's select max video. Let's select all of them, right? I think it's okay for us to have them all. The access token name will be NewTubeDevelopment and let's click generate token. And now you have the access token id and the secret key.
And as you can see they don't store the secret key so memorize it or simply download it. So let's go ahead and let's add these keys to our project. So I'm going to go ahead and go inside of my project, inside of .environment.local and I'm going to add max token ID. That's gonna be the shorter key here and then max token secret. Right, and this will be exclusively for this environment which we created new tube Development, right?
So make sure that you are inside of here. Every time you visit this page, you know, just make sure that they didn't automatically maybe select development or production here. I decided to use this one, our new one, so that it's very clear that this is for that project in case you want to reuse this, in case you want to reuse this account of yours for any other projects you might build. So I created new to development here and I clicked integrate with your app. I selected new to development here as well.
I selected all of this and then I clicked generate token. I believe that you can also find the token in the settings here. There we go, access tokens. There we go, new tube development. But I can only see, as you can see, the Mux token ID.
I cannot see the Mux token secret. In case you think yours got lost, you can just click generate a new token, again, select new to development. And I'm not sure if we need all of these, but it's not going to hurt us to have it. Okay, great. Once we have that, let's go ahead and let's look at the documentation here together.
And specifically I want to focus on uploading the videos and we're going to use the Max Uploader for web. Basically this is what we are going to build. So let's go ahead and see. I'm not sure if we need this because I think there are instructions specifically for Next.js. I just want to see, okay, this is for playing the video.
I just want to confirm that, there we go, Max uploader react. I think this is what we're going to need. So let's go ahead and install that, but you know, be careful. Just wait for you. Wait for you to see, wait for me to add it so you can see the version that I have.
So I'm just gonna add it to my project and then you're going to see the version. So just a second. There, which one is it? There we go. So 1.1.1.
So what you can do to ensure that you have the same version is this, bun add muxmuxuploader 1.1.1. Let me just confirm in the package json muxuploader react 1.1.1. Great! So we now have that. So let's go ahead and develop our Studio Uploader here.
So I'm going to go ahead inside of my modules, inside of my Studio, inside of UI components, and I will create a Studio Uploader .tsx like this. And let's go ahead and create an interface, studio uploader props, which will accept an endpoint and on success here, like this. And let's also import everything we need from the Mux uploader, which we just installed. So from Mux, Mux uploader, we need a default export Mux uploader. And then we are going to have this kind of granular Mux uploader drop file, select progress and loader status.
This will allow us to customize it to make it a little bit prettier because you can see on their documentation, let me see on the max uploader here. It's not exactly the most beautiful uploader, like it's okay, but we're gonna make it look a little bit better like this, more similar to one on YouTube. All right, so now what we have to do here is we have to actually develop it. So let's export const Studio Uploader. Let's go ahead and destructure the props here.
Endpoint on success. Now inside of here, let's go ahead and give this a div and render max uploader here. I think by default, we should already kind of see this. Let's go ahead and do, instead of Studio Upload Model, just render the Studio Uploader. You can ignore the TypeScript errors for now.
So if I go ahead and refresh this and click Create, there we go. I can already see an uploader here. The one thing that's currently missing, as you can see right here, is we are missing... Let me just find... Okay, so the react usage was here.
Okay, we are missing an endpoint, right? So endpoint is a value that we need to pass to our mux uploader and in our case we're gonna have to create that endpoint and then we're gonna have to create some webhooks because remember, videos can take hours or if not days to process. Like for example, my videos in YouTube, which last 12 hours, they definitely take a couple of hours to process and we can't really await that in a promise. We need to set up webhooks in order to make that to work. But let's first solve this.
Pass the URL to the endpoint property on the Mux uploader component. So whenever we upload a video, we're going to have to reach a specific endpoint. In order to obtain the endpoint, we're going to have to install the node instance of mux. So again, I'm going to do mux from mux-node. You can wait a second if you want to see the exact version.
So this is my exact version. So for you, you can do bun add mux slash muxnode at 9.0.1. You should have muxnode and then what you can do is you can go inside of your source instead of lib and simply create mux.ts, import mux from mux muxnode and export const mux to be new mux and inside of here you will pass the token id and you will pass the token secret and we have both of those inside of our environment.local maxTokenId and maxTokenSecret so let's add them like this and maxTokenSecret like this you can even point exclamation point exclamation points at the end so then you won't have any errors here, but looks like there are no errors with or without these exclamation points, so this works fine. So now what we have to do is we have to go back inside of our video module, specifically our procedure where we created the video. And what we have to do is we have to create an upload, right?
So this is what we're gonna do. Const upload will be await mux which you can now import from lib mux like this mux.video.uploads.create And then we're going to open up new asset settings here and we're going to add the pass-through of user ID. The pass-through is a field, as you can see, arbitrary user supplied metadata that will be included in the asset details and related webhooks. Can be used to store your own ID for a video along the asset. So basically what a pass-through is, is, well, let me remind you, when we upload a video, it's not going to be immediately processed.
We're going to have to wait for a webhook and the webhooks are anonymous. Webhooks will not come from our logged in user. So we need a way to preserve which user has uploaded this video. Otherwise, Mux will simply fire a webhook on our part and we're gonna have no idea whose video is this. That's why we need some metadata and we can store that through passthrough.
And we're gonna use the database user ID for that. Great, so we have the passthrough, set the playback policy to be public. MP4 support will be standard. So these are the settings that I found in their documentation, right? Nothing other than that.
And I believe we also need outside of here, of course, origin. And we're going to set it to all, like this, just an asterisk. But I'm going to give a little to do here in production, set to your URL. But just so we don't mess up, you know, the development, you know, course can be quite annoying. Of course, it is secure, but it can also be quite annoying.
So this is how we create an upload right and once we create an upload inside we will be able to pass the following. We will be able to pass URL upload dot URL like this. So now what's going to happen is that if you go inside of your studio upload model, every time that you create something, you will have alongside your video also a URL, right? And then you will be able to do the following. You will be able to pass an endpoint which will be your create.data.URL.
Now, of course, this is what we're going to do. So I'm going to check if we have create data URL. I think I need to add this. Otherwise, we're going to render a loader2 icon and let's just close this. So let me collapse this so it looks a bit nicer.
Like this, there we go. And we can be more specific here as well. So let's specifically check if we have data URL like this. Great, And here we can also pass onSuccess just to be an empty arrow function for now, like this. All right, so now we should properly establish the endpoint to where this should upload.
And Now it basically knows, since we're using our Mux lib to create this URL endpoint, it knows that it has to upload it to this specific environment, right? So these tokens come from, my apologies, from this environment right here, NewTube development, right? Always, whenever you click on assets, always change to YouTube development, right? So those are the keys for this environment. That's why this upload URL is important.
And I think we can already try it out. So here's what you can do. If you didn't obtain a video, either go ahead and create it or simply go to tinyurl.com slash YouTube dash clip. Let me go ahead and copy this. So I'm going to go ahead and go here and it should simply open this public Google Drive which I've created and you can just go ahead and download a simple demo and before video.
Looks like I'm getting some error here, perhaps something wrong with my settings. I'm gonna see if that's something I have to change here. But basically it's just a regular video where I'm speaking in English subtitles here. And let's go ahead and try and uploading it. So I'm going to refresh everything here.
I will click create here. Oh, it looks like something went wrong. So let's see what that is. Looks like I got 500 back. So let's see how we should debug that.
I'm going to go inside of my procedures here. Looks like we have an upload here, but maybe this fails. All right. So I think I've debugged our issue. If I click Create here, I get an error here, and in the Network tab, I can then see an error here which says Invalid parameters, deprecated standard mp4 support is not allowed on basic assets.
So that's probably because when I developed this initially I used an account with my credit card added so I think I have to remove this. Let's see if this will now change anything. If I click create, there we go, video created and we actually have the proper upload created as well. So now I'm going to go ahead and upload this demo video. So let's go ahead and go inside of your assets here.
Select the new tube development, make sure you're here. And if you upload it, you should see it there. So I'm going to select my demo video here. Oh, it looks like no URL or endpoint specified. Cannot handle up.
It Looks like it's not exactly working as intended. So I'm going to go ahead and do console.log. Upload. And below that I'm going to do a console.log URL. Upload URL.
Let's go ahead and debug this together, see exactly what's going on. I'm gonna add some space here and let's click create. So it looks like it, I can definitely see the URL here. That's interesting. Oh, I think I know what's the issue.
The issue is not in our back end. The issue is that after I pass the endpoint in the Studio Uploader, I never use the endpoint. So let's pass in the endpoint like this. And let me try again now, demo.mp4. There we go.
Now it's uploading and it just says upload complete. Nothing more, nothing less. If I go here, I can finally see one new asset right here. And this is what it looks like. So it is a free test asset.
It looks like it's limited to 10 seconds, right? And it will be deleted after 24 hours. So I'm just gonna turn off the audio here And you can see it is for API test purposes only, which is exactly what we're doing. We're testing the API to see if it works, but you can see the status. It's ready.
You can see that we have the duration of the video and all of this other useful information. I'm gonna see if it will allow us to generate thumbnails on the basic tier. I hope it will, but it's okay. We will find some other solution if it doesn't allow us to do that in free tier. Great.
So now let's go ahead and discuss how should we exactly track that this video has now been uploaded. But if you take a look in my Bonnex Rizzle Kit Studio right now, if I go here to Studio Result, there is not a single video that I can tell you, okay, I know which Mux asset is for this video, right? We have no idea neither which user uploaded this, nor for what video did it upload it because our video database entity and mux asset are two different entities so we now have to find a way to connect them together and we're going to do that the following way before so I just want to do that before we actually spend some time with the Studio Uploader. Let's go ahead and do the following. I will close everything and let's go inside of our database schema here.
We're going to go inside of the videos and we're going to add everything we need regarding mux. So after the description, go ahead and add mux status. And yes, I will prefix them with mux. Simply, you know, for us to have it easier understanding, okay, this comes from mux, not something else. So mux status, mux asset ID.
This will be unique for each of our video. We're going to have Max upload ID again. This will also be unique. We're then going to have Max playback ID. Max upload ID, my apologies, playback ID, which will also be unique, mux track ID, unique as well, and we're gonna have mux track status.
Mux track status. Like that. So these are all the elements we're going to need for mux. So the mux status will basically tell the user what's the state of their video. Are we still transcribing the video?
Are we uploading it? What's the state? Are there any errors or something like that? The asset ID will be something we will receive once the asset was created. The upload ID is something that we are going to immediately populate when we create the video inside of our videos procedure.
So because we have the upload here, so we are simply going to pass the upload ID inside of here. And then when the webhook comes, we're just going to look for that upload ID and then we're going to connect the asset with the video. The playback ID is something that will come once the asset has been created and we're gonna use the playback to play the video and to generate thumbnails or previews. The track ID and the track status will only be available if the apologies, if the asset has subtitles, right? So if we can do any kind of transcription, in that case we will see the track ID and the track status as well.
Great, so let's go ahead and do that now. So we're going to go back inside of this procedures and this time what we're going to do is we're going to set the max status manually as waiting, as in we are waiting for a video to be uploaded in this place. And we are going to set the max upload ID here to be upload.id. Now of course this will not work just yet because we have to push these schema changes. So let me shut this down and do Bonnex Drizzle Kit Push.
I believe you even might get some errors at the moment right now, because you can see max status does not exist. So feel free to shut down the app and simply do Bonne Run Dev All here. Now I got a prompt here, what I want to do with this, simply because we have 20 items in our videos table. In our case, it really doesn't matter what happens. You can select both yes and no.
I'm gonna select no, just to see if it will work. Let's see, no again, no again, no again. I'm not gonna select no for all those fields. And it just says changes applied. It really doesn't matter for us because all of these videos are just empty videos.
Matter of fact, how about we delete all videos so we have a clear example. So let's do Bionics Digital Kit Studio. Let's open the studio and I will just remove all of my videos because all of them are empty anyway. There we go. Great, so I'm just gonna refresh my app here.
And we're still not, you know, totally ready to connect our asset with the video, because what we're missing is the webhook. So that's the next thing that we have to do. So let's start by going inside of our mux. Where is it? Here.
How about we go into settings and let's click on webhooks right here. Now again, make sure you change this to new tube development and click create new webhook. And now we have the URL to notify. And luckily for us, we already know what to put here because we used webhooks before that you can find in your dev webhook here. I hope you did this because it's quite useful.
You can see that now with one command, we are connecting to all these webhooks that we will use throughout our project. So this is HTTPS. Make sure to put HTTPS in front like this. And I'm gonna click create webhook here. And now I can click on show signing secret and copy this.
Let's go inside of our environment here and let's add mox webhook secret and add it here like this. There we go. Now let's go ahead inside of our source app folder API and let's go ahead and let's create videos and then let's go ahead and let's create a webhook and let's create route.ts like this and this will basically be our videos webhook So let's export const post request to be an asynchronous request. Like this. This is our webhook.
Obviously this will translate to localhost 3000 slash API slash videos slash webhooks. So I just remembered, we also have to modify that here, I believe. And I think I made a mistake by creating this like that. How about we remove this one, delete the webhook and create a new one. And just make sure you select YouTube development here.
And let's go inside of my package.json so I can obtain the URL again. So HTTPS, this slash API slash videos slash webhook. So make sure this is properly written, right? Let me just confirm. Slash API slash videos slash webhook.
API videos webhook, Great. And then you will have to copy the new signing secret and go back inside of your .environment.local and replace it right here. There we go. Great. So now this works.
And now what we have to do is we have to first of all prepare our signing secret to be process.environment and then just copy, don't type it out because you can add some typos here without you knowing. Great! Besides this, what we're gonna have to do is we're gonna have to prepare the equals from Drizzle ORM so we can find by user ID, by upload ID, something like that. And we're also going to need from mux, muxnode slash resources slash webhooks.mjs. Looks like it also works if you just go slash webhooks.
And from here, we're going to import all the types we are going to look at. So video asset created webhook event, video asset error webhook event, video asset ready webhook event, and video asset track ready webhook event. Great. And then let's go ahead and let's create one unified type called webhook event. And this should use all of these from above.
So our WebhookEvent can be one of these types. All right. Now inside of here, first things first, we're gonna check if we are missing the signing secret. And in that case, we can just throw a new error back max webhook secret is not set and let's just say true max webhook secret great okay then what we have to do is we also have to import headers let's see headers from next headers we have to do that So let's obtain the headers payload to be await headers. Let's get the mux signature to be headers payload get mux signature.
If we are missing mux signature it means that whatever is trying to access this API endpoint is not mux. It is something most likely malicious here. So no signature found with a status of 401. If we do have the signature, we can then go ahead and get the payload from await request.json. I'm not sure if we defined request, we did not.
So the request comes from here. It will be a type of request. This is a native type. We have our payload And then let's also get our body, which will basically be a stringification of the payload. And then let's call mux from libmux.
I will separate that import here to verify this headers and everything. So mux.webhooks.verifySignature, pass in the stringified body, select mux-signature here to be mux signature, which we get from the headers. Make sure you don't add any typos here because anything wrong here can break the verification and pass in the signing secret. That's it. This will throw an error in case something goes wrong.
Now let's do a switch here on payload.type as webhook event type. And in case we have video asset, my apologies, video asset created, In that case what we're going to do is we're going to update the video record in our database with the appropriate upload that has happened. So this is what we're gonna do. Const data will be payload.data as video asset created webhook event and simply target the data. If case we are missing data upload ID, it means we can't really find the relevant video.
I mean, this should never happen, right? If this happens, something went wrong on Mux side. So let's just return new response here, no upload ID found, and we can return back a status 400 because this is not something we can work with. Otherwise, we can go ahead and update the videos schema. So make sure you imported the videos schema here.
And we can set the mux asset ID to be data.id. And we can set the mux status here to be data.status. And we also have to import the database from add slash database my apologies so we are updating these two because we now have them and let's pass in the where to be equals videos max upload id to match data upload id like this and break like this now let's just go ahead and outside of the switch here, let's do a return new response webhook received. Just so we don't throw... Webhooks always expect a success request at the end and if you have too many failures of a web hook they might actually shut down your web hook so always be careful to do that.
So I think we've written this correctly I think this should now work. Basically what we're doing here is the following. When we go inside of our videos procedures, remember, we first create the max upload and we store the upload ID inside of our newly created video entity here. So let's go ahead and demonstrate this now. So I think this should work already.
I think everything should be fine, but you know, you never know. So just make sure you do band run dev all. So you should have your webhook running. So if I go ahead and copy this, and if I go here, I should see my app running. There we go as you can see I can see my app.
Now close this and work in localhost right but you should just confirm that that is working. So what I'm going to do now is the following. I'm going to click create here and now video has been created. Don't close this just yet. If you now go inside of Drizzle Studio here and refresh, make sure you have your Drizzle Kit Studio running as I have Drizzle Kit Studio.
And if you now go inside of videos, you should be able to see a Mux status of waiting, because we are waiting for a video to be uploaded, and we have the Mux upload ID. So once the webhook hits our server, we're going to use that upload ID from Mux and tell our database, hey, I have a video which I prepared this Mux upload ID for. Can you find that video and now give it some new information like mux asset ID and mux status, right? So let's go ahead and try that. So for now, mux asset ID is completely empty, as you can see, as well as, you know, mux track ID, mux track status, everything is basically empty besides mux upload ID.
So what we're expecting to happen is that mux status changes to ready and mux asset ID changes to an actual ID if we've set everything up correctly. So I'm going to go ahead and upload my demo video here. Let's wait for this to upload. This should just say upload complete. And if I now, let's see, in Webhooks Max, can I?
I'm not sure if I can keep track of my events here. I think that's one thing that I'm missing from here. It's not exactly clear. You can't exactly keep track of what's going on. But what we can do is we can look at our terminal and looks like something was definitely hitting our AVI videos webhook and it returned 200, which means that our verification was correct.
Which in turn should mean that if I refresh this, there we go. It still says preparing, so it's still doing something in the video, but the Max Asset ID is right here. Perfect! So this is exactly what we wanted to achieve because now our video is correctly associated with a Mux asset. It's no longer random as to which video we uploaded and which video we have in our database.
Great! So in case this is not working for you, start troubleshooting from your terminal. Are you seeing API videos webhook? If you are seeing them, but there is an error here, you are most likely doing something incorrectly in regards of your webhook verification. So just be careful and go ahead and read it slowly line by line.
In case you want to see the documentation for that you can click on listen for webhooks and you have verify webhook signatures here which will guide you through the same thing that I was telling you about and then they will basically give you a generic pseudocode on how to do this but you can also click on node and they will tell you how you can verify the signature But I think this is the only thing that can actually fail at this stage. So perhaps you are missing the webhook secret. Make sure you didn't misspell this. Make sure that you actually have it in your dot environment right here. Make sure that you didn't accidentally add the wrong one.
But I think it will give you some useful information if an error happens. Great! And furthermore, if you think that the verification is not the issue but you're still getting some errors, you can add some console logs here. So I'm just giving you some guide on how to debug things, how I would go ahead and debug this. Great!
So this seems to be working and I don't think there's any point in us going further with all of these other types. We will of course add them but for now this was the most basic one. Okay, the asset has been created, how can I connect that asset to my video database? Great, we've done that. Now what I want to do is I want to wrap up the tutorial, my apologies, the chapter, not the tutorial, with making this uploader look good.
So let's go ahead and do that. We're now going to focus on the uploader, so feel free to click create and just leave a video here. Let's go back inside of our studio uploader and we should have all of these unused files right here. So this is what we're gonna do. We're gonna go ahead and give our mux uploader here an id because that's how the styles will recognize for what it is.
So I'm gonna call this video uploader like this. I'm gonna give it a class name of hidden and then I'm gonna give it group slash uploader. Basically, I'm going to hide it. It's not gonna be visible because we are now going to build our own. So we need to hide it CSS with CSS, but it still needs to exist.
It still needs to be rendered here. That's why we need the ID of it. And now let's add our mux uploader drop component and let's give it a mux uploader of video uploader. Let's go ahead and do this. How about we add this to a constant?
So const uploaderId id and just add it here and then use the uploaderId in both of these places. There we go. And now let's go ahead and give this a class name of group slash drop. So we can differentiate between different groups and what happens inside. And then we're going to use this like native web components using slots.
So all of this comes from Mux documentation on how to customize the uploader. I think they have customized look and feel here and then you can see that you can customize all of these things using sub components. So basically that's what I researched and now I'm just telling you, I'm showing you my finished result of how I managed to do it. Great, so slot heading will have a class name of flex, flex column, item center and gap of six. Inside of here we're gonna have a div with an upload icon from Lucid React.
So just make sure you have added the upload icon here. I'm just gonna move that here and I think that already we should see the upload icon and we also see the text which says or that comes by default in the heading slot I believe. Let's go ahead and make this upload icon a bit better by giving its wrapping div a class name of flex items center justify center gap of two rounded of full background of muted, height of 32 and width of 32. There we go, already looking better. Now let's give the upload icon itself a class name of size 10, text muted foreground, group, draw, and then you're going to have to open square brackets, add an at sign, and then open square brackets again, active, and then animate bounce.
So this will be a cool effect that when we like drag something, let me try. Okay, I'm not sure how exactly to demonstrate it now, but if you drag something, it's going to start to bounce. But I think that I missed something here. Let's see, group, drop, active. Okay, I'm gonna leave it.
I think it's okay for now. Let's add transition, all and duration 300, like this. We're gonna come back and fix it if it doesn't work. Outside of this div, add a new div with a class name flex, flex column, gap to, and text center. And inside of here, add a paragraph with a class name text small and it's going to say drag and drop video files to upload.
And below that another paragraph, your videos will be private until you publish them, because we will add privacy settings later for the videos. Let's add text extra small and text muted foreground here, like this. And then let's add max uploader file select here, give it a max uploader of uploader ID, and add a button from components UI button here. So make sure you have added this import. Let's close the button inside.
And the button will simply say Select Files. The button will also be a type of button, that's important, and have a class name of RoundedBull. So now you should also have this nice input or you can click Select Files directly here. Great. And now go ahead outside of this div and add a span with a slot of separator, which is basically this, the or text.
And we're just gonna give it a class name of hidden it can even be a self-closing tag and now the or text should disappear. Below that we're gonna add our mux uploader status it's gonna be a self-closing tag it will take the mux uploader status It will take the mux uploader status. It will take the mux uploader to be our uploader ID. And class name will be text small. And below that, we're going to have a mux uploader progress, self-closing tag as well, which will have a mux uploader of uploader id last name of text small.
And type will be percentage. And max uploader progress, again, which will be max uploader, uploader ID, and type will be bar. So I want to display both of them because they look good together. Now let's go ahead and try it out now. So I'm going to click create, video created, and let me go ahead and select this.
And there we go. I think this looks much nicer. We have both of these uploads and at the end we have upload complete here. Great. So yeah, you might have noticed that there is a potential issue that we have, is that if we click create, the video has been created and then if we just close this, that's it.
This video will forever stay without its equivalent record. So I was contemplating which solution do I have for this and I ended up leaving it like this because we will have a status which will basically say waiting. So the user will be able in the future to go inside and just delete that record. We can also create a cron job or a background job, which will recurrently look for videos like that. But I couldn't find a nice way to do it because we cannot exactly know when an asset has been connected to the video which has been uploaded.
What we could do is we could redirect to slash max upload ID and then use that in a route to find the video. That's one solution maybe. Yeah, that's something we can explore later. This is the solution I went in the end with. But yeah, I will explore it once again later on.
Just in case you notice like, hey, I can create as many of this and they will never be connected. Yes, that is an issue, but you're gonna see how it looks like when we actually have some data. It will actually look like something's wrong with the video it will show like a sad face because we couldn't load a thumbnail so it will indicate to the user that they can delete this but perhaps we can think of a solution to automatically handle that or avoid that from happening in the first place. Great but I think that we achieved everything we wanted in here. So let's go ahead and use this.
We created a free account, credit card not required. At least I managed to obtain a 15 second video. I hope you did too. And we created the upload model. So that's all we wanted from this chapter.
We successfully integrated Mux. Great. So, and we also have this cool responsive dialogue and you can check out how that looks. Yeah, we forgot to check that when you click create. There we go.
Looks very, very nice. Perfect. So we have all of that here. And what we're gonna do next is we're gonna explore some more events that we can track. Let's just see what's the error here.
Oh, on success here. Let's add the on success to the mux uploaders before we wrap up. Like this. So just add the onSuccess here. And what we're going to do next is we're going to explore some more of these cases which we can use to upload our, to update our video assets.
And then we will finally have some timelines and things like that to display here for more information. Great, great job.