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:

Comments