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 is a queue-like loader. 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 resource that need to be loaded,could be a single object or array resource. 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 the limited concurrent connections. default value 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 * Add desired resource,could be a single object or array resource. 39 * @param {Object|Array} source ,a single object or array resource. Each resource contains properties like below: 40 * <ul> 41 * <li><b>id</b> - resource identifier</li> 42 * <li><b>src</b> - resource url</li> 43 * <li><b>type</b> - resource type. By default, we automatically identify resource by file suffix and choose the relevant loader for you</li> 44 * <li><b>loader</b> - specified resource loader. If you specify this,we abandon choosing loader inside</li> 45 * <li><b>noCache</b> - a tag that set on or off to prevent cache,implemented by adding timestamp inside</li> 46 * <li><b>size</b> - predicted resource size, help calculating loading progress</li> 47 * <li><b>crossOrigin</b> - Whether cross-domain is needed, default is false</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 * get resource object by id or src 62 * @param {String} specified id or src 63 * @returns {Object} resource 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 * get resource object content by id or src 80 * @param {String} specified id or src 81 * @returns {Object} resource object content 82 */ 83 getContent: function(id){ 84 var item = this.get(id); 85 return item && item.content; 86 }, 87 /** 88 * remove resource object content by id or src 89 * @param {String} specified id or 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 * start loading 106 * @returns {LoadQueue} the loading instance 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 * get resource size, loaded or all. 207 * @param {Boolean} identify loaded or all resource. default is false, return all resource size. when set true, return loaded resource size. 208 * @returns {Number} resource size. 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 * get loaded resource count 221 * @returns {Uint} loaded resource count 222 */ 223 getLoaded: function(){ 224 return this._loaded; 225 }, 226 227 /** 228 * get all resource count 229 * @returns {Uint} all resource count 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 }