最近在做一個集富媒體功能于一身的項目。需要上傳視頻。這里我希望做成異步上傳,并且有進度條,響應有狀態(tài)碼,視頻連接,縮略圖。
服務端響應
{
"thumbnail": "/slsxpt//upload/thumbnail/fdceefc.jpg",
"success": true,
"link": "/slsxpt//upload/video/fdceefc.mp"
}
并且希望我的input file控件不要被form標簽包裹。原因是form中不能嵌套form,另外form標簽在瀏覽器了還是有一點點默認樣式的,搞不好又要寫css。
以前用ajaxFileUpload做過文件異步上傳。不過這個東西好久未更新,代碼還有bug,雖然最后勉強成功用上了,但總覺不好。而且ajaxFileUpload沒有直接添加xhr2的progress事件響應,比較麻煩。
上網找了一下,發(fā)現方法都是很多。
比如在文件上傳后,將上傳進度放到session中,輪詢服務器session。但我總覺的這個方法有問題,我認為這種方法看到的進度,應該是我的服務端應用程序代碼(我的也就是action)從服務器的臨時目錄復制文件的進度,因為所有請求都應該先提交給服務器軟件,也就是tomcat,tomcat對請求進行封裝session,request等對象,并且文件實際上也應該是它來接收的。也就是說在我的action代碼執(zhí)行之前,文件實際上已經上傳完畢了。
后來找到個比較好的方法使用 jquery.form.js插件的ajaxSubmit方法。這個方法以表單來提交,也就是 $.fn.ajaxSubmit.:$(form selector).ajaxSubmit({}),這個api的好處是它已經對xhr2的progress時間進行了處理,可以在調用時傳遞一個uploadProgress的function,在function里就能夠拿到進度。而且如果不想input file被form包裹也沒關系,在代碼里createElement應該可以。不過這個方法我因為犯了個小錯誤最后沒有成功,可惜了。
ajaxSubmit源碼
最后,還是使用了$.ajax 方法來做。$.ajax 不需要關聯(lián)form,有點像個靜態(tài)方法哦。唯一的遺憾就是$.ajax options里沒有對progress的響應。不過它有一個參數為 xhr ,也就是你可以定制xhr,那么久可以通過xhr添加progress的事件處理程序。再結合看一看ajaxSubmit方法里對progress事件的處理,頓時豁然開朗
那么我也可以在$.ajax 方法中添加progress事件處理函數了。為了把對dom的操作從上傳業(yè)務中抽取出來,我決定以插件的形式寫。下面是插件的代碼
;(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[]);
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 = ;
if(event.lengthComputable){
percent = Math.ceil(position / total * );
}
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-">
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="" aria-valuemin="" aria-valuemax="" style="width: %">
/div>
/div>
input type="file" class="form-control file inline btn btn-primary uploadInput uploadVideo"
accept="video/mp"
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 >= ){
$progress.parent().hide();
$progress.attr('aria-valuenow',);
$progress.width(+'%');
}
},
success : function(data){
if(data.success){
setTimeout(function(){
$('#thumbnail').attr('src',data.thumbnail);
},);
}
}
});
});
})(jQuery);
/script>
/div>
這里在響應succes的時候設置超時800毫秒之后獲取圖片,因為提取縮量圖是另一個進程在做可能響應完成的時候縮略圖還沒提取完成
看下效果
提取縮量圖
下面部分就是服務端處理上傳,并且對視頻提取縮量圖下面是action的處理代碼
package org.lyh.app.actions;
import org.apache.commons.io.FileUtils;
import org.apache.struts.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 //.
*/
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() {
MapString, Object> dataJson = new HashMapString, Object>();
System.out.println(file);
System.out.println(fileFileName);
System.out.println(fileContentType);
String fileExtend = fileFileName.substring(fileFileName.lastIndexOf("."));
String newFileName = SiteHelpers.md(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="{}">
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 //.
*/
public class VideoUtils {
public static final String FFMPEG_EXECUTOR = "C:/Software/ffmpeg.exe";
public static final int THUMBNAIL_WIDTH = ;
public static final int THUMBNAIL_HEIGHT = ;
public static boolean extractThumbnail(File inputFile,String thumbnailOutput){
ListString> command = new ArrayListString>();
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("image");
command.add("-ss");
command.add("");
command.add("-t");
command.add(".");
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之后,應該回來繼續(xù)執(zhí)行下面的代碼,所以并不需要單獨起一個線程去提取縮量圖。測試看也發(fā)現耗時不多。每次長傳耗時也區(qū)別不大,下面是兩次上傳同一個文件耗時
第一次
第二次
就用戶體驗來說沒有很大的區(qū)別。
另外這里上傳較大文件需要對tomcat和struct做點配置
修改tomcat下conf目錄下的server.xml文件,為Connector節(jié)點添加屬性 maxPostSize="0"表示不顯示上傳大小
另外修改 struts.xml添加配置,這里的value單位為字節(jié),這里大概300多mb
您可能感興趣的文章:- ajax實現異步文件或圖片上傳功能
- ajax圖片上傳,圖片異步上傳,更新實例
- Ajax異步文件上傳與NodeJS express服務端處理
- Ajax異步上傳文件實例代碼分享
- ajax實現文件異步上傳并回顯文件相關信息功能示例
- jquery中的ajax異步上傳
- Ajax表單異步上傳文件實例代碼(包括文件域)
- 表單上傳功能實現 ajax文件異步上傳
- ajax異步實現文件分片上傳實例代碼