Tuesday, March 12, 2019

Upload Video in chunks with preview and progress bar using JavaScript

So, we have 3 following goals here:
  1. Preview video before it is uploaded
  2. Upload video file in small chunks
  3. Display a progress bar
All these tasks are achievable with JS and jQuery without any other libraries.
For choosing a video file we will use the standard input element
  1. <input type="file" id="uploadVideoFile" accept="video/*" />
<input type="file" id="uploadVideoFile" accept="video/*" />
So to access the chosen file we will call
  1. var fileInput = document.getElementById("uploadVideoFile");
var fileInput = document.getElementById("uploadVideoFile");
To display video we need a video element
  1. div id="videoSourceWrapper">
  2. <video style="width: 100%;" controls>
  3. <source id="videoSource"/>
  4. </video>
  5. </div>
div id="videoSourceWrapper">
   <video style="width: 100%;" controls>
      <source id="videoSource"/>
   </video>
</div>
The following snippet displays the video after it is chosen in the video element for preview
  1. $('#uploadVideoFile').on('change',
  2. function() {
  3. var fileInput = document.getElementById("uploadVideoFile");
  4. console.log('Trying to upload the video file: %O', fileInput);
  5. if ('files' in fileInput) {
  6. if (fileInput.files.length === 0) {
  7. alert("Select a file to upload");
  8. } else {
  9. var $source = $('#videoSource');
  10. $source[0].src = URL.createObjectURL(this.files[0]);
  11. $source.parent()[0].load();
  12. $("#videoSourceWrapper").show();
  13. }
  14. } else {
  15. console.log('No found "files" property');
  16. }
  17. }
  18. );
$('#uploadVideoFile').on('change',
    function() {
        var fileInput = document.getElementById("uploadVideoFile");
        console.log('Trying to upload the video file: %O', fileInput);

        if ('files' in fileInput) {
            if (fileInput.files.length === 0) {
                alert("Select a file to upload");
            } else {
                var $source = $('#videoSource');
                $source[0].src = URL.createObjectURL(this.files[0]);
                $source.parent()[0].load();
                $("#videoSourceWrapper").show();
            }
        } else {
            console.log('No found "files" property');
        }
    }
);


