转载

ajax 异步上传视频带进度条并提取缩略图

最近在做一个集富媒体功能于一身的项目。需要上传视频。这里我希望做成异步上传,并且有进度条,响应有状态码,视频连接,缩略图。

1 { 2     "thumbnail": "/slsxpt//upload/thumbnail/6f05d4985598160c548e6e8f537247c8.jpg", 3     "success": true, 4     "link": "/slsxpt//upload/video/6f05d4985598160c548e6e8f537247c8.mp4" 5 }

并且希望我的input file控件不要被form标签包裹。原因是form中不能嵌套form,另外form标签在浏览器了还是有一点点默认样式的,搞不好又要写css。

以前用ajaxFileUpload做过文件异步上传。不过这个东西好久未更新,代码还有bug,虽然最后勉强成功用上了,但总觉不好。而且ajaxFileUpload没有直接添加xhr2的progress事件响应,比较麻烦。

上网找了一下,发现方法都是很多。

比如在文件上传后,将上传进度放到session中,轮询服务器session。但我总觉的这个方法有问题,我认为这种方法看到的进度,应该是我的服务端应用程序代码(我的也就是action)从服务器的临时目录复制文件的进度,因为所有请求都应该先提交给服务器软件,也就是tomcat,tomcat对请求进行封装session,request等对象,并且文件实际上也应该是它来接收的。也就是说在我的action代码执行之前,文件实际上已经上传完毕了。

后来找到个比较好的方法使用 jquery.form.js插件的ajaxSubmit方法。这个方法以表单来提交,也就是 $.fn.ajaxSubmit.:$(form selector).ajaxSubmit({}),这个api的好处是它已经对xhr2的progress时间进行了处理,可以在调用时传递一个uploadProgress的function,在function里就能够拿到进度。而且如果不想input file被form包裹也没关系,在代码里createElement应该可以。不过这个方法我因为犯了个小错误最后没有成功,可惜了。

最后,还是使用了$.ajax 方法来做。$.ajax 不需要管理form,有点像个静态方法哦。唯一的遗憾就是$.ajax options里没有对progress的响应。不过它有一个参数为 xhr ,也就是你可以指定xhr,那么久可以通过xhr添加progress的事件处理程序。再结合看一看ajaxSubmit方法里对progress事件的处理,顿时豁然开朗

ajax 异步上传视频带进度条并提取缩略图

