Recently working on a project that integrates rich media functions.Need to upload video.Here I want to make an asynchronous upload,And there is a progress bar,The response has a status code,Video connection,Thumbnail.

Server response

   "thumbnail":"/slsxpt//upload/thumbnail/fdceefc.jpg",   "success":true,   "link":"/slsxpt//upload/video/fdceefc.mp"

And I hope my input file control is not wrapped by the form tag.The reason is that the form cannot be nested in the form. In addition, the form tag still has a little default style in the browser.Maybe we have to write css again.

I used ajaxfileupload to upload files asynchronously before.But this thing has n’t been updated for a long time,There are bugs in the code, although it was barely used successfully in the end,But I always feel bad.And ajaxfileupload does not directly add the progress event response of xhr2,kind of hard.

I looked on the Internet,There are many ways to discover.

For example, after the file is uploaded,Put the upload progress into the session and poll the server session. But I always have a problem with this method,I think this method sees progress,It should be the progress of my server application code (mine is action) copying files from the server's temporary directory,Because all requests should be submitted to the server software first,That is tomcat, tomcat encapsulates objects such as session, request, etc., and the file should actually be received by it.In other words, before my action code executes,The file is actually uploaded.

Later I found a better way to use the ajaxsubmit method of the jquery.form.js plugin. This method submits as a form,That is $.fn.ajaxsubmit .:$(form selector) .ajaxsubmit ({}). The advantage of this api is that it has already processed the progress time of xhr2You can pass an uploadprogress function when you call it, and you can get progress in the function.And if you do n’t want the input file to be wrapped by form,Createelement should work in code.However, this method failed in the end because I made a small mistake.Pity.

ajaxsubmit source

In the end, the $.ajax method was used.$.ajax does not need to be associated with the form, it is a bit like a static method.The only regret is that there is no response to progress in $.ajax options. However, it has a parameter xhr, which means that you can customize xhr, so you can add progress event handlers through xhr.Take a look at the progress event processing in the ajaxsubmit method.Suddenly cheerful

Then I can also add a progress event handler to the $.ajax method.In order to extract the operation on the dom from the upload business,I decided to write it as a plugin.Here is the code for the plugin