As you can see, you can watch any video file having all necessary controls in the video player. Now let's have a look how to upload the video on your server.
For this purpose I have implemented the UploadVideo function
  1. function UploadVideo(file) {
  2. var loaded = 0;
  3. var chunkSize = 500000;
  4. var total = file.size;
  5. var reader = new FileReader();
  6. var slice = file.slice(0, chunkSize);
  7. // Reading a chunk to invoke the 'onload' event
  8. reader.readAsBinaryString(slice);
  9. console.log('Started uploading file "' + file.name + '"');
  10. reader.onload = function (e) {
  11. //Send the sliced chunk to the REST API
  12. $.ajax({
  13. url: "http://api/url/etc",
  14. type: "POST",
  15. data: slice,
  16. processData: false,
  17. contentType: false,
  18. error: (function (errorData) {
  19. console.log(errorData);
  20. alert("Video Upload Failed");
  21. })
  22. }).done(function (e){
  23. loaded += chunkSize;
  24. var percentLoaded = Math.min((loaded / total) * 100, 100);
  25. console.log('Uploaded ' + Math.floor(percentLoaded) + '% of file "' + file.name + '"');
  26. //Read the next chunk and call 'onload' event again
  27. if (loaded <= total) {
  28. slice = file.slice(loaded, loaded + chunkSize);
  29. reader.readAsBinaryString(slice);
  30. } else {
  31. loaded = total;
  32. console.log('File "' + file.name + '" uploaded successfully!');
  33. }
  34. }
  35. }
  36. }
function UploadVideo(file) {
    var loaded = 0;
    var chunkSize = 500000;
    var total = file.size;
    var reader = new FileReader();
    var slice = file.slice(0, chunkSize);
            
    // Reading a chunk to invoke the 'onload' event
    reader.readAsBinaryString(slice); 
    console.log('Started uploading file "' + file.name + '"');
        
    reader.onload = function (e) {
       //Send the sliced chunk to the REST API
        $.ajax({
            url: "http://api/url/etc",
            type: "POST",
            data: slice,
            processData: false,
            contentType: false,
            error: (function (errorData) {
                console.log(errorData);
                alert("Video Upload Failed");
            })
        }).done(function (e){
           loaded += chunkSize;
           var percentLoaded = Math.min((loaded / total) * 100, 100);
           console.log('Uploaded ' + Math.floor(percentLoaded) + '% of file "' + file.name + '"');
      	
           //Read the next chunk and call 'onload' event again
      	   if (loaded <= total) {
      	     slice = file.slice(loaded, loaded + chunkSize);
             reader.readAsBinaryString(slice);
 	   } else { 
  	     loaded = total;
    	     console.log('File "' + file.name + '" uploaded successfully!');
      	   }
        }
    }
}
It does literally the following:
  1. Take the first chunk from the file
  2. Call the API using AJAX
  3. When the call is done take the next chunk
  4. Iterate to step 2
But unfortunately, we cannot use the fake AJAX call here, so let's implement a stub method.
  1. function UploadVideo(file) {
  2. var loaded = 0;
  3. var chunkSize = 500000;
  4. var total = file.size;
  5. var reader = new FileReader();
  6. var slice = file.slice(0, chunkSize);
  7. // Reading a chunk to invoke the 'onload' event
  8. reader.readAsBinaryString(slice);
  9. console.log('Started uploading file "' + file.name + '"');
  10. reader.onload = function (e) {
  11. //Just simulate API
  12. setTimeout(function(){
  13. loaded += chunkSize;
  14. var percentLoaded = Math.min((loaded / total) * 100, 100);
  15. console.log('Uploaded ' + Math.floor(percentLoaded) + '% of file "' + file.name + '"');
  16. //Read the next chunk and call 'onload' event again
  17. if (loaded <= total) {
  18. slice = file.slice(loaded, loaded + chunkSize);
  19. reader.readAsBinaryString(slice);
  20. } else {
  21. loaded = total;
  22. console.log('File "' + file.name + '" uploaded successfully!');
  23. }
  24. }, 250);
  25. }
  26. }
function UploadVideo(file) {
    var loaded = 0;
    var chunkSize = 500000;
    var total = file.size;
    var reader = new FileReader();
    var slice = file.slice(0, chunkSize);
            
    // Reading a chunk to invoke the 'onload' event
    reader.readAsBinaryString(slice); 
    console.log('Started uploading file "' + file.name + '"');
        
    reader.onload = function (e) {
       //Just simulate API
       setTimeout(function(){
    	loaded += chunkSize;
      	var percentLoaded = Math.min((loaded / total) * 100, 100);
      	console.log('Uploaded ' + Math.floor(percentLoaded) + '% of file "' + file.name + '"');

      	//Read the next chunk and call 'onload' event again
      	if (loaded <= total) {
      	  slice = file.slice(loaded, loaded + chunkSize);
          reader.readAsBinaryString(slice);
	} else { 
  	  loaded = total;
    	  console.log('File "' + file.name + '" uploaded successfully!');
      	}
      }, 250);
    }
}
it 'sends' chunks every 250 milliseconds.
Finally, the simpest part is a progress bar. we just add another div beneath the video element
  1. <div id="uploadVideoProgressBar" style="height: 5px; width: 1%; background: #2781e9; margin-top: -5px;"></div>
<div id="uploadVideoProgressBar" style="height: 5px; width: 1%; background: #2781e9; margin-top: -5px;"></div>
and update its width while sending chunks
  1. $('#uploadVideoProgressBar').width(percentLoaded + "%");
$('#uploadVideoProgressBar').width(percentLoaded + "%");


You can try the complete example on JSFiddler:
https://jsfiddle.net/AndrewBuntsev/47dx86sr/

2 comments:

  1. Hi,

    Would you have an example of php code that could handle the backend of this?

    Thanks!

    ReplyDelete
    Replies
    1. Hi,

      Unfortunately, I cannot help you with it, I implemented backend for the function with JavaScript.

      Delete