1
var html5Preloader = (function () {
3
var XHR = typeof XMLHttpRequest === 'undefined' ? function () { // IE FIX
5
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
8
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
13
AudioElement = typeof Audio !== 'undefined' ? // IE FIX
18
return document.createElement('audio');
20
VideoElement = typeof Video !== 'undefined' ? // IE FIX
25
return document.createElement('video');
27
ImageElement = function () {
30
codecs = { // Chart from jPlayer
32
codec: 'audio/ogg; codecs="vorbis"',
36
codec: 'audio/wav; codecs="1"',
40
codec: 'audio/webm; codecs="vorbis"',
44
codec: 'audio/mpeg; codecs="mp3"',
48
codec: 'audio/mp4; codecs="mp4a.40.2"',
52
codec: 'video/ogg; codecs="theora, vorbis"',
56
codec: 'video/webm; codecs="vorbis, vp8"',
60
codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
65
imageTypes: ['jpg', 'png', 'jpeg', 'tiff', 'gif']
68
/* :) may fail sometimes, but these are the most common cases */
69
codecs.ogg = codecs.oga;
70
codecs.mp4 = codecs.m4v;
71
codecs.webm = codecs.webmv;
73
function isIn (needle, haystack) {
74
for (var i=0; i<haystack.length; i++) {
75
if (haystack[i] === needle) {
83
function map (arr, callback) {
85
return arr.map(callback);
90
for (i=0; i<arr.length; i++) {
91
r.push(callback(arr[i]));
97
function bind (func, self) {
98
return func.bind ? func.bind(self) : function () {
99
return func.apply(self, arguments);
103
function delay (callback) {
104
var args = [].slice.call(arguments, 1);
105
setTimeout(function () {
106
callback.apply(this, args);
110
function EventEmitter () {
112
for (k in EventEmitter.prototype) {
113
if (EventEmitter.prototype.hasOwnProperty(k)) {
114
this[k] = EventEmitter.prototype[k];
117
this._listeners = {};
120
EventEmitter.prototype = {
123
emit: function (name, args) {
125
if (this._listeners[name]) {
126
for (var i=0; i<this._listeners[name].length; i++) {
127
this._listeners[name][i].apply(this, args);
133
on: function (name, listener) {
134
this._listeners[name] = this._listeners[name] || [];
135
this._listeners[name].push(listener);
139
off: function (name, listener) {
140
if (this._listeners[name]) {
142
delete this._listeners[name];
145
for (var i=0; i<this._listeners[name].length; i++) {
146
if (this._listeners[name][i] === listener) {
147
this._listeners[name].splice(i--, 1);
150
this._listeners[name].length || delete this._listeners[name];
155
once: function (name, listener) {
158
return listener.apply(this, arguments);
161
return this.on(name, ev);
165
function loadFile (file, callback, timeout) {
166
if (!(this instanceof loadFile)) {
167
return new loadFile(file, callback, timeout);
174
if (typeof file === 'string') {
175
a = file.split('*:');
176
b = a[ a[1] ? 1 : 0 ].split('||');
177
self.id = a[1] ? a[0] : b[0];
178
self.alternates = alternates;
180
for (a=0; a<b.length; a++) {
182
c = c[c.length - 1].toLowerCase();
184
t = codecs[c] ? codecs[c].media : isIn(c, support.imageTypes) ? 'image' : 'document';
186
if (codecs[c] && !codecs[c].supported) {
196
alternates.length || alternates.push({
201
delay(callback, TypeError('Invalid path'), self);
205
function loadNext() {
206
var file = alternates.shift(),
207
_timeoutTimer = null;
210
delay(callback, {e: Error('No viable alternatives')}, null);
214
if (typeof timeout === 'number') {
215
_timeoutTimer = setTimeout(function() {
216
delay(callback, {e: Error('Load event not fired within ' + timeout + 'ms')}, self);
220
new loadFile[file.type](
224
_timeoutTimer && clearTimeout(_timeoutTimer);
226
self.dom = f && f.dom;
228
if (e && self.alternates.length) {
239
function MediaFile (construct) {
240
return function (filename, callback) {
244
function onready () {
245
file.onload = file.onerror = null;
246
file.removeEventListener && file.removeEventListener('canplaythrough', onready, true);
248
callback(null, self);
251
file.addEventListener && file.addEventListener('canplaythrough', onready, true);
252
file.onload = onready;
253
file.onerror = function (e) {
260
file.load && file.load();
264
loadFile.audio = MediaFile(AudioElement);
265
loadFile.video = MediaFile(VideoElement);
266
loadFile.image = MediaFile(ImageElement);
268
loadFile.document = function (file, callback) {
270
parsedUrl = /(\[(!)?(.+)?\])?$/.exec(file),
271
mimeType = parsedUrl[3],
272
xhr = self.dom = new XHR();
275
delay(callback, Error('No XHR!'), self);
279
file = file.substr(0, file.length - parsedUrl[0].length);
280
file += parsedUrl[2] ? (file.indexOf('?') === -1 ? '?' : '&') + 'fobarz=' + (+new Date) : '';
282
mimeType && xhr.overrideMimeType(mimeType === '@' ? 'text/plain; charset=x-user-defined' : mimeType);
284
xhr.onreadystatechange = function () {
285
if (xhr.readyState !== 4) return;
288
self.dom = xhr.responseXML && xhr.responseXML.documentElement ?
290
String(xhr.responseText || '') ;
293
callback(null, self) :
294
callback({e: Error('Request failed: ' + xhr.status)}, self) ;
296
callback({e: e}, self);
300
xhr.onerror = function (e) {
304
xhr.open('GET', file, true);
309
var dummyAudio = AudioElement(),
310
dummyVideo = VideoElement(),
313
support.audio = !!dummyAudio.canPlayType;
314
support.video = !!dummyVideo.canPlayType;
316
support.audioTypes = [];
317
support.videoTypes = [];
320
if (codecs.hasOwnProperty(i)) {
321
if (codecs[i].media === 'video') {
322
(codecs[i].supported = support.video &&
323
dummyVideo.canPlayType(codecs[i].codec)) &&
324
support.videoTypes.push(i);
325
} else if (codecs[i].media === 'audio') {
326
(codecs[i].supported = support.audio &&
327
dummyAudio.canPlayType(codecs[i].codec)) &&
328
support.audioTypes.push(i);
334
if (!support.audio) {
335
loadFile.audio = function (a, callback) {
336
delay(callback, Error('<AUDIO> not supported.'), a);
339
if (!support.video) {
340
loadFile.video = function (a, callback) {
341
delay(callback, Error('<VIDEO> not supported.'), a);
345
function html5Preloader () {
349
if (!(self instanceof html5Preloader)) {
350
self = new html5Preloader();
351
args.length && self.loadFiles.apply(self, args);
357
html5Preloader.EventEmitter.call(self);
359
self.loadCallback = bind(self.loadCallback, self);
361
args.length && self.loadFiles.apply(self, args);
364
html5Preloader.prototype = {
372
loadCallback: function (e, f) {
374
if (!this.filesLoadedMap[f.id]) {
376
this.filesLoadedMap[f.id] = f;
379
this.emit(e ? 'error' : 'fileloaded', e ? [e, f] : [f]);
381
if (this.filesLoading - this.filesLoaded === 0) {
384
this.filesLoading = 0;
385
this.filesLoaded = 0;
389
getFile: function (id) {
390
return typeof id === 'undefined' ? map(this.files, function (f) {
393
typeof id === 'number' ? this.files[id].dom :
394
typeof id === 'string' ? this.files[ID_PREFIX + id].dom :
398
removeFile: function (id) {
406
this.files[ID_PREFIX + f.id] && delete this.files[ID_PREFIX + f.id];
407
this.files.splice(id, 1);
410
f = this.files[ID_PREFIX + id];
411
f && delete this.files[ID_PREFIX + id];
413
for (i=0; i<this.files.length; i++) {
414
this.files[i] === f && this.files.splice(i--, 1);
419
loadFiles: function () {
420
var files = [].slice.call(arguments),
423
for (i=0; i<files.length; i++) {
424
f = html5Preloader.loadFile(files[i], this.loadCallback, this.timeout);
426
this.files[ID_PREFIX + f.id] = f;
430
this.active = this.active || !!this.filesLoading;
433
addFiles: function (list) {
434
return this.loadFiles.apply(this, list instanceof Array ? list : arguments);
437
getProgress: function () {
438
return this.filesLoading ? this.filesLoaded / this.filesLoading : 1.0;
442
html5Preloader.support = support;
443
html5Preloader.loadFile = loadFile;
444
html5Preloader.EventEmitter = EventEmitter;
446
return html5Preloader;