;(function ($) {
   var defaults={
       uploadprogress:null,       beforesend:null,       success:null,     },     setting={
   var upload=function ($this) {
     $this.parent (). on ("change", $this, function (event) {
       //var $this=$(event.target),       var formdata=new formdata (),         target=event.target || event.srcelement;
       //$.each(target.files, function (key, value)
       //console.log (key);
       //formdata.append (key, value);
       formdata.append ("file", target.files []);
       settings.filetype&&formdata.append ("filetype", settings.filetype);
       $.ajax ({
         url:$this.data ("url"),         type:"post",         data:formdata,         datatype:"json",         processdata:false,         contenttype:false,         cache:false,         beforesend:function () {
           //console.log("start ");
           if (settings.beforesend) {
             settings.beforesend ();
         },         xhr:function () {
           var xhr=$.ajaxsettings.xhr ();
           if (xhr.upload) {
             xhr.upload.addeventlistener ("progress", function (event) {
               var total=event.total,                 position=event.loaded || event.position,                 percent =;
               if (event.lengthcomputable) {
                 percent=math.ceil (position/total *);
               if (settings.uploadprogress) {
                 settings.uploadprogress (event, position, total, percent);
             }, false);
           return xhr;
         },         success:function (data, status, jxhr) {
           if (settings.success) {
             settings.success (data);
         },         error:function (jxhr, status, error) {
           if (settings.error) {
             settings.error (jxhr, status, error);
   $.fn.uploadfile=function (options) {
     settings=$.extend ((}, defaults, options);
     //File Upload
     return this.each (function () {
       upload ($(this));
 }) ($|| jquery);

Now I can use this API in my jsp page.

   <input type="text" name="resource_url" hidden="hidden" />
     <div role="progressbar"
        aria-valuenow="" aria-valuemin="" aria-valuemax="">
   <input type="file"
      data-url="${baseurl} /upload-video.action"
      data-label="<i></i>select file" />
     (function ($) {
       $(document) .ready (function () {
         var $progress=$(". uploadvideoprogress"),             start=false;
         $("input.uploadinput.uploadvideo"). uploadfile ({
           beforesend:function () {
             $progress.parent (). show ();
           },           uploadprogress:function (event, position, total, percent) {
             $progress.attr ("aria-valuenow", percent);
             $progress.width (percent + "%");
             if (percent>=) {
               $progress.parent (). hide ();
               $progress.attr ("aria-valuenow",);
               $progress.width (+ "%");
           },           success:function (data) {
             if (data.success) {
               settimeout (function () {
                 $("#thumbnail"). attr ("src", data.thumbnail);
     }) (jquery);

Get the picture here after setting a timeout of 800 milliseconds when responding to succes,Because the extraction of the thumbnail is another process, the thumbnail has not yet been extracted when the response may be completed.

Look at the effect

Extracting a thumbnail image

The following part is the server processing uploads,And extract the thumbnail of the video. Below is the processing code for the action.

package org.lyh.app.actions;
 import org.apache.commons.io.fileutils;
 import org.apache.struts.servletactioncontext;
 import org.lyh.app.base.baseaction;
 import org.lyh.library.sitehelpers;
 import org.lyh.library.videoutils;
 import java.io.file;
 import java.io.ioexception;
 import java.security.keystore;
 import java.util.hashmap;
 import java.util.map;
 * created by admin on //.
 * /
 public class uploadaction extends baseaction {
   private string savebasepath;
   private string imagepath;
   private string videopath;
   private string audiopath;
   private string thumbnailpath;
   private file file;
   private string filefilename;
   private string filecontenttype;
   //Omit the setter getter method
   public string video () {
     map<string, object>datajson=new hashmap<string, object>();
     system.out.println (file);
     system.out.println (filefilename);
     system.out.println (filecontenttype);
     string fileextend=filefilename.substring (filefilename.lastindexof ("."));
     string newfilename=sitehelpers.md (filefilename + file.gettotalspace ());
     string typedir="normal";
     string thumbnailname=null, thumbnailfile=null;
     boolean needthumb=false, extractok=false;
     if (filecontenttype.contains ("video")) {
       //extract the thumbnail
       thumbnailname=newfilename + ".jpg";
           = app.getrealpath (savebasepath + thumbnailpath) + "/" + thumbnailname;
     string realpath=app.getrealpath (savebasepath + typedir);
     file savefile=new file (realpath, newfilename + fileextend);
     //A file with the same name exists,jump over
     if (! savefile.exists ()) {
       if (! savefile.getparentfile (). exists ()) {
         savefile.getparentfile (). mkdirs ();
       try {
         fileutils.copyfile (file, savefile);
         if (needthumb) {
           extractok=videoutils.extractthumbnail (savefile, thumbnailfile);
           system.out.println ("Successful extraction of thumbnails:" + extractok);
         datajson.put ("success", true);
       } catch (ioexception e) {
         system.out.println (e.getmessage ());
         datajson.put ("success", false);
     } else {
       datajson.put ("success", true);
     if ((boolean) datajson.get ("success")) {
       datajson.put ("link",           app.getcontextpath () + "/" + savebasepath + typedir + "/" + newfilename + fileextend);
       if (needthumb) {
         datajson.put ("thumbnail",             app.getcontextpath () + "/" + savebasepath + thumbnailpath + "/" + thumbnailname);
     this.responcejson (datajson);
     return none;

action configuration

<action name="upload- *" method="{}">
     <param name="savebasepath">/upload</param>
     <param name="imagepath">/images</param>
     <param name="videopath">/video</param>
     <param name="audiopath">/audio</param>
     <param name="thumbnailpath">/thumbnail</param>

Personally here,If the name of the file is exactly the same as the size,The probability that they are a file is very high,So I use the file name and file size to do md5 calculations. It should be able to avoid the same file uploading.

Use ffmpeg when transcoding. Need to go here to download.

package org.lyh.library;
 import java.io.file;
 import java.io.ioexception;
 import java.io.inputstream;
 import java.util.arraylist;
 import java.util.list;
 * created by admin on //.
 * /
 public class videoutils {
   public static final string ffmpeg_executor="c:/software/ffmpeg.exe";
   public static final int thumbnail_width =;
   public static final int thumbnail_height =;
   public static boolean extractthumbnail (file inputfile, string thumbnailoutput) {
     list<string>command=new arraylist<string>();
     file ffmpegexe=new file (ffmpeg_executor);
     if (! ffmpegexe.exists ()) {
       system.out.println ("The transcoding tool does not exist");
       return false;
     system.out.println (ffmpegexe.getabsolutepath ());
     system.out.println (inputfile.getabsolutepath ());
     command.add (ffmpegexe.getabsolutepath ());
     command.add ("-i");
     command.add (inputfile.getabsolutepath ());
     command.add ("-y");
     command.add ("-f");
     command.add ("image");
     command.add ("-ss");
     command.add ("");
     command.add ("-t");
     command.add (".");
     command.add ("-s");
     command.add (thumbnail_width + "*" + thumbnail_height);
     command.add (thumbnailoutput);
     processbuilder builder=new processbuilder ();
     builder.command (command);
     builder.redirecterrorstream (true);
     try {
       long starttime=system.currenttimemillis ();
       process process=builder.start ();
       system.out.println ("Startup time" + (system.currenttimemillis ()-starttime));
       return true;
     } catch (ioexception e) {
       e.printstacktrace ();
       return false;

In addition, another process is started by java here,In my opinion they should be irrelevant,After java starts ffmpeg.exe, you should come back and continue to execute the following code,Therefore, there is no need to set up a separate thread to extract the thumbnail image.The test also found that it did not take much time.Each long pass takes little time,Below is the time taken to upload the same file twice

the first time

the second time

There is not much difference in terms of user experience.

In addition, uploading larger files here requires some configuration for tomcat and struct

Modify the server.xml file in the conf directory under tomcat and add the attribute maxpostsize="0" to the connector node to indicate that the upload size is not displayed

Modify struts.xml to add configuration,Here the value is in bytes.Here is about 300mb

  • Previous Django error with ajax post data 403 error how to solve
  • Next How to solve PHP form repeated submission