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

服务端响应

 {
"thumbnail": "/slsxpt//upload/thumbnail/6f05d4985598160c548e6e8f537247c8.jpg",
"success": true,
"link": "/slsxpt//upload/video/6f05d4985598160c548e6e8f537247c8.mp4"
}

并且希望我的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的操作从上传业务中抽取出来,我决定以插件的形式写。下面是插件的代码

 ;(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[0]);
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 = 0;
if(event.lengthComputable){
percent = Math.ceil(position / total * 100);
}
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);
// 文件上传
return this.each(function(){
upload($(this));
}); }
})($ || jQuery);

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

 <div class="col-sm-5">
<input type="text" name="resource_url" id="resource_url" hidden="hidden"/>
<div class="progress" style='display: none;'>
<div class="progress-bar progress-bar-success uploadVideoProgress" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> </div>
</div>
<input type="file" class="form-control file2 inline btn btn-primary uploadInput uploadVideo"
accept="video/mp4"
data-url="${baseUrl}/upload-video.action"
data-label="<i class='glyphicon glyphicon-circle-arrow-up'></i> &nbsp;选择文件" />
<script>
(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 >= 100){
$progress.parent().hide();
$progress.attr('aria-valuenow',0);
$progress.width(0+'%');
}
},
success : function(data){
if(data.success){
setTimeout(function(){
$('#thumbnail').attr('src',data.thumbnail);
},800);
}
}
});
});
})(jQuery);
</script>
</div>

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

看下效果

提取缩量图

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

 package org.lyh.app.actions;

 import org.apache.commons.io.FileUtils;
import org.apache.struts2.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 2015/7/2.
*/
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; // 省略setter getter方法 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.md5(fileFileName + file.getTotalSpace());
String typeDir = "normal";
String thumbnailName = null,thumbnailFile = null;
boolean needThumb = false,extractOk = false;
if (fileContentType.contains("video")) {
typeDir = videoPath;
// 提取缩量图
needThumb = true;
thumbnailName = newFileName + ".jpg";
thumbnailFile
= app.getRealPath(saveBasePath + thumbnailPath) + "/" + thumbnailName;
}
String realPath = app.getRealPath(saveBasePath + typeDir);
File saveFile = new File(realPath, newFileName + fileExtend);
// 存在同名文件,跳过
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("提取缩略图成功:"+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配置

 <action name="upload-*" class="uploadAction" method="{1}">
<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>
</action>

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

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

 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 2015/7/15.
*/
public class VideoUtils {
public static final String FFMPEG_EXECUTOR = "C:/Software/ffmpeg.exe";
public static final int THUMBNAIL_WIDTH = 400;
public static final int THUMBNAIL_HEIGHT = 300; 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("转码工具不存在");
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("image2");
command.add("-ss");
command.add("10");
command.add("-t");
command.add("0.001");
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("启动耗时"+(System.currentTimeMillis()-startTime));
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
} }

另外这里由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 异步上传视频带进度条并提取缩略图的更多相关文章

  1. java进行文件上传,带进度条

    网上看到别人发过的一个java上传的代码,自己写了个完整的,附带源码 项目环境:jkd7.tomcat7. jar包:commons-fileupload-1.2.1.jar.commons-io-1 ...

  2. 【Web】前端文件上传,带进度条

    最近做项目发现,在文件上传的过程中,增加进度条,能大大改善用户体验.本例介绍带进度条的文件上传 环境搭建 参考:[Java]JavaWeb文件上传和下载. 原生ajax上传带进度条 <%@ pa ...

  3. web文件上传,带进度条

    原生ajax上传带进度条 (百分比) <%@ page language="java" contentType="text/html; charset=UTF-8& ...

  4. asp.net mvc 实现上传文件带进度条

    本文乃是博主早期写的,此种思路虽然实现了,但固然不是最好的,仅做参考学习. 可以用js onprogress .fileinput .webuploader.jq ajaxsubmit等实现 思路:a ...

  5. Extjs 使用fileupload插件上传文件 带进度条显示

    一.首先我们看看官方给出的插件的解释: 一个文件上传表单项具有自定义的样式,并且可以控制按钮的文本和 像文本表单的空文本类似的其他特性. 它使用一个隐藏的文件输入元素,并在用户选择文件后 在form提 ...

  6. servlet多文件上传(带进度条)

    需要commons-fileupload-1.3.jar和commons-io-2.4.jar的支持 页面效果:(图片文件都可以) (1)进度标识类 public class UploadStatus ...

  7. FormData上传文件 带进度条

    * jQuery ajax  FormData 上传文件 template $.ajax({ url: url, type: 'POST', data: new FormData(form), dat ...

  8. ASP.NET Jquery+ajax上传文件(带进度条)

    效果图 支持ie6+,chrome,ie6中文文件名会显示乱码. 上传时候会显示进度条. 需要jquery.uploadify.js插件,稍后会给出下载 前台代码 <%@ Page Langua ...

  9. AJAX大文件切割上传以及带进度条。

    分块传输的原理就是利用HTML5新增的文件slice截取函数. 代码如下: html: <input id="f" type="file" name=&q ...

随机推荐

  1. debug.keystore文件不存在解决办法

    重装系统之后,丢失了debug.keystore,找了很久都没有找到,根据网上所讲的只要重新运行一个android项目;就会在avd中生成一个新的debug.keystroe,此法也没解决,索性直接重 ...

  2. [TypeScript] Configuring TypeScript Which Files to Compile with "Files" and "OutDir"

    This lesson shows how to configure the .tsconfig so you only compile the .ts files you want. It then ...

  3. swift Array 数组

    // //  main.Swift //  swift数组 // //  Created by zhangbiao on 14-6-15. //  Copyright (c) 2014年 理想. Al ...

  4. MYSQL参数学习---------------- 张碧池

    http://pottievil.com/category/mysql/mysql%E5%8F%82%E6%95%B0/

  5. Android开发之sharedpreferences 详解

    SharedPreferences简介:   做软件开发应该都知道,很多软件会有配置文件,里面存放这程序运行当中的各个属性值,由于其配置信息并不多,如果采用数据库来存放并不划算,因为数据库连接跟操作等 ...

  6. Android开发之Toast解析

    Toast是Android系统提供的一个显示消息提示的类,它的使用非常简单,用途很广,如软件的升级,可以用它进行提示:退出程序时,也可以用它进行提醒,输入限制的提醒,等等. 使用场景: 1.需要提示用 ...

  7. 微信上传图文消息素材40007,invalid media_id hint

    上传图文消息素材40007,invalid media_id hint,微信40007 >>>>>>>>>>>>>> ...

  8. 11.10 noip模拟试题

    1.第K小数 (number.cpp/c/pas) [问题描述] 有两个正整数数列,元素个数分别为N和M.从两个数列中分别任取一个数 相乘,这样一共可以得到N*M个数,询问这N*M个数中第K小数是多少 ...

  9. 鼠标移入 移出div div会消失的处理

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  10. SQL使用数据库引擎存储过程,系统视图查询,DBA,BI开发人员必备基础知识

    在开发过程中会遇到需要弄清楚这个数据库什么时候建的,这个数据库中有多少表,这个存储过程长的什么样子等等信息,今天把自己工作过程中经常用到的一些数据库引擎存储过程,系统视图等等总结一下以备不时之用.下面 ...