HTML5浏览器实现超大文件上传和断点续传

随着视频网站和大数据应用的兴起,特别是高清视频、4K视频、全景VR和大数据应用的到来,超大文件上传已经成为了日常的应用需求。

基于云计算的架构,我们需要将各地的大数据上传到云平台进行存储和处理,那么,动辄几个GB或者TB的大数据文件如何上传?在网络抖动的情况下如何保障断点续传?如何通过多终端快速上传?这是现实中常常遇到的问题。

为了通用性更强,采用安装特定的浏览器上传插件方式并不可取,最好的实现方式就是通过浏览器直接上传,当前已经有很多基于PHP或者Java程序来实现的上传服务器,然而在实际使用中你会会发现,这些基于脚本语言实现的上传功能模块性能很弱,一是不支持2GB以上的文件上传;二是无法支持断点续传;三是效率极低,单台服务器最多支持几十个并发上传连接。

因此,我们为了解决这个问题,花费了很大精力自主用C++语言实现了这一高性能上传服务器,在客户端只要调用一段 js 代码即可,这是一个通用的上传服务模块,可以快速集成到第三方应用平台中。

项目地址:

https://github.com/liufeihong/Hyper-Upload-Server

 

HTML5浏览器实现超大文件上传和断点续传_第1张图片

 

HTML5浏览器实现超大文件上传和断点续传_第2张图片

Hyper Upload Server 超级上传服务器简介:

这是一款超级文件上传服务器,采用异步I/O架构,采用C++语言编码实现。它支持4GB以上超大文件上传和断点续传,支持Windows和Linux服务器平台,支持任意格式的文件上传,尤其适合大的视频网站应用。单台服务器支持1000并发上传进程,支持PC端和智能手机端主流的浏览器。

主要特性

1. 服务器端采用异步I/O架设设计,具有高性能I/O处理能力,尤其适用于超大文件上传;

2. 服务器端采用高效内存分配技术确保在运行过程中服务器的内存开销最小化;

3. 完全采用标准协议实现,因此兼容几乎所有的PC端和移动端浏览器;

4. 服务器端采用C++语言自主实现,对上传文件的尺寸无限制,天生支持超大文件上传。

   而基于PHP、JAVA等技术实现的文件上传服务天生无法支持超大文件上传,无法逾越2GB的最大文件尺寸瓶颈;

5. 服务器端采用无缓冲即时写入方式,上传数据写入一步到位。不同于PHP、JAVA等技术实现方式需要两步写入;

6. 服务器端可跨平台编译运行,支持Windows和Linux平台;

7. 高性能,单台服务器支持1000个并发上传进程;

8. 支持4GB以上超大文件上传,文件大小不受限制;

9. 客户端支持采用HTTP标准协议上传;

10.支持断点续传,断网、关机重启均不受影响;

11.支持HTML5浏览器上传进度实时显示;

12.支持IE8及以上浏览器上传进度显示;

13.支持查看客户端在线连接, 查看方法: http://ip:port/lists

14.多浏览器兼容,包括Chrome,Firefox,Safari,IE,Opera,Edge;

 上传代码实现

