码迷,mamicode.com
首页 > Web开发 > 详细

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

时间:2015-07-16 00:29:32      阅读:204      评论:0      收藏:0      [点我收藏+]

标签:

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

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 方法中添加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> &nbsp;选择文件" />
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>

看下效果

技术分享

下面部分就是服务端处理上传,并且对视频提取缩量图下面是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之后,应该回来继续执行下面的代码,所以并不需要单独起一个线程去提取缩量图。测试看也发现耗时不多。每次长传耗时也区别不大,下面是两次上传同一个文件耗时

第一次

技术分享

技术分享

第二次

技术分享

技术分享

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

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

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

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

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

 

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

标签:

原文地址:http://www.cnblogs.com/lvyahui/p/4649754.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!