那么我也可以在$.ajax 方法中添加progress事件处理函数了。为了把对dom的操作从上传业务中抽取出来,我决定以插件的形式写。下面是插件的代码

 1 ;(function ($) {  2     var defaults = {  3             uploadProgress        :    null,  4             beforeSend            :    null,  5             success                :    null,  6         },  7         setting = {  8   9         }; 10  11     var upload = function($this){ 12         $this.parent().on('change',$this,function(event){ 13             //var $this = $(event.target), 14             var    formData = new FormData(), 15                 target = event.target || event.srcElement; 16             //$.each(target.files, function(key, value) 17             //{ 18             //    console.log(key); 19             //    formData.append(key, value); 20             //}); 21             formData.append('file',target.files[0]); 22             settings.fileType && formData.append('fileType',settings.fileType); 23             $.ajax({ 24                 url                :    $this.data('url'), 25                 type            :    "POST", 26                 data            :    formData, 27                 dataType        :    'json', 28                 processData        :    false, 29                 contentType        :    false, 30                 cache            :    false, 31                 beforeSend        :    function(){ 32                     //console.log('start'); 33                     if(settings.beforeSend){ 34                         settings.beforeSend(); 35                     } 36                 }, 37                 xhr                :     function() { 38                     var xhr = $.ajaxSettings.xhr(); 39                     if(xhr.upload){ 40                         xhr.upload.addEventListener('progress',function(event){ 41                             var total = event.total, 42                                 position = event.loaded  || event.position, 43                                 percent = 0; 44                             if(event.lengthComputable){ 45                                 percent = Math.ceil(position / total * 100); 46                             } 47                             if(settings.uploadProgress){ 48                                 settings.uploadProgress(event, position, total, percent); 49                             } 50  51                         }, false); 52                     } 53                     return xhr; 54                 }, 55                 success            :    function(data,status,jXhr){ 56                     if(settings.success){ 57                         settings.success(data); 58                     } 59                 }, 60                 error            :    function(jXhr,status,error){ 61                     if(settings.error){ 62                         settings.error(jXhr,status,error); 63                     } 64                 } 65             }); 66         }); 67  68     }; 69     $.fn.uploadFile = function (options) { 70         settings = $.extend({}, defaults, options); 71         // 文件上传 72         return this.each(function(){ 73             upload($(this)); 74         }); 75  76  77     } 78 })($ || jQuery);

下面就可以在我的jsp页面里面使用这个api了。

 1 <div class="col-sm-5">  2     <input type="text" name="resource_url" id="resource_url" hidden="hidden"/>  3     <div class="progress" style='display: none;'>  4         <div class="progress-bar progress-bar-success uploadVideoProgress" role="progressbar"  5              aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">  6   7         </div>  8     </div>  9     <input type="file" class="form-control file2 inline btn btn-primary uploadInput uploadVideo" 10            accept="video/mp4" 11            data-url="${baseUrl}/upload-video.action" 12            data-label="<i class='glyphicon glyphicon-circle-arrow-up'></i>  选择文件" /> 13     <script> 14         (function($){ 15             $(document).ready(function(){ 16                 var $progress   = $('.uploadVideoProgress'), 17                         start  = false; 18                 $('input.uploadInput.uploadVideo').uploadFile({ 19                     beforeSend      : function(){ 20                         $progress.parent().show(); 21                     }, 22                     uploadProgress  : function(event, position, total, percent){ 23                         $progress.attr('aria-valuenow',percent); 24                         $progress.width(percent+'%'); 25                         if(percent >= 100){ 26                             $progress.parent().hide(); 27                             $progress.attr('aria-valuenow',0); 28                             $progress.width(0+'%'); 29                         } 30                     }, 31                     success         : function(data){ 32                         if(data.success){ 33                             $('#thumbnail').attr('src',data.thumbnail); 34                         } 35                     } 36                 }); 37             }); 38         })(jQuery); 39     </script> 40 </div>

看下效果

ajax 异步上传视频带进度条并提取缩略图

下面部分就是服务端处理上传,并且对视频提取缩量图下面是action的处理代码

 1 package org.lyh.app.actions;  2   3 import org.apache.commons.io.FileUtils;  4 import org.apache.struts2.ServletActionContext;  5 import org.lyh.app.base.BaseAction;  6 import org.lyh.library.SiteHelpers;  7 import org.lyh.library.VideoUtils;  8   9 import java.io.File; 10 import java.io.IOException; 11 import java.security.KeyStore; 12 import java.util.HashMap; 13 import java.util.Map; 14  15 /** 16  * Created by admin on 2015/7/2. 17  */ 18 public class UploadAction extends BaseAction{ 19     private String saveBasePath; 20     private String imagePath; 21     private String videoPath; 22     private String audioPath; 23     private String thumbnailPath; 24  25     private File file; 26     private String fileFileName; 27     private String fileContentType; 28  29     /* 30     // 省略setter getter方法 31     */ 32      33     public String video() { 34         Map<String, Object> dataJson = new HashMap<String, Object>(); 35         System.out.println(file); 36         System.out.println(fileFileName); 37         System.out.println(fileContentType); 38         String fileExtend = fileFileName.substring(fileFileName.lastIndexOf(".")); 39         String newFileName = SiteHelpers.md5(fileFileName + file.getTotalSpace()); 40         String typeDir = "normal"; 41         if (fileContentType.contains("video")) { 42             typeDir = videoPath; 43             // 提取缩量图 44             String thumbnailName = newFileName + ".jpg"; 45             String thumbnailFile 46                     = 47                     ServletActionContext.getServletContext() 48                             .getRealPath(saveBasePath + thumbnailPath) + "/" + thumbnailName; 49             dataJson.put("thumbnail",ServletActionContext.getServletContext() 50                     .getContextPath() + "/" + saveBasePath + thumbnailPath + "/" + thumbnailName); 51             VideoUtils.extractThumbnail(file, thumbnailFile); 52         } 53         String realPath = ServletActionContext.getServletContext().getRealPath(saveBasePath + typeDir); 54         File saveFile = new File(realPath, newFileName + fileExtend); 55         // 存在同名文件,跳过 56         if (!saveFile.exists()) { 57  58             if (!saveFile.getParentFile().exists()) { 59                 saveFile.getParentFile().mkdirs(); 60             } 61             try { 62                 FileUtils.copyFile(file, saveFile); 63                 dataJson.put("success", true); 64             } catch (IOException e) { 65                 System.out.println(e.getMessage()); 66                 dataJson.put("success", false); 67             } 68         }else{ 69             dataJson.put("success",true); 70         } 71         if((Boolean)dataJson.get("success")){ 72             dataJson.put("link", ServletActionContext.getServletContext() 73                     .getContextPath() + "/" + saveBasePath + typeDir + "/" + newFileName + fileExtend); 74         } 75         this.responceJson(dataJson); 76         return NONE; 77     } 78  79 }

action配置

1 <action name="upload-*" class="uploadAction" method="{1}"> 2         <param name="saveBasePath">/upload</param> 3         <param name="imagePath">/images</param> 4         <param name="videoPath">/video</param> 5         <param name="audioPath">/audio</param> 6         <param name="thumbnailPath">/thumbnail</param> 7 </action>

这里个人任务,如果文件的名称跟大小完全一样的话,它们是一个文件的概率就非常大了,所以我这里取文件名跟文件大小做md5运算,应该可以稍微避免下重复上传相同文件了。

转码的时候用到FFmpeg。需要的可以去这里下载。

 1 package org.lyh.library;  2   3 import java.io.File;  4 import java.io.IOException;  5 import java.io.InputStream;  6 import java.util.ArrayList;  7 import java.util.List;  8   9 /** 10  * Created by admin on 2015/7/15. 11  */ 12 public class VideoUtils { 13     public static final String FFMPEG_EXECUTOR = "C:/Software/ffmpeg.exe"; 14     public static final int THUMBNAIL_WIDTH = 400; 15     public static final int THUMBNAIL_HEIGHT = 300; 16  17     public static boolean extractThumbnail(File inputFile,String thumbnailOutput){ 18         List<String> command = new ArrayList<String>(); 19         File ffmpegExe = new File(FFMPEG_EXECUTOR); 20         if(!ffmpegExe.exists()){ 21             System.out.println("转码工具不存在"); 22             return false; 23         } 24  25         System.out.println(ffmpegExe.getAbsolutePath()); 26         System.out.println(inputFile.getAbsolutePath()); 27         command.add(ffmpegExe.getAbsolutePath()); 28         command.add("-i"); 29         command.add(inputFile.getAbsolutePath()); 30         command.add("-y"); 31         command.add("-f"); 32         command.add("image2"); 33         command.add("-ss"); 34         command.add("10"); 35         command.add("-t"); 36         command.add("0.001"); 37         command.add("-s"); 38         command.add(THUMBNAIL_WIDTH+"*"+THUMBNAIL_HEIGHT); 39         command.add(thumbnailOutput); 40  41         ProcessBuilder builder = new ProcessBuilder(); 42         builder.command(command); 43         builder.redirectErrorStream(true); 44         try { 45             long startTime = System.currentTimeMillis(); 46             Process process = builder.start(); 47             System.out.println("启动耗时"+(System.currentTimeMillis()-startTime)); 48             return true; 49         } catch (IOException e) { 50             e.printStackTrace(); 51             return false; 52         } 53     } 54  55 }

另外这里由java启动了另外一个进程,在我看来他们应该是不想干的,java启动了ffmpeg.exe之后,应该回来继续执行下面的代码,所以并不需要单独起一个线程去提取缩量图。测试看也发现耗时不多。每次长传耗时也区别不大,下面是两次上传同一个文件耗时

第一次

ajax 异步上传视频带进度条并提取缩略图

ajax 异步上传视频带进度条并提取缩略图

第二次

ajax 异步上传视频带进度条并提取缩略图

ajax 异步上传视频带进度条并提取缩略图

另外这里上传较大文件需要对tomcat和struct做点配置

修改tomcat下conf目录下的server.xml文件,为Connector节点添加属性 maxPostSize="0"表示不显示上传大小

另外修改 struts.xml添加配置,这里的value单位为字节,这里大概300多mb

<constant name="struts.multipart.maxSize" value="396014978"/>

就用户体验来说没有很大的区别。

正文到此结束
Loading...