;
/*--jquery.md5.js begin--*/
(function($){

var rotateLeft = function(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
}

var addUnsigned = function(lX, lY) {
    var lX4, lY4, lX8, lY8, lResult;
    lX8 = (lX & 0x80000000);
    lY8 = (lY & 0x80000000);
    lX4 = (lX & 0x40000000);
    lY4 = (lY & 0x40000000);
    lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
    if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
    if (lX4 | lY4) {
    if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
    else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
    } else {
    return (lResult ^ lX8 ^ lY8);
    }
}

var F = function(x, y, z) {
    return (x & y) | ((~ x) & z);
}

var G = function(x, y, z) {
    return (x & z) | (y & (~ z));
}

var H = function(x, y, z) {
    return (x ^ y ^ z);
}

var I = function(x, y, z) {
    return (y ^ (x | (~ z)));
}

var FF = function(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
};

var GG = function(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
};

var HH = function(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
};

var II = function(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
};

var convertToWordArray = function(string) {
    var lWordCount;
    var lMessageLength = string.length;
    var lNumberOfWordsTempOne = lMessageLength + 8;
    var lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
    var lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
    var lWordArray = Array(lNumberOfWords - 1);
    var lBytePosition = 0;
    var lByteCount = 0;
    while (lByteCount < lMessageLength) {
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
        lByteCount++;
    }
    lWordCount = (lByteCount - (lByteCount % 4)) / 4;
    lBytePosition = (lByteCount % 4) * 8;
    lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
    lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
    lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
    return lWordArray;
};

var wordToHex = function(lValue) {
    var WordToHexValue = "", WordToHexValueTemp = "", lByte, lCount;
    for (lCount = 0; lCount <= 3; lCount++) {
    lByte = (lValue >>> (lCount * 8)) & 255;
    WordToHexValueTemp = "0" + lByte.toString(16);
    WordToHexValue = WordToHexValue + WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
    }
    return WordToHexValue;
};

var uTF8Encode = function(string) {
    string = string.replace(/\x0d\x0a/g, "\x0a");
    var output = "";
    for (var n = 0; n < string.length; n++) {
    var c = string.charCodeAt(n);
    if (c < 128) {
    output += String.fromCharCode(c);
    } else if ((c > 127) && (c < 2048)) {
    output += String.fromCharCode((c >> 6) | 192);
    output += String.fromCharCode((c & 63) | 128);
    } else {
    output += String.fromCharCode((c >> 12) | 224);
    output += String.fromCharCode(((c >> 6) & 63) | 128);
    output += String.fromCharCode((c & 63) | 128);
    }
    }
    return output;
};

$.extend({
md5: function(string) {
var x = Array();
var k, AA, BB, CC, DD, a, b, c, d;
var S11=7, S12=12, S13=17, S14=22;
var S21=5, S22=9 , S23=14, S24=20;
var S31=4, S32=11, S33=16, S34=23;
var S41=6, S42=10, S43=15, S44=21;
string = uTF8Encode(string);
x = convertToWordArray(string);
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
for (k = 0; k < x.length; k += 16) {
AA = a; BB = b; CC = c; DD = d;
a = FF(a, b, c, d, x[k+0], S11, 0xD76AA478);
d = FF(d, a, b, c, x[k+1], S12, 0xE8C7B756);
c = FF(c, d, a, b, x[k+2], S13, 0x242070DB);
b = FF(b, c, d, a, x[k+3], S14, 0xC1BDCEEE);
a = FF(a, b, c, d, x[k+4], S11, 0xF57C0FAF);
d = FF(d, a, b, c, x[k+5], S12, 0x4787C62A);
c = FF(c, d, a, b, x[k+6], S13, 0xA8304613);
b = FF(b, c, d, a, x[k+7], S14, 0xFD469501);
a = FF(a, b, c, d, x[k+8], S11, 0x698098D8);
d = FF(d, a, b, c, x[k+9], S12, 0x8B44F7AF);
c = FF(c, d, a, b, x[k+10], S13, 0xFFFF5BB1);
b = FF(b, c, d, a, x[k+11], S14, 0x895CD7BE);
a = FF(a, b, c, d, x[k+12], S11, 0x6B901122);
d = FF(d, a, b, c, x[k+13], S12, 0xFD987193);
c = FF(c, d, a, b, x[k+14], S13, 0xA679438E);
b = FF(b, c, d, a, x[k+15], S14, 0x49B40821);
a = GG(a, b, c, d, x[k+1], S21, 0xF61E2562);
d = GG(d, a, b, c, x[k+6], S22, 0xC040B340);
c = GG(c, d, a, b, x[k+11], S23, 0x265E5A51);
b = GG(b, c, d, a, x[k+0], S24, 0xE9B6C7AA);
a = GG(a, b, c, d, x[k+5], S21, 0xD62F105D);
d = GG(d, a, b, c, x[k+10], S22, 0x2441453);
c = GG(c, d, a, b, x[k+15], S23, 0xD8A1E681);
b = GG(b, c, d, a, x[k+4], S24, 0xE7D3FBC8);
a = GG(a, b, c, d, x[k+9], S21, 0x21E1CDE6);
d = GG(d, a, b, c, x[k+14], S22, 0xC33707D6);
c = GG(c, d, a, b, x[k+3], S23, 0xF4D50D87);
b = GG(b, c, d, a, x[k+8], S24, 0x455A14ED);
a = GG(a, b, c, d, x[k+13], S21, 0xA9E3E905);
d = GG(d, a, b, c, x[k+2], S22, 0xFCEFA3F8);
c = GG(c, d, a, b, x[k+7], S23, 0x676F02D9);
b = GG(b, c, d, a, x[k+12], S24, 0x8D2A4C8A);
a = HH(a, b, c, d, x[k+5], S31, 0xFFFA3942);
d = HH(d, a, b, c, x[k+8], S32, 0x8771F681);
c = HH(c, d, a, b, x[k+11], S33, 0x6D9D6122);
b = HH(b, c, d, a, x[k+14], S34, 0xFDE5380C);
a = HH(a, b, c, d, x[k+1], S31, 0xA4BEEA44);
d = HH(d, a, b, c, x[k+4], S32, 0x4BDECFA9);
c = HH(c, d, a, b, x[k+7], S33, 0xF6BB4B60);
b = HH(b, c, d, a, x[k+10], S34, 0xBEBFBC70);
a = HH(a, b, c, d, x[k+13], S31, 0x289B7EC6);
d = HH(d, a, b, c, x[k+0], S32, 0xEAA127FA);
c = HH(c, d, a, b, x[k+3], S33, 0xD4EF3085);
b = HH(b, c, d, a, x[k+6], S34, 0x4881D05);
a = HH(a, b, c, d, x[k+9], S31, 0xD9D4D039);
d = HH(d, a, b, c, x[k+12], S32, 0xE6DB99E5);
c = HH(c, d, a, b, x[k+15], S33, 0x1FA27CF8);
b = HH(b, c, d, a, x[k+2], S34, 0xC4AC5665);
a = II(a, b, c, d, x[k+0], S41, 0xF4292244);
d = II(d, a, b, c, x[k+7], S42, 0x432AFF97);
c = II(c, d, a, b, x[k+14], S43, 0xAB9423A7);
b = II(b, c, d, a, x[k+5], S44, 0xFC93A039);
a = II(a, b, c, d, x[k+12], S41, 0x655B59C3);
d = II(d, a, b, c, x[k+3], S42, 0x8F0CCC92);
c = II(c, d, a, b, x[k+10], S43, 0xFFEFF47D);
b = II(b, c, d, a, x[k+1], S44, 0x85845DD1);
a = II(a, b, c, d, x[k+8], S41, 0x6FA87E4F);
d = II(d, a, b, c, x[k+15], S42, 0xFE2CE6E0);
c = II(c, d, a, b, x[k+6], S43, 0xA3014314);
b = II(b, c, d, a, x[k+13], S44, 0x4E0811A1);
a = II(a, b, c, d, x[k+4], S41, 0xF7537E82);
d = II(d, a, b, c, x[k+11], S42, 0xBD3AF235);
c = II(c, d, a, b, x[k+2], S43, 0x2AD7D2BB);
b = II(b, c, d, a, x[k+9], S44, 0xEB86D391);
a = addUnsigned(a, AA);
b = addUnsigned(b, BB);
c = addUnsigned(c, CC);
d = addUnsigned(d, DD);
}
var tempValue = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
return tempValue.toLowerCase();
}
});
})(jQuery); 
/*--jquery.md5.js end--*/

