使用resumable.js上传大文件(视频)兵转换flv格式
前台代码
1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Video.aspx.cs" Inherits="BPMS.WEB.Video" %> 2 3 <!DOCTYPE html> 4 5 <html xmlns="http://www.w3.org/1999/xhtml"> 6 <head runat="server"> 7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 8 <meta name="renderer" content="webkit" /> 9 <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" /> 10 <title></title> 11 <script src="/Themes/Scripts/jquery-1.8.2.min.js"></script> 12 <link rel="stylesheet" type="text/css" href="/ddUpload/css/style.css" /> 13 <script src="/ddUpload/js/resumable.js" charset="utf-8"></script> 14 </head> 15 <body> 16 17 <div id="frame"> 18 <br /> 19 <span style="color: red; font-size: 12px;" id="spanerr"></span> 20 <div class="resumable-drop" ondragenter="jQuery(this).addClass('resumable-dragover');" ondragend="jQuery(this).removeClass('resumable-dragover');" ondrop="jQuery(this).removeClass('resumable-dragover');"> 21 <a class="resumable-browse" href="#" style="text-decoration: none;">选择视频</a> 22 </div> 23 <div class="resumable-progress"> 24 <table> 25 <tr> 26 <td width="100%"> 27 <div class="progress-container"> 28 <div class="progress-bar"></div> 29 </div> 30 </td> 31 <td class="progress-text" nowrap="nowrap"></td> 32 <td class="progress-pause" nowrap="nowrap"> 33 <a href="#" onclick="r.upload(); return(false);" class="progress-resume-link"> 34 <img src="/ddUpload/css/resume.png" title="Resume upload" /></a> 35 <a href="#" onclick="r.pause(); return(false);" class="progress-pause-link"> 36 <img src="/ddUpload/css/pause.png" title="Pause upload" /></a> 37 </td> 38 </tr> 39 </table> 40 </div> 41 <ul class="resumable-list"></ul> 42 <script> 43 var r = new Resumable({ 44 target: '/ddUpload/savefile.aspx', 45 chunkSize: 1 * 1024 * 1024, 46 simultaneousUploads: 1, 47 testChunks: false, 48 throttleProgressCallbacks: 1, 49 method: "octet" 50 }); 51 // Resumable.js isn't supported, fall back on a different method 52 if (!r.support) { 53 $('.resumable-error').show(); 54 55 } else { 56 // Show a place for dropping/selecting files 57 $('.resumable-drop').show(); 58 r.assignDrop($('.resumable-drop')[0]); 59 r.assignBrowse($('.resumable-browse')[0]); 60 var objName = ""; 61 // Handle file add event 62 r.on('fileAdded', function (file) { 63 file.startchunkindex = 0; // 设置当前文件开始上传的块数 64 // Show progress pabr 65 $('.resumable-progress, .resumable-list').show(); 66 // Show pause, hide resume 67 $('.resumable-progress .progress-resume-link').hide(); 68 $('.resumable-progress .progress-pause-link').show(); 69 // Add the file to the list 70 $('.resumable-list').append('<li class="resumable-file-' + file.uniqueIdentifier + '">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span>'); 71 $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-name').html(file.fileName); 72 objName = file.fileName; 73 // Actually start the upload 74 r.upload(); 75 }); 76 77 r.on('uploadStart', function () { 78 $('.resumable-progress .progress-resume-link').hide(); 79 $('.resumable-progress .progress-pause-link').show(); 80 }); 81 r.on('pause', function () { 82 // Show resume, hide pause 83 $('.resumable-progress .progress-resume-link').show(); 84 $('.resumable-progress .progress-pause-link').hide(); 85 }); 86 r.on('complete', function () { 87 // Hide pause/resume when the upload has completed 88 $('.resumable-progress .progress-resume-link, .resumable-progress .progress-pause-link').hide(); 89 $("#spanjindu").html("文件上传完成正在转码!请耐心等候!"); 90 $.ajax({ 91 //请求的路径 92 url: "/CommonModule/TB_Video/UploadHandler.ashx?action=VideoConvert&FilePath=" + objName, 93 //请求的方法 94 type: "post", 95 dataType: "json", 96 //请求成功时调用 97 success: function (msg) { 98 if (msg["msg"] != "0") { 99 $("#spanjindu").html("转码完成!请保存数据!"); 100 window.location = "/Video.aspx"; 101 } else { 102 $("#spanjindu").html("转码失败!请联系相关人员"); 103 } 104 }, 105 //请求失败时调用 106 error: function (msg) { 107 alert(msg); 108 } 109 }); 110 111 112 }); 113 r.on('fileSuccess', function (file, message) { 114 // Reflect that the file upload has completed 115 $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html('(completed)'); 116 }); 117 r.on('fileError', function (file, message) { 118 // Reflect that the file upload has resulted in error 119 $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html('(file could not be uploaded: ' + message + ')'); 120 }); 121 r.on('fileProgress', function (file) { 122 //alert(file.progress()); 123 // Handle progress for both the file and the overall upload 124 125 $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html(Math.floor(file.progress() * 100) + '%'); 126 $('.progress-bar').css({ width: Math.floor(r.progress() * 100) + '%' }); 127 }); 128 } 129 </script> 130 </div> 131 <div style="padding-top: 50px; padding-left: 200px;"> 132 <span style="color: blue; font-size: 16px" id="spanjindu"></span> 133 <br /> 134 视频列表:<br /> 135 <ul> 136 <asp:Literal ID="Literal1" runat="server"></asp:Literal> 137 </ul> 138 </div> 139 140 </body> 141 </html>
后台.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Data; 4 using System.Linq; 5 using System.Text; 6 using System.Web; 7 using System.Web.UI; 8 using System.Web.UI.WebControls; 9 10 namespace BPMS.WEB 11 { 12 public partial class Video : System.Web.UI.Page 13 { 14 protected void Page_Load(object sender, EventArgs e) 15 { 16 load(); 17 } 18 public void load() 19 { 20 int TotalRecord = 0; int TotalPage = 0; 21 DataTable dt = new BPMS.Business.GetPageTab().GetTab("SELECT * FROM TB_Video" , 1, 100, "ID", "ID", out TotalRecord, out TotalPage); 22 StringBuilder sb = new StringBuilder(); 23 foreach (DataRow row in dt.Rows) 24 { 25 sb.Append("<li style=\"padding:4px 7px 2px 4px\"><a target=\"_blank\" href=\"/play/WebForm1.aspx?path=" + HttpUtility.UrlEncode((row["FilePath"].ToString())) + "\">" + row["FilePath"] + " 观看</a></li>"); 26 } 27 this.Literal1.Text = sb.ToString(); 28 } 29 } 30 }
上传代码
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Web; 6 using System.Web.UI; 7 using System.Web.UI.WebControls; 8 9 namespace Html5Upload 10 { 11 public partial class savefile : System.Web.UI.Page 12 { 13 protected void Page_Load(object sender, EventArgs e) 14 { 15 System.Threading.Thread.Sleep(1000); 16 Response.AddHeader("Content-Type", "application/json"); 17 18 if (Request.QueryString["resumableChunkNumber"] != null) 19 { 20 string filedir = Request.MapPath("/ddUpload/temp/"); 21 string savefile = filedir+ Request.QueryString["resumableChunkNumber"] + ".lzp"; 22 //Request.Files[0].SaveAs(savefile); 23 byte[] data = Request.BinaryRead(Request.ContentLength); 24 using (Stream file = File.Create(savefile)) 25 { 26 file.Write(data, 0, data.Length); 27 file.Close(); 28 } 29 30 if (Request.QueryString["resumableChunkNumber"] == Request.QueryString["resumableTotalChunks"]) 31 { 32 MergeFile(filedir, ".lzp", Request.QueryString["resumableFilename"]); 33 } 34 } 35 36 Response.Write("die('{\"jsonrpc\" : \"2.0\", \"result\" : null, \"id\" : \"id\"}');"); 37 38 } 39 40 /// <summary> 41 /// 要合并的文件夹目录 42 /// </summary> 43 /// <param name="filePath">文件目录</param> 44 /// <param name="Extension">扩展名</param> 45 /// <param name="filename">合并文件名</param> 46 bool MergeFile(string filePath, string Extension,string filename) 47 { 48 bool rBool = false; 49 //获得当前目录下文件夹列表,按文件名排序 50 SortedList<int, string> FileList = new SortedList<int, string>(); 51 DirectoryInfo dirInfo = new DirectoryInfo(filePath); 52 53 foreach (FileSystemInfo var in dirInfo.GetFileSystemInfos()) 54 { 55 if (var.Attributes != FileAttributes.Directory) 56 { 57 if (var.Extension == Extension) 58 { 59 FileList.Add(Convert.ToInt32(var.Name.Replace(Extension, "")), var.Name); 60 } 61 } 62 } 63 64 if (FileList.Count > 0) //存在文件 65 { 66 FileStream outFile = new FileStream(filePath + "\\" + filename, FileMode.OpenOrCreate, FileAccess.Write); 67 using (outFile) 68 { 69 foreach (int item in FileList.Keys) 70 { 71 int data = 0; 72 byte[] buffer = new byte[1024]; 73 74 FileStream inFile = new FileStream(filePath + "\\" + FileList[item], FileMode.OpenOrCreate, FileAccess.Read); 75 using (inFile) 76 { 77 while ((data = inFile.Read(buffer, 0, 1024)) > 0) 78 { 79 outFile.Write(buffer, 0, data); 80 } 81 inFile.Close(); 82 } 83 } 84 outFile.Close(); 85 rBool = true; //合并成功 86 } 87 } 88 89 return rBool; 90 } 91 } 92 }
视频转码代码
1 using DotNet.Utilities; 2 using System; 3 using System.Collections.Generic; 4 using System.IO; 5 using System.Linq; 6 using System.Web; 7 8 namespace BPMS.WEB.CommonModule.TB_Video 9 { 10 /// <summary> 11 /// UploadHandler 的摘要说明 12 /// </summary> 13 public class UploadHandler : IHttpHandler 14 { 15 16 public void ProcessRequest(HttpContext context) 17 { 18 context.Response.ContentType = "text/plain"; 19 context.Response.Buffer = true; 20 context.Response.ExpiresAbsolute = DateTime.Now.AddDays(-1); 21 context.Response.AddHeader("pragma", "no-cache"); 22 context.Response.AddHeader("cache-control", ""); 23 context.Response.CacheControl = "no-cache"; 24 string active = HttpContext.Current.Request["action"]; 25 26 switch (active) 27 { 28 //case "Uploadify": //上传文件 29 // HttpPostedFile file = context.Request.Files["Filedata"]; 30 // string uploadPath = HttpContext.Current.Server.MapPath(@context.Request["folder"]) + "\\"; 31 // if (file != null) 32 // { 33 // string _FileName = file.FileName; 34 // string _FileSize = FileHelper.CountSize(file.ContentLength); 35 // string _Extension = System.IO.Path.GetExtension(file.FileName).ToLower(); 36 37 // if (!Directory.Exists(uploadPath)) 38 // { 39 // Directory.CreateDirectory(uploadPath); 40 // } 41 // //+ _Extension 42 // file.SaveAs(uploadPath + _FileName ); 43 // context.Response.Write("1"); 44 // } 45 // else 46 // { 47 // context.Response.Write("0"); 48 // } 49 // break; 50 //case "Delete"://删除文件 51 // break; 52 //case "download"://下载 53 54 // break; 55 case "VideoConvert": 56 VideoConvert(context); 57 break; 58 default: 59 break; 60 } 61 } 62 63 public void VideoConvert(HttpContext context) 64 { 65 string FilePath = context.Request["FilePath"]; 66 string path1 = "/ddUpload/temp/" + FilePath; 67 string path2Name = FilePath.Substring(0,FilePath.LastIndexOf('.'))+DateTime.Now.ToString("yyyyMMddHHmmss"); 68 string path2 = "/shuchu/"+path2Name+".flv"; 69 BPMS.WEB.Common.VideoConvert con = new Common.VideoConvert(); 70 71 bool isok = false; 72 string houzhui = FilePath.Substring(FilePath.LastIndexOf('.')); 73 if (houzhui.ToLower() == ".flv") 74 { 75 File.Copy(context.Server.MapPath(path1),context.Server.MapPath(path2)); 76 isok = true; 77 } 78 else 79 { 80 isok = con.ConvertFlv(path1, path2); 81 } 82 83 if (isok) 84 { 85 BPMS.Business.TB_Video bll = new Business.TB_Video(); 86 BPMS.Entity.TB_Video entity = new BPMS.Entity.TB_Video(); 87 entity.FilePath = path2Name; 88 //entity.UserID = RequestSession.GetSessionUser().UserId; 89 //entity.UserName = RequestSession.GetSessionUser().UserName; 90 bll.Add(entity) ; 91 context.Response.Write("{\"msg\":\""+path2Name+"\"}"); 92 DeleteSignedFile(context.Server.MapPath("/ddUpload/temp/"), "lzp"); 93 } 94 else 95 { 96 context.Response.Write("{\"msg\":\"0\"}"); 97 } 98 context.Response.End(); 99 100 } 101 102 /// <summary> 103 /// 删除给定路径下的给定后缀名的文件 104 /// </summary> 105 /// <param name="path">传入路径</param> 106 /// <param name="suffixName">传入后缀名</param> 107 /// <returns>删除文件列表</returns> 108 private static string[] DeleteSignedFile(string path, string suffixName) 109 { 110 string deletefiles = ""; //记录删除的文件名称 111 112 DirectoryInfo di = new DirectoryInfo(path); 113 //遍历该路径下的所有文件 114 foreach (FileInfo fi in di.GetFiles()) 115 { 116 string exname = fi.Name.Substring(fi.Name.LastIndexOf(".") + 1);//得到后缀名 117 //判断当前文件后缀名是否与给定后缀名一样 118 if (exname == suffixName) 119 { 120 File.Delete(path + "\\" + fi.Name);//删除当前文件 121 deletefiles += fi.Name + ",";//追加删除文件列表 122 } 123 } 124 125 if (deletefiles != "") 126 deletefiles = deletefiles.Substring(0, deletefiles.Length - 1); 127 return deletefiles.Split(',');//以数组形式返回删除文件列表 128 129 } 130 131 public bool IsReusable 132 { 133 get 134 { 135 return false; 136 } 137 } 138 } 139 }
引用js代码
1 /* 2 * MIT Licensed 3 * http://www.23developer.com/opensource 4 * http://github.com/23/resumable.js 5 * Steffen Tiedemann Christensen, steffen@23company.com 6 */ 7 8 (function () { 9 "use strict"; 10 11 var Resumable = function (opts) { 12 if (!(this instanceof Resumable)) { 13 return new Resumable(opts); 14 } 15 this.version = 1.0; 16 // SUPPORTED BY BROWSER? 17 // Check if these features are support by the browser: 18 // - File object type 19 // - Blob object type 20 // - FileList object type 21 // - slicing files 22 this.support = ( 23 (typeof (File) !== 'undefined') && 24 (typeof (Blob) !== 'undefined') && 25 (typeof (FileList) !== 'undefined') && 26 (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false) 27 ); 28 if (!this.support) { 29 //alert("此上传控件兼容 火狐firefox, 谷歌Google Chrome,IE10 以上等浏览器!你的浏览器不支持此上传!"); 30 document.getElementById('spanerr').innerHTML = "提示:此上传控件兼容 火狐firefox, 谷歌Google Chrome,IE10 以上等浏览器!如果是双核模式浏览器,如360,百度,搜狗等浏览器!可切换成谷歌内核(极速模式)!<img src='/ddUpload/css/err.png'/>"; 31 return (false); 32 } 33 34 35 // PROPERTIES 36 var $ = this; 37 $.files = []; 38 $.defaults = { 39 chunkSize: 1 * 1024 * 1024, 40 forceChunkSize: false, 41 simultaneousUploads: 3, 42 fileParameterName: 'file', 43 throttleProgressCallbacks: 0.5, 44 query: {}, 45 headers: {}, 46 preprocess: null, 47 method: 'multipart', 48 prioritizeFirstAndLastChunk: false, 49 target: '/', 50 testChunks: true, 51 generateUniqueIdentifier: null, 52 maxChunkRetries: undefined, 53 chunkRetryInterval: undefined, 54 permanentErrors: [404, 415, 500, 501], 55 maxFiles:1,//undefined, 56 withCredentials: false, 57 xhrTimeout: 0, 58 maxFilesErrorCallback: function (files, errorCount) { 59 var maxFiles = $.getOpt('maxFiles'); 60 alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); 61 }, 62 minFileSize: 1, 63 minFileSizeErrorCallback: function (file, errorCount) { 64 alert(file.fileName || file.name + ' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); 65 }, 66 maxFileSize: undefined, 67 maxFileSizeErrorCallback: function (file, errorCount) { 68 alert(file.fileName || file.name + ' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); 69 }, 70 fileType: ['mp4', 'flv', 'rm', 'rmvb', 'avi', 'wmv', 'mpg', 'mpeg', '3gp', 'swf', 'asf', 'divx', 'xvid', '3gp2', 'flv1', 'mpeg1', 'mpeg2', 'mpeg3', 'mpeg4', 'h264'], 71 fileTypeErrorCallback: function (file, errorCount) { 72 //alert(file.fileName || file.name + '' + $.getOpt('fileType') + '.'); 73 document.getElementById('spanjindu').innerHTML = "请上传视频文件!"; 74 //$("#spanjindu").html("请上传视频文件!"); 75 } 76 }; 77 $.opts = opts || {}; 78 $.getOpt = function (o) { 79 var $opt = this; 80 // Get multiple option if passed an array 81 if (o instanceof Array) { 82 var options = {}; 83 $h.each(o, function (option) { 84 options[option] = $opt.getOpt(option); 85 }); 86 return options; 87 } 88 // Otherwise, just return a simple option 89 if ($opt instanceof ResumableChunk) { 90 if (typeof $opt.opts[o] !== 'undefined') { 91 return $opt.opts[o]; 92 } else { 93 $opt = $opt.fileObj; 94 } 95 } 96 if ($opt instanceof ResumableFile) { 97 if (typeof $opt.opts[o] !== 'undefined') { 98 return $opt.opts[o]; 99 } else { 100 $opt = $opt.resumableObj; 101 } 102 } 103 if ($opt instanceof Resumable) { 104 if (typeof $opt.opts[o] !== 'undefined') { 105 return $opt.opts[o]; 106 } else { 107 return $opt.defaults[o]; 108 } 109 } 110 }; 111 112 // EVENTS 113 // catchAll(event, ...) 114 // fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file), fileError(file, message), 115 // complete(), progress(), error(message, file), pause() 116 $.events = []; 117 $.on = function (event, callback) { 118 $.events.push(event.toLowerCase(), callback); 119 }; 120 $.fire = function () { 121 // `arguments` is an object, not array, in FF, so: 122 var args = []; 123 for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); 124 // Find event listeners, and support pseudo-event `catchAll` 125 var event = args[0].toLowerCase(); 126 for (var i = 0; i <= $.events.length; i += 2) { 127 if ($.events[i] == event) $.events[i + 1].apply($, args.slice(1)); 128 if ($.events[i] == 'catchall') $.events[i + 1].apply(null, args); 129 } 130 if (event == 'fileerror') $.fire('error', args[2], args[1]); 131 if (event == 'fileprogress') $.fire('progress'); 132 }; 133 134 135 // INTERNAL HELPER METHODS (handy, but ultimately not part of uploading) 136 var $h = { 137 stopEvent: function (e) { 138 e.stopPropagation(); 139 e.preventDefault(); 140 }, 141 each: function (o, callback) { 142 if (typeof (o.length) !== 'undefined') { 143 for (var i = 0; i < o.length; i++) { 144 // Array or FileList 145 if (callback(o[i]) === false) return; 146 } 147 } else { 148 for (i in o) { 149 // Object 150 if (callback(i, o[i]) === false) return; 151 } 152 } 153 }, 154 generateUniqueIdentifier: function (file) { 155 var custom = $.getOpt('generateUniqueIdentifier'); 156 if (typeof custom === 'function') { 157 return custom(file); 158 } 159 var relativePath = file.webkitRelativePath || file.fileName || file.name; // Some confusion in different versions of Firefox 160 var size = file.size; 161 return (size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')); 162 }, 163 contains: function (array, test) { 164 var result = false; 165 166 $h.each(array, function (value) { 167 if (value == test) { 168 result = true; 169 return false; 170 } 171 return true; 172 }); 173 174 return result; 175 }, 176 formatSize: function (size) { 177 if (size < 1024) { 178 return size + ' bytes'; 179 } else if (size < 1024 * 1024) { 180 return (size / 1024.0).toFixed(0) + ' KB'; 181 } else if (size < 1024 * 1024 * 1024) { 182 return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'; 183 } else { 184 return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'; 185 } 186 }, 187 getTarget: function (params) { 188 var target = $.getOpt('target'); 189 if (target.indexOf('?') < 0) { 190 target += '?'; 191 } else { 192 target += '&'; 193 } 194 return target + params.join('&'); 195 } 196 }; 197 198 var onDrop = function (event) { 199 $h.stopEvent(event); 200 appendFilesFromFileList(event.dataTransfer.files, event); 201 }; 202 var onDragOver = function (e) { 203 e.preventDefault(); 204 }; 205 206 // INTERNAL METHODS (both handy and responsible for the heavy load) 207 var appendFilesFromFileList = function (fileList, event) { 208 // check for uploading too many files 209 var errorCount = 0; 210 var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']); 211 if (typeof (o.maxFiles) !== 'undefined' && o.maxFiles < (fileList.length + $.files.length)) { 212 // if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file 213 if (o.maxFiles === 1 && $.files.length === 1 && fileList.length === 1) { 214 $.removeFile($.files[0]); 215 } else { 216 o.maxFilesErrorCallback(fileList, errorCount++); 217 return false; 218 } 219 } 220 var files = []; 221 $h.each(fileList, function (file) { 222 var fileName = file.name.split('.'); 223 var fileType = fileName[fileName.length - 1].toLowerCase(); 224 225 if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) { 226 o.fileTypeErrorCallback(file, errorCount++); 227 return false; 228 } 229 230 if (typeof (o.minFileSize) !== 'undefined' && file.size < o.minFileSize) { 231 o.minFileSizeErrorCallback(file, errorCount++); 232 return false; 233 } 234 if (typeof (o.maxFileSize) !== 'undefined' && file.size > o.maxFileSize) { 235 o.maxFileSizeErrorCallback(file, errorCount++); 236 return false; 237 } 238 239 // directories have size == 0 240 if (!$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) { 241 (function () { 242 var f = new ResumableFile($, file); 243 window.setTimeout(function () { 244 $.files.push(f); 245 files.push(f); 246 f.container = (typeof event != 'undefined' ? event.srcElement : null); 247 $.fire('fileAdded', f, event) 248 }, 0); 249 })() 250 }; 251 }); 252 window.setTimeout(function () { 253 $.fire('filesAdded', files) 254 }, 0); 255 }; 256 257 // INTERNAL OBJECT TYPES 258 function ResumableFile(resumableObj, file) { 259 var $ = this; 260 $.opts = {}; 261 $.getOpt = resumableObj.getOpt; 262 $._prevProgress = 0; 263 $.resumableObj = resumableObj; 264 $.file = file; 265 $.fileName = file.fileName || file.name; // Some confusion in different versions of Firefox 266 $.size = file.size; 267 $.relativePath = file.webkitRelativePath || $.fileName; 268 $.uniqueIdentifier = $h.generateUniqueIdentifier(file); 269 $._pause = false; 270 $.container = ''; 271 $.startchunkindex = 0; 272 273 var _error = false; 274 275 // Callback when something happens within the chunk 276 var chunkEvent = function (event, message) { 277 // event can be 'progress', 'success', 'error' or 'retry' 278 switch (event) { 279 case 'progress': 280 $.resumableObj.fire('fileProgress', $); 281 break; 282 case 'error': 283 $.abort(); 284 _error = true; 285 $.chunks = []; 286 $.resumableObj.fire('fileError', $, message); 287 break; 288 case 'success': 289 if (_error) return; 290 $.resumableObj.fire('fileProgress', $); // it's at least progress 291 if ($.isComplete()) { 292 $.resumableObj.fire('fileSuccess', $, message); 293 } 294 break; 295 case 'retry': 296 $.resumableObj.fire('fileRetry', $); 297 break; 298 } 299 }; 300 301 // Main code to set up a file object with chunks, 302 // packaged to be able to handle retries if needed. 303 $.chunks = []; 304 $.abort = function () { 305 // Stop current uploads 306 var abortCount = 0; 307 $h.each($.chunks, function (c) { 308 if (c.status() == 'uploading') { 309 c.abort(); 310 abortCount++; 311 } 312 }); 313 if (abortCount > 0) $.resumableObj.fire('fileProgress', $); 314 }; 315 $.cancel = function () { 316 // Reset this file to be void 317 var _chunks = $.chunks; 318 $.chunks = []; 319 // Stop current uploads 320 $h.each(_chunks, function (c) { 321 if (c.status() == 'uploading') { 322 c.abort(); 323 $.resumableObj.uploadNextChunk(); 324 } 325 }); 326 $.resumableObj.removeFile($); 327 $.resumableObj.fire('fileProgress', $); 328 }; 329 $.retry = function () { 330 $.bootstrap(); 331 var firedRetry = false; 332 $.resumableObj.on('chunkingComplete', function () { 333 if (!firedRetry) $.resumableObj.upload(); 334 firedRetry = true; 335 }); 336 }; 337 $.bootstrap = function () { 338 $.abort(); 339 _error = false; 340 // Rebuild stack of chunks from file 341 $.chunks = []; 342 $._prevProgress = 0; 343 var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; 344 var maxOffset = Math.max(round($.file.size / $.getOpt('chunkSize')), 1); 345 for (var offset = 0; offset < maxOffset; offset++) { 346 (function (offset) { 347 window.setTimeout(function () { 348 $.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent)); 349 $.resumableObj.fire('chunkingProgress', $, offset / maxOffset); 350 }, 0); 351 })(offset) 352 } 353 window.setTimeout(function () { 354 $.resumableObj.fire('chunkingComplete', $); 355 }, 0); 356 }; 357 $.progress = function () { 358 if (_error) return (1); 359 // Sum up progress across everything 360 var ret = 0; 361 var error = false; 362 $h.each($.chunks, function (c) { 363 if (c.status($.startchunkindex) == 'error') error = true; 364 ret += c.progress(true, $.startchunkindex); // get chunk progress relative to entire file 365 //console.info(c.progress(true,$.startchunkindex)); 366 }); 367 ret = (error ? 1 : (ret > 0.999 ? 1 : ret)); 368 ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused 369 $._prevProgress = ret; 370 return (ret); 371 }; 372 $.isUploading = function () { 373 var uploading = false; 374 $h.each($.chunks, function (chunk) { 375 if (chunk.status() == 'uploading') { 376 uploading = true; 377 return (false); 378 } 379 }); 380 return (uploading); 381 }; 382 $.isComplete = function () { 383 var outstanding = false; 384 $h.each($.chunks, function (chunk) { 385 var status = chunk.status(); 386 if (status == 'pending' || status == 'uploading' || chunk.preprocessState === 1) { 387 outstanding = true; 388 return (false); 389 } 390 }); 391 return (!outstanding); 392 }; 393 $.pause = function (pause) { 394 if (typeof (pause) === 'undefined') { 395 $._pause = ($._pause ? false : true); 396 } else { 397 $._pause = pause; 398 } 399 }; 400 $.isPaused = function () { 401 return $._pause; 402 }; 403 404 405 // Bootstrap and return 406 $.resumableObj.fire('chunkingStart', $); 407 $.bootstrap(); 408 return (this); 409 } 410 411 function ResumableChunk(resumableObj, fileObj, offset, callback) { 412 var $ = this; 413 $.opts = {}; 414 $.getOpt = resumableObj.getOpt; 415 $.resumableObj = resumableObj; 416 $.fileObj = fileObj; 417 $.fileObjSize = fileObj.size; 418 $.fileObjType = fileObj.file.type; 419 $.offset = offset; 420 $.callback = callback; 421 $.lastProgressCallback = (new Date); 422 $.tested = false; 423 $.retries = 0; 424 $.pendingRetry = false; 425 $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished 426 427 // Computed properties 428 var chunkSize = $.getOpt('chunkSize'); 429 $.loaded = 0; 430 $.startByte = $.offset * chunkSize; 431 $.endByte = Math.min($.fileObjSize, ($.offset + 1) * chunkSize); 432 if ($.fileObjSize - $.endByte < chunkSize && !$.getOpt('forceChunkSize')) { 433 // The last chunk will be bigger than the chunk size, but less than 2*chunkSize 434 $.endByte = $.fileObjSize; 435 } 436 $.xhr = null; 437 438 // test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session 439 $.test = function () { 440 // Set up request and listen for event 441 $.xhr = new XMLHttpRequest(); 442 443 var testHandler = function (e) { 444 $.tested = true; 445 var status = $.status(); 446 if (status == 'success') { 447 $.callback(status, $.message()); 448 $.resumableObj.uploadNextChunk(); 449 } else { 450 $.send(); 451 } 452 }; 453 $.xhr.addEventListener('load', testHandler, false); 454 $.xhr.addEventListener('error', testHandler, false); 455 456 // Add data from the query options 457 var params = []; 458 var customQuery = $.getOpt('query'); 459 if (typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); 460 $h.each(customQuery, function (k, v) { 461 params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); 462 }); 463 // Add extra data to identify chunk 464 params.push(['resumableChunkNumber', encodeURIComponent($.offset + 1)].join('=')); 465 params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); 466 params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); 467 params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); 468 params.push(['resumableType', encodeURIComponent($.fileObjType)].join('=')); 469 params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); 470 params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); 471 params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); 472 params.push(['resumableTotalChunks', encodeURIComponent($.fileObj.chunks.length)].join('=')); 473 // Append the relevant chunk and send it 474 $.xhr.open('GET', $h.getTarget(params)); 475 $.xhr.timeout = $.getOpt('xhrTimeout'); 476 $.xhr.withCredentials = $.getOpt('withCredentials'); 477 // Add data from header options 478 $h.each($.getOpt('headers'), function (k, v) { 479 $.xhr.setRequestHeader(k, v); 480 }); 481 $.xhr.send(null); 482 }; 483 484 $.preprocessFinished = function () { 485 $.preprocessState = 2; 486 $.send(); 487 }; 488 489 // send() uploads the actual data in a POST call 490 $.send = function () { 491 var preprocess = $.getOpt('preprocess'); 492 if (typeof preprocess === 'function') { 493 switch ($.preprocessState) { 494 case 0: 495 preprocess($); 496 $.preprocessState = 1; 497 return; 498 case 1: 499 return; 500 case 2: 501 break; 502 } 503 } 504 if ($.getOpt('testChunks') && !$.tested) { 505 $.test(); 506 return; 507 } 508 509 // Set up request and listen for event 510 $.xhr = new XMLHttpRequest(); 511 512 // Progress 513 $.xhr.upload.addEventListener('progress', function (e) { 514 if ((new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000) { 515 $.callback('progress'); 516 $.lastProgressCallback = (new Date); 517 } 518 $.loaded = e.loaded || 0; 519 }, false); 520 $.loaded = 0; 521 $.pendingRetry = false; 522 $.callback('progress'); 523 524 // Done (either done, failed or retry) 525 var doneHandler = function (e) { 526 var status = $.status(); 527 if (status == 'success' || status == 'error') { 528 $.callback(status, $.message()); 529 $.resumableObj.uploadNextChunk(); 530 } else { 531 $.callback('retry', $.message()); 532 $.abort(); 533 $.retries++; 534 var retryInterval = $.getOpt('chunkRetryInterval'); 535 if (retryInterval !== undefined) { 536 $.pendingRetry = true; 537 setTimeout($.send, retryInterval); 538 } else { 539 $.send(); 540 } 541 } 542 }; 543 $.xhr.addEventListener('load', doneHandler, false); 544 $.xhr.addEventListener('error', doneHandler, false); 545 546 // Set up the basic query data from Resumable 547 var query = { 548 resumableChunkNumber: $.offset + 1, 549 resumableChunkSize: $.getOpt('chunkSize'), 550 resumableCurrentChunkSize: $.endByte - $.startByte, 551 resumableTotalSize: $.fileObjSize, 552 resumableType: $.fileObjType, 553 resumableIdentifier: $.fileObj.uniqueIdentifier, 554 resumableFilename: $.fileObj.fileName, 555 resumableRelativePath: $.fileObj.relativePath, 556 resumableTotalChunks: $.fileObj.chunks.length 557 }; 558 // Mix in custom data 559 var customQuery = $.getOpt('query'); 560 if (typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); 561 $h.each(customQuery, function (k, v) { 562 query[k] = v; 563 }); 564 565 var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))), 566 bytes = $.fileObj.file[func]($.startByte, $.endByte), 567 data = null, 568 target = $.getOpt('target'); 569 570 if ($.getOpt('method') === 'octet') { 571 // Add data from the query options 572 data = bytes; 573 var params = []; 574 $h.each(query, function (k, v) { 575 params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); 576 }); 577 target = $h.getTarget(params); 578 } else { 579 // Add data from the query options 580 data = new FormData(); 581 $h.each(query, function (k, v) { 582 data.append(k, v); 583 }); 584 data.append($.getOpt('fileParameterName'), bytes); 585 } 586 587 $.xhr.open('POST', target); 588 $.xhr.timeout = $.getOpt('xhrTimeout'); 589 $.xhr.withCredentials = $.getOpt('withCredentials'); 590 // Add data from header options 591 $h.each($.getOpt('headers'), function (k, v) { 592 $.xhr.setRequestHeader(k, v); 593 }); 594 $.xhr.send(data); 595 }; 596 $.abort = function () { 597 // Abort and reset 598 if ($.xhr) $.xhr.abort(); 599 $.xhr = null; 600 }; 601 $.status = function (startchunkindex) { 602 603 if ($.offset < startchunkindex) { 604 // console.info($.offset+'success'); 605 return ('success'); 606 607 } 608 609 610 // Returns: 'pending', 'uploading', 'success', 'error' 611 if ($.pendingRetry) { 612 // if pending retry then that's effectively the same as actively uploading, 613 // there might just be a slight delay before the retry starts 614 return ('uploading'); 615 } else if (!$.xhr) { 616 return ('pending'); 617 } else if ($.xhr.readyState < 4) { 618 // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening 619 return ('uploading'); 620 } else { 621 622 623 if ($.xhr.status == 200) { 624 // HTTP 200, perfect 625 return ('success'); 626 } else if ($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { 627 // HTTP 415/500/501, permanent error 628 return ('error'); 629 } else { 630 // this should never happen, but we'll reset and queue a retry 631 // a likely case for this would be 503 service unavailable 632 $.abort(); 633 return ('pending'); 634 } 635 } 636 }; 637 $.message = function () { 638 return ($.xhr ? $.xhr.responseText : ''); 639 }; 640 $.progress = function (relative, startchunkindex) { 641 if (typeof (relative) === 'undefined') relative = false; 642 var factor = (relative ? ($.endByte - $.startByte) / $.fileObjSize : 1); 643 if ($.pendingRetry) return (0); 644 var s = $.status(startchunkindex); 645 switch (s) { 646 case 'success': 647 case 'error': 648 return (1 * factor); 649 case 'pending': 650 return (0 * factor); 651 default: 652 return ($.loaded / ($.endByte - $.startByte) * factor); 653 } 654 }; 655 return (this); 656 } 657 658 // QUEUE 659 $.uploadNextChunk = function () { 660 var found = false; 661 662 // In some cases (such as videos) it's really handy to upload the first 663 // and last chunk of a file quickly; this let's the server check the file's 664 // metadata and determine if there's even a point in continuing. 665 if ($.getOpt('prioritizeFirstAndLastChunk')) { 666 $h.each($.files, function (file) { 667 if (file.chunks.length && file.chunks[0].status() == 'pending' && file.chunks[0].preprocessState === 0) { 668 file.chunks[0].send(); 669 found = true; 670 return (false); 671 } 672 if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() == 'pending' && file.chunks[file.chunks.length - 1].preprocessState === 0) { 673 file.chunks[file.chunks.length - 1].send(); 674 found = true; 675 return (false); 676 } 677 }); 678 if (found) return (true); 679 } 680 681 // Now, simply look for the next, best thing to upload 682 $h.each($.files, function (file) { 683 if (file.isPaused() === false) { 684 // $h.each(file.chunks, function(chunk){ 685 // if(chunk.status()=='pending' && chunk.preprocessState === 0) { 686 // chunk.send(); 687 // found = true; 688 // return(false); 689 // } 690 // }); 691 for (var i = file.startchunkindex; i < file.chunks.length; i++) { 692 693 if (file.chunks[i].status() == 'pending' && file.chunks[i].preprocessState === 0) { 694 file.chunks[i].send(); 695 found = true; 696 return (false); 697 } 698 699 }; 700 701 } 702 if (found) return (false); 703 }); 704 if (found) return (true); 705 706 // The are no more outstanding chunks to upload, check is everything is done 707 var outstanding = false; 708 $h.each($.files, function (file) { 709 if (!file.isComplete()) { 710 outstanding = true; 711 return (false); 712 } 713 }); 714 if (!outstanding) { 715 // All chunks have been uploaded, complete 716 $.fire('complete'); 717 } 718 return (false); 719 }; 720 721 722 // PUBLIC METHODS FOR RESUMABLE.JS 723 $.assignBrowse = function (domNodes, isDirectory) { 724 if (typeof (domNodes.length) == 'undefined') domNodes = [domNodes]; 725 726 $h.each(domNodes, function (domNode) { 727 var input; 728 if (domNode.tagName === 'INPUT' && domNode.type === 'file') { 729 input = domNode; 730 } else { 731 input = document.createElement('input'); 732 input.setAttribute('type', 'file'); 733 input.style.display = 'none'; 734 domNode.addEventListener('click', function () { 735 input.style.opacity = 0; 736 input.style.display = 'block'; 737 input.focus(); 738 input.click(); 739 input.style.display = 'none'; 740 }, false); 741 domNode.appendChild(input); 742 } 743 var maxFiles = $.getOpt('maxFiles'); 744 if (typeof (maxFiles) === 'undefined' || maxFiles != 1) { 745 input.setAttribute('multiple', 'multiple'); 746 } else { 747 input.removeAttribute('multiple'); 748 } 749 if (isDirectory) { 750 input.setAttribute('webkitdirectory', 'webkitdirectory'); 751 } else { 752 input.removeAttribute('webkitdirectory'); 753 } 754 // When new files are added, simply append them to the overall list 755 input.addEventListener('change', function (e) { 756 appendFilesFromFileList(e.target.files, e); 757 e.target.value = ''; 758 }, false); 759 }); 760 }; 761 $.assignDrop = function (domNodes) { 762 if (typeof (domNodes.length) == 'undefined') domNodes = [domNodes]; 763 764 $h.each(domNodes, function (domNode) { 765 domNode.addEventListener('dragover', onDragOver, false); 766 domNode.addEventListener('drop', onDrop, false); 767 }); 768 }; 769 $.unAssignDrop = function (domNodes) { 770 if (typeof (domNodes.length) == 'undefined') domNodes = [domNodes]; 771 772 $h.each(domNodes, function (domNode) { 773 domNode.removeEventListener('dragover', onDragOver); 774 domNode.removeEventListener('drop', onDrop); 775 }); 776 }; 777 $.isUploading = function () { 778 var uploading = false; 779 $h.each($.files, function (file) { 780 if (file.isUploading()) { 781 uploading = true; 782 return (false); 783 } 784 }); 785 return (uploading); 786 }; 787 $.upload = function () { 788 // Make sure we don't start too many uploads at once 789 if ($.isUploading()) return; 790 // Kick off the queue 791 $.fire('uploadStart'); 792 for (var num = 1; num <= $.getOpt('simultaneousUploads') ; num++) { 793 $.uploadNextChunk(); 794 } 795 }; 796 $.pause = function () { 797 // Resume all chunks currently being uploaded 798 $h.each($.files, function (file) { 799 file.abort(); 800 }); 801 $.fire('pause'); 802 }; 803 $.cancel = function () { 804 for (var i = $.files.length - 1; i >= 0; i--) { 805 $.files[i].cancel(); 806 } 807 $.fire('cancel'); 808 }; 809 $.progress = function () { 810 var totalDone = 0; 811 var totalSize = 0; 812 // Resume all chunks currently being uploaded 813 $h.each($.files, function (file) { 814 totalDone += file.progress() * file.size; 815 totalSize += file.size; 816 }); 817 return (totalSize > 0 ? totalDone / totalSize : 0); 818 }; 819 $.addFile = function (file, event) { 820 appendFilesFromFileList([file], event); 821 }; 822 $.removeFile = function (file) { 823 for (var i = $.files.length - 1; i >= 0; i--) { 824 if ($.files[i] === file) { 825 $.files.splice(i, 1); 826 } 827 } 828 }; 829 $.getFromUniqueIdentifier = function (uniqueIdentifier) { 830 var ret = false; 831 $h.each($.files, function (f) { 832 if (f.uniqueIdentifier == uniqueIdentifier) ret = f; 833 }); 834 return (ret); 835 }; 836 $.getSize = function () { 837 var totalSize = 0; 838 $h.each($.files, function (file) { 839 totalSize += file.size; 840 }); 841 return (totalSize); 842 }; 843 844 return (this); 845 }; 846 847 848 // Node.js-style export for Node and Component 849 if (typeof module != 'undefined') { 850 module.exports = Resumable; 851 } else if (typeof define === "function" && define.amd) { 852 // AMD/requirejs: Define the module 853 define(function () { 854 return Resumable; 855 }); 856 } else { 857 // Browser: Expose to window 858 window.Resumable = Resumable; 859 } 860 861 })();
css 代码
1 /* Reset */ 2 body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;} 3 4 /* Baseline */ 5 body, p, h1, h2, h3, h4, h5, h6 {font:normal 12px/1.3em Helvetica, Arial, sans-serif; color:#333; } 6 h1 {font-size:22px; font-weight:bold;} 7 h2 {font-size:19px; font-weight:bold;} 8 h3 {font-size:16px; font-weight:bold;} 9 h4 {font-size:14px; font-weight:bold;} 10 h5 {font-size:12px; font-weight:bold;} 11 p {margin:10px 0;} 12 13 14 /*body {text-align:center; margin:40px;}*/ 15 #frame {margin:0 auto; width:560px; text-align:left;} 16 17 18 19 /* Uploader: Drag & Drop */ 20 .resumable-error {display:none; font-size:14px; font-style:italic;} 21 .resumable-drop {padding:10px; font-size:16px; text-align:left; color:#666; font-weight:bold;background-color:#eee; border:2px dashed #aaa; border-radius:10px; margin-top:20px; z-index:9999; height:20px; width:70px;} 22 .resumable-dragover {padding:30px; color:#555; background-color:#ddd; border:1px solid #999;} 23 24 /* Uploader: Progress bar */ 25 .resumable-progress {margin:30px 0 30px 0; width:100%; display:none;} 26 .progress-container {height:7px; background:#9CBD94; position:relative; } 27 .progress-bar {position:absolute; top:0; left:0; bottom:0; background:#45913A; width:0;} 28 .progress-text {font-size:11px; line-height:9px; padding-left:10px;} 29 .progress-pause {padding:0 0 0 7px;} 30 .progress-resume-link {display:none;} 31 .is-paused .progress-resume-link {display:inline;} 32 .is-paused .progress-pause-link {display:none;} 33 .is-complete .progress-pause {display:none;} 34 35 /* Uploader: List of items being uploaded */ 36 .resumable-list {overflow:auto; margin-right:-20px; display:none;} 37 .uploader-item {width:148px; height:90px; background-color:#666; position:relative; border:2px solid black; float:left; margin:0 6px 6px 0;} 38 .uploader-item-thumbnail {width:100%; height:100%; position:absolute; top:0; left:0;} 39 .uploader-item img.uploader-item-thumbnail {opacity:0;} 40 .uploader-item-creating-thumbnail {padding:0 5px; font-size:9px; color:white;} 41 .uploader-item-title {position:absolute; font-size:9px; line-height:11px; padding:3px 50px 3px 5px; bottom:0; left:0; right:0; color:white; background-color:rgba(0,0,0,0.6); min-height:27px;} 42 .uploader-item-status {position:absolute; bottom:3px; right:3px;} 43 44 /* Uploader: Hover & Active status */ 45 .uploader-item:hover, .is-active .uploader-item {border-color:#4a873c; cursor:pointer; } 46 .uploader-item:hover .uploader-item-title, .is-active .uploader-item .uploader-item-title {background-color:rgba(74,135,60,0.8);} 47 48 /* Uploader: Error status */ 49 .is-error .uploader-item:hover, .is-active.is-error .uploader-item {border-color:#900;} 50 .is-error .uploader-item:hover .uploader-item-title, .is-active.is-error .uploader-item .uploader-item-title {background-color:rgba(153,0,0,0.6);} 51 .is-error .uploader-item-creating-thumbnail {display:none;}
图片 resume.png
图片pause.png