1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 //TODO: 超时timeout,失败重连次数maxTries,更多的下载器Loader,队列暂停恢复等。 8 9 /** 10 * @class LoadQueue是一个队列下载工具。 11 * @mixes EventMixin 12 * @borrows EventMixin#on as #on 13 * @borrows EventMixin#off as #off 14 * @borrows EventMixin#fire as #fire 15 * @param {Object} source 要下载的资源。可以是单个资源对象或多个资源的数组。 16 * @module hilo/loader/LoadQueue 17 * @requires hilo/core/Class 18 * @requires hilo/event/EventMixin 19 * @requires hilo/loader/ImageLoader 20 * @requires hilo/loader/ScriptLoader 21 * @property {Int} maxConnections 同时下载的最大连接数。默认为2。 22 */ 23 var LoadQueue = Class.create(/** @lends LoadQueue.prototype */{ 24 Mixes: EventMixin, 25 constructor: function(source){ 26 this._source = []; 27 this.add(source); 28 }, 29 30 maxConnections: 2, //TODO: 应该是每个host的最大连接数。 31 32 _source: null, 33 _loaded: 0, 34 _connections: 0, 35 _currentIndex: -1, 36 37 /** 38 * 增加要下载的资源。可以是单个资源对象或多个资源的数组。 39 * @param {Object|Array} source 资源对象或资源对象数组。每个资源对象包含以下属性: 40 * <ul> 41 * <li><b>id</b> - 资源的唯一标识符。可用于从下载队列获取目标资源。</li> 42 * <li><b>src</b> - 资源的地址url。</li> 43 * <li><b>type</b> - 指定资源的类型。默认会根据资源文件的后缀来自动判断类型,不同的资源类型会使用不同的加载器来加载资源。</li> 44 * <li><b>loader</b> - 指定资源的加载器。默认会根据资源类型来自动选择加载器,若指定loader,则会使用指定的loader来加载资源。</li> 45 * <li><b>noCache</b> - 指示加载资源时是否增加时间标签以防止缓存。</li> 46 * <li><b>size</b> - 资源对象的预计大小。可用于预估下载进度。</li> 47 * <li><b>crossOrigin</b> - 是否需要跨域,默认否。</li> 48 * </ul> 49 * @returns {LoadQueue} 下载队列实例本身。 50 */ 51 add: function(source){ 52 var me = this; 53 if(source){ 54 source = source instanceof Array ? source : [source]; 55 me._source = me._source.concat(source); 56 } 57 return me; 58 }, 59 60 /** 61 * 根据id或src地址获取资源对象。 62 * @param {String} id 指定资源的id或src。 63 * @returns {Object} 资源对象。 64 */ 65 get: function(id){ 66 if(id){ 67 var source = this._source; 68 for(var i = 0; i < source.length; i++){ 69 var item = source[i]; 70 if(item.id === id || item.src === id){ 71 return item; 72 } 73 } 74 } 75 return null; 76 }, 77 78 /** 79 * 根据id或src地址获取资源内容。 80 * @param {String} id 指定资源的id或src。 81 * @returns {Object} 资源内容。 82 */ 83 getContent: function(id){ 84 var item = this.get(id); 85 return item && item.content; 86 }, 87 /** 88 * 根据id或src地址删除资源内容。 89 * @param {String} id 指定资源的id或src。 90 */ 91 removeContent: function(id){ 92 if(id){ 93 var source = this._source; 94 for(var i = 0; i < source.length; i++){ 95 var item = source[i]; 96 if(item.id === id || item.src === id){ 97 source.splice(i, 1); 98 return; 99 } 100 } 101 } 102 }, 103 104 /** 105 * 开始下载队列。 106 * @returns {LoadQueue} 下载队列实例本身。 107 */ 108 start: function(){ 109 var me = this; 110 me._loadNext(); 111 return me; 112 }, 113 114 /** 115 * @private 116 */ 117 _loadNext: function(){ 118 var me = this, source = me._source, len = source.length; 119 120 //all items loaded 121 if(me._loaded >= len){ 122 me.fire('complete'); 123 return; 124 } 125 126 if(me._currentIndex < len - 1 && me._connections < me.maxConnections){ 127 var index = ++me._currentIndex; 128 var item = source[index]; 129 var loader = me._getLoader(item); 130 131 if(loader){ 132 var onLoad = loader.onLoad, onError = loader.onError; 133 134 loader.onLoad = function(e){ 135 loader.onLoad = onLoad; 136 loader.onError = onError; 137 var content = onLoad && onLoad.call(loader, e) || e.target; 138 me._onItemLoad(index, content); 139 }; 140 loader.onError = function(e){ 141 loader.onLoad = onLoad; 142 loader.onError = onError; 143 onError && onError.call(loader, e); 144 me._onItemError(index, e); 145 }; 146 me._connections++; 147 } 148 149 me._loadNext(); 150 loader && loader.load(item); 151 } 152 }, 153 154 /** 155 * @private 156 */ 157 _getLoader: function(item){ 158 var loader = item.loader; 159 if(loader) return loader; 160 161 var type = item.type || getExtension(item.src); 162 163 switch(type){ 164 case 'png': 165 case 'jpg': 166 case 'jpeg': 167 case 'gif': 168 case 'webp': 169 loader = new ImageLoader(); 170 break; 171 case 'js': 172 case 'jsonp': 173 loader = new ScriptLoader(); 174 break; 175 } 176 177 return loader; 178 }, 179 180 /** 181 * @private 182 */ 183 _onItemLoad: function(index, content){ 184 var me = this, item = me._source[index]; 185 item.loaded = true; 186 item.content = content; 187 me._connections--; 188 me._loaded++; 189 me.fire('load', item); 190 me._loadNext(); 191 }, 192 193 /** 194 * @private 195 */ 196 _onItemError: function(index, e){ 197 var me = this, item = me._source[index]; 198 item.error = e; 199 me._connections--; 200 me._loaded++; 201 me.fire('error', item); 202 me._loadNext(); 203 }, 204 205 /** 206 * 获取全部或已下载的资源的字节大小。 207 * @param {Boolean} loaded 指示是已下载的资源还是全部资源。默认为全部。 208 * @returns {Number} 指定资源的字节大小。 209 */ 210 getSize: function(loaded){ 211 var size = 0, source = this._source; 212 for(var i = 0; i < source.length; i++){ 213 var item = source[i]; 214 size += (loaded ? item.loaded && item.size : item.size) || 0; 215 } 216 return size; 217 }, 218 219 /** 220 * 获取已下载的资源数量。 221 * @returns {Uint} 已下载的资源数量。 222 */ 223 getLoaded: function(){ 224 return this._loaded; 225 }, 226 227 /** 228 * 获取所有资源的数量。 229 * @returns {Uint} 所有资源的数量。 230 */ 231 getTotal: function(){ 232 return this._source.length; 233 } 234 235 }); 236 237 /** 238 * @private 239 */ 240 function getExtension(src){ 241 var extRegExp = /\/?[^/]+\.(\w+)(\?\S+)?$/i, match, extension; 242 if(match = src.match(extRegExp)){ 243 extension = match[1].toLowerCase(); 244 } 245 return extension || null; 246 }