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应该可以。不过这个方法我因为犯了个小错误最后没有成功,可惜了。

ajaxSubmit源码

最后,还是使用了$.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                             setTimeout(function(){
34                                 $('#thumbnail').attr('src',data.thumbnail);
35                             },800);
36                         }
37                     }
38                 });
39             });
40         })(jQuery);
41     </script>
42 </div>

这里在响应succes的时候设置超时800毫秒之后获取图片,因为提取缩量图是另一个进程在做可能响应完成的时候缩略图还没提取完成

看下效果

提取缩量图

下面部分就是服务端处理上传,并且对视频提取缩量图下面是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     // 省略setter getter方法
30 
31     public String video() {
32         Map<String, Object> dataJson = new HashMap<String, Object>();
33         System.out.println(file);
34         System.out.println(fileFileName);
35         System.out.println(fileContentType);
36         String fileExtend = fileFileName.substring(fileFileName.lastIndexOf("."));
37         String newFileName = SiteHelpers.md5(fileFileName + file.getTotalSpace());
38         String typeDir = "normal";
39         String thumbnailName = null,thumbnailFile = null;
40         boolean needThumb = false,extractOk = false;
41         if (fileContentType.contains("video")) {
42             typeDir = videoPath;
43             // 提取缩量图
44             needThumb = true;
45             thumbnailName = newFileName + ".jpg";
46             thumbnailFile
47                     = app.getRealPath(saveBasePath + thumbnailPath) + "/" + thumbnailName;
48         }
49         String realPath = app.getRealPath(saveBasePath + typeDir);
50         File saveFile = new File(realPath, newFileName + fileExtend);
51         // 存在同名文件,跳过
52         if (!saveFile.exists()) {
53             if (!saveFile.getParentFile().exists()) {
54                 saveFile.getParentFile().mkdirs();
55             }
56             try {
57                 FileUtils.copyFile(file, saveFile);
58                 if(needThumb){
59                     extractOk = VideoUtils.extractThumbnail(saveFile, thumbnailFile);
60                     System.out.println("提取缩略图成功:"+extractOk);
61                 }
62                 dataJson.put("success", true);
63             } catch (IOException e) {
64                 System.out.println(e.getMessage());
65                 dataJson.put("success", false);
66             }
67         }else{
68             dataJson.put("success", true);
69         }
70         if((Boolean)dataJson.get("success")){
71             dataJson.put("link",
72                     app.getContextPath() + "/" + saveBasePath + typeDir + "/" + newFileName + fileExtend);
73             if(needThumb){
74                 dataJson.put("thumbnail",
75                         app.getContextPath() + "/" + saveBasePath + thumbnailPath + "/" + thumbnailName);
76             }
77         }
78         this.responceJson(dataJson);
79         return NONE;
80     }
81 
82 }

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"/>

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现Ajax上传文件并显示进度条,您可以使用FormData对象和XMLHttpRequest对象。以下是一个简单的示例代码: HTML代码: ``` <form id="file-upload-form"> <input type="file" name="file"> <button type="submit">上传文件</button> </form> <div id="progress-bar"></div> ``` JavaScript代码: ``` var form = document.getElementById('file-upload-form'); var progressBar = document.getElementById('progress-bar'); form.addEventListener('submit', function(e) { e.preventDefault(); var formData = new FormData(this); var xhr = new XMLHttpRequest(); // 监听上传进度 xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { var percentComplete = (e.loaded / e.total) * 100; progressBar.style.width = percentComplete + '%'; } }, false); // 完成上传 xhr.addEventListener('load', function() { progressBar.innerHTML = '上传完成!'; }, false); // 处理上传错误 xhr.addEventListener('error', function() { progressBar.innerHTML = '上传失败。'; }, false); // 发送上传请求 xhr.open('POST', 'upload.php'); xhr.send(formData); }); ``` 在这个示例中,我们首先获取了表单和进度条元素。然后,我们监听了表单的提交事件,阻止了默认的表单提交行为。接着,创建了FormData对象来包含上传的文件数据,并创建了XMLHttpRequest对象。我们给XMLHttpRequest对象添加了一个上传进度事件监听器,当上传进度发生变化时,更新进度条的宽度。接着,我们添加了一个成功上传完成的事件监听器和一个上传错误的监听器。最后,我们打开了一个POST请求,并将FormData对象作为参数发送到服务器。 请注意,这只是一个基本的示例,您需要根据自己的需求进行修改。同时,要确保服务器端也能够处理Ajax上传请求,并返回正确的响应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值