Validating Video Uploads on the Frontend
I recently worked on a project where we wanted to do client-side validation of video file uploads. The app requires users to upload videos of a specific type (`.mov`) and resolution, and ideally the validation would occur client-side so feedback is instant.
Our first iteration involved checking the filename to infer the file type - that is, if the filename ended in .mov
. This is simple but not thorough, any filename ending in .mov
passes the validation. For validating the video's resolution... well, we had nothing. Only after the video was uploaded could the backend of our application analyze the video and return validation for the video.
The above solution is far from ideal so we continued to explore options. After more experimenting we borrowed a method commonly used on image files to validate both the file type and video resolution on the frontend. There are still some loopholes but it's much more robust than previously.
The method is similar to one commonly used with images and file inputs - create an object URL from the file input value, create an img
element, and set the img
's src
attribute to the object URL.
In the case of a video, we create an object URL from the file input, then create avideo
element, and set the video
's src
attribute to the object URL.
Here's how we did it. The HTML is just a regular input
with type="file"
.
<input type="file" id="video-upload" />
We then attach an event listener to the input
:
const fileUpload = document.getElementById("video-upload");
fileUpload.addEventListener("change", event => {
const file = event.target.files[0];
const videoEl = document.createElement("video");
videoEl.src = window.URL.createObjectURL(file);
})
At this point we have an event listener on the change
event of the input
. When the user selects a file the event listener callback grabs the file (using event.target.files[0]
), creates a new video
element, and sets the src
of the video
element to a DOMString containing a URL representing the file object.
For the real magic we'll rely on two Media events - onloadedmetadata
and onerror
.
The onloadedmetadata
event is fired when the metadata has been loaded - all attributes now contain the information we need. Within the callback for this event we'll retrieve the videoHeight
and videoWidth
of the video
element we created before. These values give the resolution of the video file.
videoEl.onloadedmetadata = event => {
window.URL.revokeObjectURL(videoEl.src);
const { name, type } = file;
const { videoWidth, videoHeight } = videoEl;
console.log(`Filename: ${name} - Type: ${type} - Size: ${videoWidth}px x ${videoHeight}px`);
}
We can also grab the file.type
to access the mime type, but keep in mind this is inferred from the file extension so the result will rarely be different than our previous attempt when parsing the filename. However, there's another method we can use to check if the file is indeed a video file, the onerror
event:
videoEl.onerror = () => {
console.log('Please upload a video file.');
}
This callback is triggered when there's an error loading the video source. In the case of an invalid file type, like an image, the error will be type 4, indicating the video source is not supported.
Here's our full script:
const fileUpload = document.getElementById("video-upload");
fileUpload.addEventListener("change", event => {
const resultEl = document.getElementById("meta");
const file = event.target.files[0];
const videoEl = document.createElement("video");
videoEl.src = window.URL.createObjectURL(file);
// When the video metadata has loaded, check
// the video width/height
videoEl.onloadedmetadata = event => {
window.URL.revokeObjectURL(videoEl.src);
const { name, type } = file;
const { videoWidth, videoHeight } = videoEl;
console.log(`Filename: ${name} - Type: ${type} - Size: ${videoWidth}px x ${videoHeight}px`);
}
// If there's an error, most likely because the file
// is not a video, display an error.
videoEl.onerror = () => {
console.log('Please upload a video file.');
}
})
And a Codepen showing it in action: