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

服务端响应

 {
"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. Testing和Instrumentation(转)

    Android提供了一系列强大的测试工具,它针对Android的环境,扩展了业内标准的JUnit测试框架.尽管你可以使用JUnit测试Android工程,但Android工具允许你为应用程序的各个方面 ...

  2. 在html使用a标签 直接下载图片 不通过后台实现直接下载

    由于a标签在HTML中链接图片会被识别并打开到网页上 如果想下载这个图片的话 就需要连接到后台读取文件并生成一个头信息下载.不过可以先给a标签加上一个download属性即可直接下载了. <a ...

  3. UIBarButtonItem导航栏添加按钮

    1 前言 UIBarButtonItem为导航栏按钮,在导航栏的左侧和右侧,他们具有许多种不同的形状和形式. 2 代码讲解 ZYViewController.m [plain]  (void)view ...

  4. linux内核分析系列--百度

    http://www.baidu.com/p/frsllzh http://www.baidu.com/p/%E9%98%BF%E4%BF%A1sxq

  5. PHP安全设置

    1.register_globals(全局变量注册开关) 2.magic_quotes_gpc(魔术引号开关) 3.magic_quotes_runtime(魔术引号开关) 4.magic_quote ...

  6. angularjs 根据变量改变 动态加载模板

    directive return { restrict: 'E', replace: true, templateUrl: 'app/view/order.html', link: function ...

  7. Nginx Resource

    Nginx中URL转换成小写首先编译安装nginx_lua_module模块server节: location / { if($uri ~ [A-Z]){ rewrite_by_lua 'return ...

  8. Oracle 特殊字符模糊查询的方法

    最近在写DAO层的时候,遇到一个问题,就是使用like进行模糊查询时,输入下划线,无法精确查到数据,而是返回所有的数据. 这让我很好奇,百度之后才发现,原来是因为有些特殊字符需要进行转义才可以进行查询 ...

  9. MySQL存储过程详解 mysql 存储过程

    原文地址:MySQL存储过程详解  mysql 存储过程作者:王者佳暮 mysql存储过程详解 1.     存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储 ...

  10. width:100% 和 max-width:100%; 有区别吗【转藏】

    这个博客是基于“Pelican+Markdown+定制的my-gum主题”的.定制的主题将博文正文页面的 右边栏去掉,这导致在Firefox等浏览器中,正文中大的图片会突破正文块的宽度,高度也得不到限 ...