/*--- HYUpload.js-begin--*/

function toast(msg,duration)
{
    var toastDiv = document.getElementById('hyupload-toast');
    if (msg) {
        toastDiv.style.display = 'block';
        toastDiv.innerHTML = msg;
        duration = parseInt(duration);
        if (!duration) duration = 3000;
        if (!window.uploadtoast) {
            window.clearTimeout(window.uploadtoast);
        }
        window.uploadtoast = window.setTimeout(function() {
            toastDiv.style.display = 'none';
            delete window.uploadtoast;
        },duration);
    }
    else {
        toastDiv.style.display = 'none';
        toastDiv.innerHTML = '';
    }
}

Array.prototype.in_array=function(e){
    var S=String.fromCharCode(2);
    var r=new RegExp(S+e+S);
    return (r.test(S+this.join(S)+S));
};

var HYFileUploader = function (options) {
    var _options = {
        filelist:'hyupload-filelist',
        notice_url:'',
        savepaths:{'video':'.mp4,.mkv,.avi,.wmv','document':'.zip,.rar','image':'.jpg,.jpeg,.png,.gif','audio':'.aac,.mp3'},
        ontaskfinish:function(taskobj) {
        
        },
        ontaskprogress:function(taskobj) {
        
        },
        ontaskprogress:function(progress) {
        
        },
        ontaskstart:function(taskobj) {
            return true;
        }
    };
    
    $.extend(_options, options);
    
    //简单的Cookie帮助函数
    var setCookie = function(cname,cvalue,exdays) {
        var d = new Date();
        d.setTime(d.getTime()+(exdays*24*60*60*1000));
        var expires = "expires="+d.toGMTString();
        document.cookie = cname + "=" + cvalue + "; " + expires;
    };

    var getCookie = function(cname) {
        var name = cname + "=";
        var ca = document.cookie.split(';');
        for(var i=0; i         {
            var c = ca[i].trim();
            if (c.indexOf(name)==0) return c.substring(name.length,c.length);
        }
        return "";
    };
    
    //
    //简单的文件HASH值计算,如果您不是十分考究,应该可以用于产品。
    //由于计算文件HASH值用到了多种数据,因此在HYFileUploader系统范围内发生HASH冲突的可能性应该非常小,应该可以放心使用。
    //获取文件的ID可以用任何算法来实现,只要保证做到同一文件的ID是相同的即可,获取的ID长度不要超过32字节
    //
    var getFileId = function (file) {
        //给浏览器授予一个唯一的ID用于区分不同的浏览器实例
        var clientid = getCookie("HUAYIUPLOAD");
        if (clientid == "") {
            //用一个随机值来做浏览器的ID,将作为文件HASH值的一部分
            var rand = parseInt(Math.random() * 1000);
            var t = (new Date()).getTime();
            clientid =rand+'T'+t;
            
            setCookie("HUAYIUPLOAD",clientid,365);
        }
        
        var info = clientid;
        if (file.lastModified)
            info += file.lastModified;
        if (file.name)
            info += file.name;
        if (file.size)
            info += file.size;
        //https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js
        var fileid = $.md5(info);
        return fileid;
    };
    
    var get_file_mimetype = function(filename) {
        var mimeType;
        var extPos = filename.lastIndexOf('.');
        if (extPos != -1) {
            var extName = filename.substr(extPos).toLowerCase();
            if (extName == '.rm' || extName == '.rmvb') {
                mimeType = 'video/realvideo';
            }
            else if (extName == '.dat') {
                mimeType = 'video/vcd';
            }
            else if (extName == '.ra') {
                mimeType = 'audio/realaudio';
            }
        }
        
        if (!mimeType) mimeType = 'bin/unknown';
        return mimeType;
    };

    //savepaths:{'video':'.mp4,*.mkv,.avi,.wmv','document':'.zip,*.rar','image':'.jpg,.jpeg,.png,.gif','audio':'.aac,.mp3'}
    var get_save_path = function(filename,savepaths) {
        var extPos = filename.lastIndexOf('.');
        if (extPos != -1) {
            var extName = filename.substr(extPos).toLowerCase();
            
            for (path in savepaths) {
                var extList = savepaths[path].split(',');
                if (extList.indexOf(extName) != -1) {
                    return path;
                }
            }
        }
        
        return '';
    }
    
    var bytesToSize = function (bytes) {
        if (bytes === 0) return '0 B';
        if (bytes < 1024) return (bytes + 'B');
        var k = 1024,sizes = ['B','KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        var i = Math.floor(Math.log(bytes) / Math.log(k));
        return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
    }

    var _addFileToList = function (listViewer,fileobj) {
        var fileSources = '';
        var fileid = '';
        var filename = '',filesize = 0;
        var fileitemId = '';
        
        var $listViewer = $(listViewer);
        
        if (fileobj instanceof Array) {
            fileSources = '';
            
            for(var i = 0; i < fileobj.length; i ++) {
                if (filename != '') filename += '|';
                var file = fileobj[i];
                
                filename += file.name;
                filesize += file.size;
                
                fileid = getFileId(file);
                
                fileSources += '';
                
                fileitemId += fileid;
                
                file.fileid = fileid;
            }
            fileSources += '
';
            
            fileitemId = $.md5(fileitemId);
        }
        else {
            var typename = typeof fileobj;
            
            if (typename != 'object') {
                return false;
            }
            
            if (fileobj.size == 0) {
                toast('文件 “'+fileobj.name+'” 长度为0无法上传,忽略此文件。');
                return false;
            }
            fileid = getFileId(fileobj);
            
            filename = fileobj.name;
            filesize = fileobj.size;
            fileSources = '';
            
            fileitemId = fileid;
            
            fileobj.fileid = fileid;
        }
        
        var file_existed = false;
            
        $listViewer.children('div.upload-fileitem').each(function(index,obj) {
            if (obj.id == fileitemId) {
                file_existed = true;
                return false;
            }
        });
        
        if (file_existed) {
            toast('添加的文件或文件组合 “'+filename+'” 已经存在,不重复添加');
            
            return false;
        }
            
        if (!listViewer.uploadOptions) {
            toast('添加的文件 “'+filename+'” 失败,文件列表未绑定上传对象。');
            
            return false;
        }
        
        //移除提示元素
        $listViewer.children('div.fileupload-help').remove();
        $listViewer.children('.notice').remove();
                        
        var server = listViewer.uploadOptions.server;
        var cid = listViewer.uploadOptions.cid;
        var serverid = listViewer.uploadOptions.serverid;
        var userid = listViewer.uploadOptions.userid;
        
        var html = '

';
        html += fileSources;
        html += '';
        html += '
'+filename+'
';
        html += '
长度:';
        html += bytesToSize(filesize);
        html += '
';
        html += '
已传:0
';
        html += '
速率:0
';
        html += '
';
        html += '
';
        html += '
0%
';
        
        var $fileItem = $(html);
        $fileItem.appendTo($listViewer);
        
        //$('#upload-filelistview').mCustomScrollbar('update');
        
        $fileItem.find('a.remove_icon').click(function(evt) {
            evt.preventDefault();
            if (!$(this).hasClass('enabled')) {
                return;
            }
            var $item = $(this).parent();
            if ($item.attr('data-upload')=='finished') {
                toast('文件已经上传完成,不必移除,如要清空队列,请强制刷新浏览器。');
                return;
            }
            //remove_icon 的外层 upload-fileitem
            var fileItemDomObj = $item.get(0);
            
            var fileViewer = $item.get(0).fileViewer;
            if (fileViewer.uploader) {
                var uploader = fileViewer.uploader;
                
                if ($item.attr('data-upload')=='uploading') {
                    if (!uploader.options.forceremove) {
                        if (!confirm('视频文件正在上传,确定要放弃吗?')) {
                            return;
                        }
                    }
                                
                    //再次检查是否还在上传,如果没有启动上传uploader 属性无值
                    
                    if (uploader.curFileitemElement && uploader.curFileitemElement == fileItemDomObj) {
                        //仅仅执行取消上传操作
                        if (uploader.currentFile) {
                            if (uploader.currentXHR && typeof uploader.currentXHR.abort == 'function') {
                                if (uploader.options.forceremove) {
                                    uploader.taskIsForceRemove = true;
                                }
                                
                                uploader.currentXHR.abort();
                                return ;
                            }
                        }
                    }
                }
            }
                        
            if (fileItemDomObj.fileObject) {
                fileItemDomObj.fileObject = null;
                delete fileItemDomObj.fileObject;
            }
            $item.remove();
            
            var $fileitems = $(fileViewer).children('.upload-fileitem');
            if ($fileitems.length == 0) {
                $(fileViewer).html('

暂无上传任务

');
            }
            
            //$('#upload-filelistview').mCustomScrollbar('update');
        });
        
        $fileItem.find('a.edit_icon').click(function(evt) {
            evt.preventDefault();
        });
            
        var fileItemDom = $fileItem.get(0);
        var fileObject = {
            taskid:fileitemId,
            files:fileobj,
            totalfilesize:filesize,
            uploadedsize:0,
            finished:0
        }
        
        fileItemDom.fileObject = fileObject;
        fileItemDom.fileViewer = listViewer;
        
        return fileid;
    };
    //fileInfo 是上传服务器返回的
    var _onUploadedFileFinished = function(fileInfo) {
        var uploader = this;
        
        //fileInfo 对象的值可以用于保存到CMS里,而不仅仅是用于显示
        if (uploader.options.show_output && uploader.uploadResult) {
            var html = '
';
                html += '
文件名:'+fileInfo.name+'
';
                html += '
保存路径:'+fileInfo.path+'
';
                html += '
文件尺寸:'+fileInfo.filesize+'
';
                html += '
访问URL:'+fileInfo.url+'
';
            html += '
';
            //将文件信息显示在列表里
            uploader.uploadResult.innerHTML +=html;
        }
        
        uploader.currentXHR = null;
        
        if (uploader.curFileitemElement) {
            var $fileNode = $(uploader.curFileitemElement);
            
            var cid = $fileNode.attr('data-cid');
            var serverid = $fileNode.attr('data-serverid');
            var userid = $fileNode.attr('data-userid');
            
            uploader.currentFile.uploadInfo = fileInfo;
            
            var taskid = uploader.curFileitemElement.id;
            var mimeType=mimeType = uploader.currentFile.type;
                
            if (!mimeType) {
                mimeType = get_file_mimetype(fileInfo.name);
            }
                        
            var title = uploader.curFileitemElement.videotitle ? curFileitemElement.videotitle:'';
            var desc = uploader.curFileitemElement.videodesc ? curFileitemElement.videodesc:'';
            //记录文件完成上传
            
            var uploadEvent = {
                "event":"filefinished",
                "taskid":uploader.curFileitemElement.id,
                "fileid":uploader.currentFile.fileid,
                "cid":cid,
                "serverid":serverid,
                "userid":userid,
                "mimetype":mimeType,
                "filepath":fileInfo.path,
                "filename":fileInfo.name,
                "filesize":fileInfo.filesize,
                "url":fileInfo.url,
                "title":title,
                "desc":desc
            };
                        
            if (uploader.options.notice_url) {
                $.post(uploader.options.notice_url,uploadEvent,function(data) {
                    if (data.code != 1) {
                        toast(data.msg);
                    }
                },'json');
            }
            
            if (typeof uploader.options.onevent == 'function') {
                uploader.options.onevent.call(uploader, uploadEvent);
            }
                        
            var isTaskfinish = 1;
            
            if (uploader.curFileitemElement.fileObject.files instanceof Array) {
                for(var i = 0; i < uploader.curFileitemElement.fileObject.files.length; i ++) {
                    if (!uploader.curFileitemElement.fileObject.files[i].uploadInfo) {
                        isTaskfinish = 0;
                        break;
                    }
                }
            }
            else {
                isTaskfinish = 1;
            }
            
            uploader.currentFile = null;
            uploader.upload_start = false;
            
            if (isTaskfinish) {
                $fileNode.attr('data-upload','finished').addClass('finished');
                $fileNode.find('.remove_icon').removeClass('enabled');
                
                uploader.curFileitemElement.fileObject.finished = 1;
                                
                if (typeof uploader.options.ontaskfinish == 'function') {
                    uploader.options.ontaskfinish.call(uploader,uploader.curFileitemElement.fileObject);
                }
                
                uploader.curFileitemElement = null;
                //接着启动下一个任务
                if (!_startNextTaskupload.call(uploader)) {
                    if (uploader.startbutton) {
                        uploader.startbutton.disabled=false;
                        $(uploader.startbutton).removeClass('disabled');
                        uploader.startbutton = null;
                    }
                }
            }
            else {
                //继续上传本任务的下一个文件
                _startFileupload.call(uploader,false);
            }                
        }
    }

    /*
        文件上传处理代码
        fileObj : html5 File 对象
        start_offset: 上传的数据相对于文件头的起始位置
        fileid: 文件的ID,这个是上面的getFileId 函数获取的,
    */
    var _do_upload_file = function (fileObj,start_offset,fileid) {
        var uploader = this;
        
        var xhr = new XMLHttpRequest();
        var formData = new FormData();
        
        var blobfile;
        
        if(start_offset >= fileObj.size){
            return false;
        }
        
        var bitrateDiv = null;
        var finishDiv = null;
        var progressBar = null;
        var progressDiv = null;

        if (uploader.curFileitemElement) {
            var $fileItem = $(uploader.curFileitemElement);
            
            bitrateDiv = $fileItem.find('.bitrate').get(0);
            finishDiv = $fileItem.find('.finish').get(0);
            progressBar = $fileItem.find('.progress-track').get(0);
            progressDiv = $fileItem.find('.percent-label').get(0);
        
        }
        else {
            bitrateDiv = document.getElementById("bitrate");
            finishDiv = document.getElementById("finish");
            progressBar = document.getElementById('progressbar');
            progressDiv = document.getElementById('percent-label');
        }

        var oldTimestamp = 0;
        var oldLoadsize = 0;
        var totalFilesize = uploader.curFileitemElement.fileObject.totalfilesize;
        if (totalFilesize == 0) {
            
            return false;
        }
        //将fileObj 设置为当前正在上传的文件
        uploader.currentFile = fileObj;
        //已经完成了的文件的总尺寸
        var totalUploadedsize = uploader.curFileitemElement.fileObject.uploadedsize;
        
        var uploadProgress = function (evt) {
            if (evt.lengthComputable) {
                var uploadedSize = totalUploadedsize + evt.loaded + start_offset; 
                var percentComplete = Math.round(uploadedSize * 100 / totalFilesize);

                var timestamp = (new Date()).valueOf();
                var isFinish = evt.loaded == evt.total;

                if (timestamp > oldTimestamp || isFinish) {
                    var duration = timestamp - oldTimestamp;
                    if (duration > 500 || isFinish) {
                        var size =  evt.loaded - oldLoadsize;

                        var bitrate = (size * 8 / duration /1024) * 1000; //kbps
                        if (bitrate > 1000)
                            bitrate = Math.round(bitrate / 1000) + 'Mbps';
                        else
                            bitrate = Math.round(bitrate) + 'Kbps';

                        var finish = totalUploadedsize + evt.loaded + start_offset;

                        if (finish > 1048576)
                            finish = (Math.round(finish / (1048576/100)) / 100).toString() + 'MB';
                        else
                            finish = (Math.round(finish / (1024/100) ) / 100).toString() + 'KB';

                        progressBar.style.width = percentComplete+'%';
                        progressDiv.innerHTML = percentComplete.toString() + '%';
                        bitrateDiv.innerHTML = bitrate;
                        finishDiv.innerHTML = finish;

                        oldTimestamp = timestamp;
                        oldLoadsize = evt.loaded;
                        
                        if (typeof uploader.options.ontaskprogress == 'function') {
                            var progress = {
                                'bitrate':bitrate,
                                'finish':finish,
                                'percent':percentComplete
                            };
                            
                            uploader.options.ontaskprogress.call(uploader,progress);
                        }
                    }
                }
            }
            else {
                progressDiv.innerHTML = 'N/A';
            }
        }
        
        xhr.onreadystatechange = function(){
            if (xhr.readyState == 4 && xhr.status == 200) {
                if (console)
                    console.log( xhr.responseText );
            }
            else if (xhr.status == 400) {
            
            }
        };

        var uploadComplete = function (evt) {
            progressDiv.innerHTML = '100%';
            
            uploader.curFileitemElement.fileObject.uploadedsize += uploader.currentFile.size;
            
            var result = JSON.parse(evt.target.responseText);
            if (result.result == 'success') {
                _onUploadedFileFinished.call(uploader,result.files[0]);
            }
            else {
                uploader.currentFile = null;
                uploader.upload_start = false;
            
                toast(result.msg);
            }
        }

        var uploadFailed = function (evt) {
            if (!uploader.currentFile) return;
            if (uploader.reconnectId) return;
            uploader.upload_start = false;
            
            toast("检测到网络故障!两秒后尝试重连...");
                    
            uploader.reconnectId = window.setTimeout(function() {
                _startFileupload.call(uploader,true);
            },2000);
            
            if (typeof uploader.options.ontaskfail == 'function') {
                uploader.options.ontaskfail.call(uploader,uploader.curFileitemElement.fileObject);
            }
        }

        var uploadCanceled = function (evt) {
                        
            if (uploader.curFileitemElement) {
                $(uploader.curFileitemElement).attr('data-upload','aborted');
                
                
                var fileid = uploader.currentFile.fileid;
                //记录文件取消上传
                
                var uploadEvent = {
                    "event":'abortupload',
                    "taskid":uploader.curFileitemElement.id,
                    "fileid":fileid
                };
            
                if (uploader.options.notice_url) {
                    $.post(uploader.options.notice_url,uploadEvent,function(data) {
                        if (data.code != 1) {
                            toast(data.msg);
                        }
                    },'json');
                }
                
                if (typeof uploader.options.onevent == 'function') {
                    uploader.options.onevent.call(uploader, uploadEvent);
                }
                if (uploader.taskIsForceRemove) {
                    delete uploader.curFileitemElement.fileObject;
                    $(uploader.curFileitemElement).remove();
                    uploader.curFileitemElement = null;
                    
                    var $fileitems = $(uploader.fileViewer).children('.upload-fileitem');
                    if ($fileitems.length == 0) {
                        $(uploader.fileViewer).html('

暂无上传任务

');
                    }
                }
            }
            
            uploader.currentFile = null;
            uploader.upload_start = false;
            
            if (uploader.startbutton) {
                uploader.startbutton.disabled=false;
                $(uploader.startbutton).removeClass('disabled');
                uploader.startbutton = null;
            }
            
            if (uploader.taskIsForceRemove) {
                uploader.currentXHR = null;
                
                delete uploader.taskIsForceRemove;
                //在可以强制删除的情况下继续执行下一个任务,但不提示toast
                _startNextTaskupload.call(uploader,true);
            }
            else {
                toast("上传已经被暂停取消或者浏览器断开了连接!");
                if (typeof uploader.options.onpause == 'function') {
                    uploader.options.onpause.call(uploader);
                }
            }
        }
        
        
        //设置超时时间,由于是上传大文件,因此千万不要设置超时
        //xhr.timeout = 20000;
        //xhr.ontimeout = function(event){
        //        alert('文件上传时间太长,服务器在规定的时间内没有响应!');
        //}         

        xhr.overrideMimeType("application/octet-stream"); 

        var mimeType = fileObj.type;
                
        if (!mimeType) {
            mimeType = get_file_mimetype(fileObj.name);
        }
                
        //附加的文件数据应该放在请求的前面
        var cid = uploader.curFileitemElement.getAttribute('data-cid');
        var serverid = uploader.curFileitemElement.getAttribute('data-serverid');
        formData.append('channelid', cid);
        formData.append('filename', fileObj.name);
        //必须将fileid信息传送给服务器,服务器只有在获得了fileid信息后才对文件做断点续传处理
        formData.append('fileid', fileid);
        //请将文件数据放在最后的域
        var filesize = fileObj.size;
        var blob = fileObj.slice(start_offset,filesize);
        if('msSaveOrOpenBlob' in navigator){
            formData.append("file",blob, fileObj.name);
        }
        else {
            var fileOfBlob = new File([blob], fileObj.name);
            formData.append('file', fileOfBlob);
        }
            
        xhr.upload.addEventListener("progress", uploadProgress, false);
        
        xhr.addEventListener("load", uploadComplete, false);
        xhr.addEventListener("error", uploadFailed, false);
        xhr.addEventListener("abort", uploadCanceled, false);
        
        var upload_url_full = uploader.upload_file_url + get_save_path(fileObj.name,uploader.options.savepaths);
        
        xhr.open('POST', upload_url_full);
        //
        xhr.send(formData);
        uploader.currentXHR = xhr;
        uploader.upload_start = true;
        toast('');
        if (start_offset == 0) {
            
            var uploadEvent = {
                "event":'startupload',
                "fileid":fileid,
                "cid":cid,
                "serverid":serverid,
                "filename":fileObj.name,
                "mimetype":mimeType
            };
            
            //开始文件上传
            if (uploader.options.notice_url) {
                $.post(uploader.options.notice_url,uploadEvent,function(data) {
                    if (data.code != 1) {
                        toast(data.msg);
                    }
                },'json');
            }
            
            if (typeof uploader.options.onevent == 'function') {
                uploader.options.onevent.call(uploader, uploadEvent);
            }
        }
        else {
            //记录文件断点续传
            var uploadEvent = {
                "event":'resumeupload',
                "fileid":fileid,
                "cid":cid,
                "serverid":serverid,
                "filename":fileObj.name,
                "mimetype":mimeType
            };
            
            if (uploader.options.notice_url) {
                $.post(uploader.options.notice_url,uploadEvent,function(data) {
                    if (data.code != 1) {
                        toast(data.msg);
                    }
                },'json');
            }
            
            if (typeof uploader.options.onevent == 'function') {
                uploader.options.onevent.call(uploader, uploadEvent);
            }
        }
    }
    /*
        处理上传服务器直接返回的上传完成信息
    */
    var _do_finish_file = function (fileObj,fileInfo) {
        var uploader = this;
        
        var bitrateDiv = null;
        var finishDiv = null;
        var progressBar = null;
        var progressDiv = null;

        if (!uploader.curFileitemElement) {
            return;
        }
        
        var $fileItem = $(uploader.curFileitemElement);
        
        bitrateDiv = $fileItem.find('.bitrate').get(0);
        finishDiv = $fileItem.find('.finish').get(0);
        progressBar = $fileItem.find('.progress-track').get(0);
        progressDiv = $fileItem.find('.percent-label').get(0);
        
        //模拟进度完成
        
        var totalFilesize = uploader.curFileitemElement.fileObject.totalfilesize;
        if (totalFilesize == 0) {
            return false;
        }
        //将fileObj 设置为当前正在上传的文件
        uploader.currentFile = fileObj;
        //已经完成了的文件的总尺寸
        var totalUploadedsize = uploader.curFileitemElement.fileObject.uploadedsize;
        
        var uploadedSize = totalUploadedsize + fileObj.size; 
        var percentComplete = Math.round(uploadedSize * 100 / totalFilesize);
                
        var finish = uploadedSize;

        if (finish > 1048576)
            finish = (Math.round(finish / (1048576/100)) / 100).toString() + 'MB';
        else
            finish = (Math.round(finish / (1024/100) ) / 100).toString() + 'KB';

        progressBar.style.width = percentComplete+'%';
        progressDiv.innerHTML = percentComplete.toString() + '%';
        bitrateDiv.innerHTML = '';
        finishDiv.innerHTML = finish;
        
        uploader.curFileitemElement.fileObject.uploadedsize += fileObj.size;
        
        _onUploadedFileFinished.call(uploader,fileInfo);
    }

    //doupload
    var _startFileupload = function (bReconnect) {
        var uploader = this;
        
        if (!this.options) {
            alert("发生未知错误,没有正确绑定上传对象。")
            return false;
        }
        
        if (!this.curFileitemElement) {
            alert("请选择文件后再试!")
            return false;
        }
        
        if (!bReconnect) {
            if (uploader.currentFile) {
                alert("上传对象已经有文件在上传了,不要重复调用!")
                return false;
            }
        }
        
        var fileObject = this.curFileitemElement.fileObject;
        if (fileObject.finished) {
            return false;
        }
        
        var fileObj = null;
        
        if (!this.currentFile) {
                        
            if (this.curFileitemElement.fileObject.files instanceof Array) {
                for(var i = 0; i < this.curFileitemElement.fileObject.files.length; i ++) {
                    if (!this.curFileitemElement.fileObject.files[i].uploadInfo) {
                        fileObj = this.curFileitemElement.fileObject.files[i];
                        break;
                    }
                }
            }
            else {
                if (!this.curFileitemElement.fileObject.files.uploadInfo) {
                    fileObj = this.curFileitemElement.fileObject.files;
                }
            }
            
            if (!fileObj) {
                alert("任务文件已经上传完毕!")
                return false;
            }
            
            //this.currentFile = fileObj;
        }
        else {
            fileObj = this.currentFile;
        }
        
        var fileid = getFileId(fileObj);
        var t = (new Date()).getTime();
        //通过以下URL获取文件的断点续传信息,必须的参数为fileid,后面追加t参数是避免浏览器缓存
        var url = this.resume_info_url + '?fileid='+fileid + '&t='+t;
        
        var ajax = new XMLHttpRequest();
        
        ajax.onerror = function (e) {
            if (uploader.reconnectId) 
                return;
            
            uploader.reconnectId = window.setTimeout(function() {
                uploader.currentXHR = null;
                
                _startFileupload.call(uploader,true);
            },2000);
        };
        
        ajax.onabort = function (evt) {
            uploader.currentFile = null;
            uploader.upload_start = false;
            
            if (uploader.curFileitemElement) {
                $(uploader.curFileitemElement).attr('data-upload','aborted');
                
                var fileid = uploader.curFileitemElement.id;
                        
                uploader.curFileitemElement = null;
            }
            if (uploader.startbutton) {
                uploader.startbutton.disabled=false;
                $(uploader.startbutton).removeClass('disabled');
                uploader.startbutton = null;
            }
                        
            toast("上传被取消或者浏览器断开了连接!");
        }
        
        ajax.onreadystatechange = function () {
            
            if(this.readyState == 4){
                if (bReconnect) {
                    //目前是重连状态,清除重连标志
                    uploader.reconnectId = 0;
                }
            
                if (this.status == 200){
                    var response = this.responseText;
                    
                    var result = JSON.parse(response);
                    if (!result) {
                        alert('服务器返回的数据不正确,可能是不兼容的服务器,上传无法继续!');
                        return;
                    }
                    //断点续传信息返回的文件对象包含已经上传的尺寸
                    var uploadedBytes = result.file && result.file.size;
                    if (!result.file.finished && uploadedBytes < fileObj.size) {
                        _do_upload_file.call(uploader,fileObj,uploadedBytes,fileid);
                    }
                    else {
                        //文件已经上传完成了,就不要再上传了,直接返回结果就可以了
                        _do_finish_file.call(uploader,fileObj,result.file);
                    }
                }else {
                    uploader.currentFile = null;
                    uploader.currentXHR = false;
            
                    toast('获取文件断点续传信息失败,服务器错误码:'+this.status+', 上传无法继续!');
                }  
            }
        }

        if (typeof this.options.ontaskstart == 'function') {
            if (false === this.options.ontaskstart.call(this,this.curFileitemElement.fileObject)) {
                return false;
            }
        }
        
        ajax.open('get',url,true);
        ajax.send(null);
        this.currentXHR = ajax;
        
        return true;
    }

    //任务上传完成后,开启下一个任务
    function _startNextTaskupload(isAfterAbort) {
        var uploader = this;
        var $fileitems = $(this.fileViewer).children('.upload-fileitem');
        $fileitems.each(function(index,elm) {
            if ($(elm).attr('data-upload') != 'finished') {
                if (elm.fileObject) {
                    $(elm).attr('data-upload','uploading');
                    uploader.curFileitemElement = elm;
                    return false;
                }
            }
        });
        
        if (!uploader.curFileitemElement) {
            
            if (typeof this.options.onend == 'function') {
                this.options.onend.call(this);
            }
            if (!isAfterAbort) {            
                toast('所有文件已经上传完毕!');
            }
            return false;
        }
        
        _startFileupload.call(uploader,false);
        
        return true;
    }

    
    var fileViewer = document.getElementById(_options.filelist);
    if (!fileViewer) {
        return null;
    }
    
    fileViewer.uploadOptions = _options;
    
    if (_options.enabledrop) {
        //禁止浏览器打开文件的默认行为
        document.addEventListener("drop",function(e){//拖离 
            e.preventDefault();
        });
        document.addEventListener("dragleave",function(e){//拖后放 
            e.preventDefault();
        });
        document.addEventListener("dragenter",function(e){//拖进
            e.preventDefault();
        });
        document.addEventListener("dragover",function(e){//拖来拖去  
            e.preventDefault();
        });

        fileViewer.addEventListener("drop",function (e) {
            var fileList = e.dataTransfer.files; //获取文件对象
            if (console) console.log(fileList)
            //检测是否是拖拽文件到页面的操作
            if(fileList.length == 0){
                return false;
            }
            
            for(var i = 0; i < fileList.length; i++) {
                _addFileToList(this,fileList[i]);
            }
        },false);
    }
    
    if (_options.fileselector) {
        var fileSelector = document.getElementById(_options.fileselector);
        if (fileSelector) {
            fileSelector.fileViewer = fileViewer;
            
            fileSelector.addEventListener("change",function (e) {
                var fileViewer = this.fileViewer;
                
                if (window.File && window.FileList) {
                    if (this.files.length) {
                        
                        for(var i = 0; i < this.files.length; i++) {
                            _addFileToList(fileViewer,this.files[i]);
                        }
                        
                        this.value = '';
                    }
                }
                else {
                    window.alert('抱歉,你的浏览器不支持FileAPI,请升级浏览器!');
                }
            },false);
        }
    }
    
    return {
        options:_options,
        fileViewer: fileViewer,
        resume_info_url:'',
        upload_file_url:'',
        upload_start:false,
        startbutton:null,
        currentFile: null,  //当前正在上传的文件对象
        curFileitemElement : null, //当前正在上传的 文件 DOM元素
        
        start:function(btnObj) {
            if (this.upload_start) {
                alert("文件上传正在进行中,请稍候再点击重复长传!")
                return false;
            }
        
            var $fileitems = $(this.fileViewer).children('.upload-fileitem');
            if ($fileitems.length == 0) {
                toast('没有任何可以上传的文件,请先添加文件再试!');
                return false;
            }
            
            var uploader = this;
            this.fileViewer.uploader = this;
            
            $fileitems.each(function(index,elm) {
                if ($(elm).attr('data-upload') != 'finished') {
                    if (elm.fileObject) {
                        $(elm).attr('data-upload','uploading');
                        uploader.curFileitemElement = elm;
                        
                        var uploadsrv_addr = 'http://'+uploader.options.server;
                        uploader.resume_info_url = uploadsrv_addr+'/resume/';
                        uploader.upload_file_url = uploadsrv_addr+'/upload/';
            
                        return false;
                    }
                }
            });
            
            if (!this.curFileitemElement) {
                toast('所有文件已经上传完毕,请添加新文件后再试!');
                return false;
            }

            this.startbutton = btnObj;
            
            if (this.options.authize_url) {
                //如果提供了授权验证URL,则向CMS平台进行验证
                $.get(this.options.authize_url,{"t":(new Date).getTime()},function(result) {
                    if (result.code != 1) {
                        if (typeof this.options.onend == 'function') {
                            this.options.onend.call(this);
                        }
                        toast(result.msg);
                        return;
                    }
                    
                    if (_startFileupload.call(uploader,false)) {
                        if (uploader.startbutton) {
                            uploader.startbutton.disabled=true;
                            $(uploader.startbutton).addClass('disabled');
                        }
                    }
                    else {
                        if (typeof this.options.onend == 'function') {
                            this.options.onend.call(this);
                        }
                    }
                },'json');
                
                return true;
            }
            else {
                if (_startFileupload.call(uploader,false)) {
                    if (uploader.startbutton) {
                        uploader.startbutton.disabled=true;
                        $(uploader.startbutton).addClass('disabled');
                    }
                    return true;
                }
                return false;
            }
        },
        
        pause:function () {
            if (this.currentFile) {
                if (this.currentXHR && typeof this.currentXHR.abort == 'function') {
                    this.currentXHR.abort();
                }
            }
        },
        
        addtask:function(fileobjs) {
            _addFileToList(this.fileViewer,fileobjs);
        },
        
        removetask:function(taskid) {
            var $fileItemElm = $('#'+taskid);
            if ($fileItemElm.length == 1 && $fileItemElm.hasClass('upload-fileitem')) {
                if ($fileItemElm.attr('data-upload')=='uploading') {
                    alert('任务正在进行中,请停止任务后再删除!');
                    return;
                }
                
                var itemDom = $fileItemElm.get(0);
                if (itemDom.fileObject) {
                    delete itemDom.fileObject;
                }
                
                $fileItemElm.remove();
            }
        },
        
        clear:function() {
            var $fileitems = $(this.fileViewer).children('.upload-fileitem');
            if ($fileitems.length == 0) {
                return false;
            }
            
            if (!confirm('确实要清除任务列表吗?')) {
                return false;
            }
            
            if (this.currentFile) {
                if (this.currentXHR && typeof this.currentXHR.abort == 'function') {
                    this.currentXHR.abort();
                }
                
                if (typeof this.options.onend == 'function') {
                    this.options.onend.call(this);
                }
            }
            
            $fileitems.each(function(index,elm) {
                if (elm.fileObject) delete elm.fileObject;
            });
            
            $fileitems.remove();
            this.curFileitemElement = null;
            this.currentFile = null;
            this.currentXHR = null;
                        
            $(this.fileViewer).html('

暂无上传任务

');
        },
                
        version: "2.0"
    };
};

 

技术交流

- QQ:1918098288

- Mail: [email protected]

 

你可能感兴趣的:(流媒体中间件,上传服务器,HTML5上传,断点续传,大文件上传)