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