~xibo-maintainers/xibo/tempel

« back to all changes in this revision

Viewing changes to web/modules/html5Preloader.js

  • Committer: Dan Garner
  • Date: 2015-06-19 14:19:44 UTC
  • mto: This revision was merged to the branch mainline in revision 429.
  • Revision ID: git-v1:311c8859470bb7789f6b15c409daa2c343d8c6be
Module Installation Routines

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
var html5Preloader = (function () {
2
 
 
3
 
var     XHR = typeof XMLHttpRequest === 'undefined' ? function () { // IE FIX
4
 
                try {
5
 
                        return new ActiveXObject("Msxml2.XMLHTTP.6.0");
6
 
                } catch (err1) {}
7
 
                try {
8
 
                        return new ActiveXObject("Msxml2.XMLHTTP.3.0");
9
 
                } catch (err2) {}
10
 
 
11
 
                return null;
12
 
        } : XMLHttpRequest,
13
 
        AudioElement = typeof Audio !== 'undefined' ? // IE FIX
14
 
                function(){
15
 
                        return new Audio();
16
 
                } :
17
 
                function(){
18
 
                        return document.createElement('audio');
19
 
                },
20
 
        VideoElement = typeof Video !== 'undefined' ? // IE FIX
21
 
                function () {
22
 
                        return new Video();
23
 
                } :
24
 
                function () {
25
 
                        return document.createElement('video');
26
 
                },
27
 
        ImageElement = function () {
28
 
                return new Image();
29
 
        },
30
 
        codecs = { // Chart from jPlayer
31
 
                oga: { // OGG
32
 
                        codec: 'audio/ogg; codecs="vorbis"',
33
 
                        media: 'audio'
34
 
                },
35
 
                wav: { // PCM
36
 
                        codec: 'audio/wav; codecs="1"',
37
 
                        media: 'audio'
38
 
                },
39
 
                webma: { // WEBM
40
 
                        codec: 'audio/webm; codecs="vorbis"',
41
 
                        media: 'audio'
42
 
                },
43
 
                mp3: {
44
 
                        codec: 'audio/mpeg; codecs="mp3"',
45
 
                        media: 'audio'
46
 
                },
47
 
                m4a: { // AAC / MP4
48
 
                        codec: 'audio/mp4; codecs="mp4a.40.2"',
49
 
                        media: 'audio'
50
 
                },
51
 
                ogv: { // OGG
52
 
                        codec: 'video/ogg; codecs="theora, vorbis"',
53
 
                        media: 'video'
54
 
                },
55
 
                webmv: { // WEBM
56
 
                        codec: 'video/webm; codecs="vorbis, vp8"',
57
 
                        media: 'video'
58
 
                },
59
 
                m4v: { // H.264 / MP4
60
 
                        codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
61
 
                        media: 'video'
62
 
                }
63
 
        },
64
 
        support = {
65
 
                imageTypes: ['jpg', 'png', 'jpeg', 'tiff', 'gif']
66
 
        },
67
 
        ID_PREFIX = 'FILE@';
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;
72
 
 
73
 
function isIn (needle, haystack) {
74
 
        for (var i=0; i<haystack.length; i++) {
75
 
                if (haystack[i] === needle) {
76
 
                        return true;
77
 
                }
78
 
        }
79
 
 
80
 
        return false;
81
 
}
82
 
 
83
 
function map (arr, callback) {
84
 
        if (arr.map) {
85
 
                return arr.map(callback);
86
 
        }
87
 
 
88
 
        var     r = [],
89
 
                i;
90
 
        for (i=0; i<arr.length; i++) {
91
 
                r.push(callback(arr[i]));
92
 
        }
93
 
 
94
 
        return r;
95
 
}
96
 
 
97
 
function bind (func, self) {
98
 
        return func.bind ? func.bind(self) : function () {
99
 
                return func.apply(self, arguments);
100
 
        };
101
 
}
102
 
 
103
 
function delay (callback) {
104
 
        var args = [].slice.call(arguments, 1);
105
 
        setTimeout(function () {
106
 
                callback.apply(this, args);
107
 
        }, 0);
108
 
}
109
 
 
110
 
function EventEmitter () {
111
 
        var k;
112
 
        for (k in EventEmitter.prototype) {
113
 
                if (EventEmitter.prototype.hasOwnProperty(k)) {
114
 
                        this[k] = EventEmitter.prototype[k];
115
 
                }
116
 
        }
117
 
        this._listeners = {};
118
 
};
119
 
 
120
 
EventEmitter.prototype = {
121
 
        _listeners: null,
122
 
 
123
 
        emit: function (name, args) {
124
 
                args = 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);
128
 
                        }
129
 
                }
130
 
                return this;
131
 
        },
132
 
 
133
 
        on: function (name, listener) {
134
 
                this._listeners[name] = this._listeners[name] || [];
135
 
                this._listeners[name].push(listener);
136
 
                return this;
137
 
        },
138
 
 
139
 
        off: function (name, listener) {
140
 
                if (this._listeners[name]) {
141
 
                        if (!listener) {
142
 
                                delete this._listeners[name];
143
 
                                return this;
144
 
                        }
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);
148
 
                                }
149
 
                        }
150
 
                        this._listeners[name].length || delete this._listeners[name];
151
 
                }
152
 
                return this;
153
 
        },
154
 
 
155
 
        once: function (name, listener) {
156
 
                function ev () {
157
 
                        this.off(ev);
158
 
                        return listener.apply(this, arguments);
159
 
                }
160
 
 
161
 
                return this.on(name, ev);
162
 
        }
163
 
};
164
 
 
165
 
function loadFile (file, callback, timeout) {
166
 
        if (!(this instanceof loadFile)) {
167
 
                return new loadFile(file, callback, timeout);
168
 
        }
169
 
 
170
 
        var     self            = this,
171
 
                alternates      = [],
172
 
                a, b, c, t;
173
 
 
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;
179
 
 
180
 
                for (a=0; a<b.length; a++) {
181
 
                        c = b[a].split('.');
182
 
                        c = c[c.length - 1].toLowerCase();
183
 
 
184
 
                        t = codecs[c] ? codecs[c].media : isIn(c, support.imageTypes) ? 'image' : 'document';
185
 
 
186
 
                        if (codecs[c] && !codecs[c].supported) {
187
 
                                continue;
188
 
                        }
189
 
 
190
 
                        alternates.push({
191
 
                                type: t,
192
 
                                path: b[a]
193
 
                        });
194
 
                }
195
 
 
196
 
                alternates.length || alternates.push({
197
 
                        type: t,
198
 
                        path: b[a-1]
199
 
                });
200
 
        } else {
201
 
                delay(callback, TypeError('Invalid path'), self);
202
 
                return;
203
 
        }
204
 
 
205
 
        function loadNext() {
206
 
                var file = alternates.shift(),
207
 
                        _timeoutTimer = null;
208
 
 
209
 
                if (!file) {
210
 
                        delay(callback, {e: Error('No viable alternatives')}, null);
211
 
                        return;
212
 
                }
213
 
 
214
 
                if (typeof timeout === 'number') {
215
 
                        _timeoutTimer = setTimeout(function() {
216
 
                                delay(callback, {e: Error('Load event not fired within ' + timeout + 'ms')}, self);
217
 
                        }, timeout);
218
 
                }
219
 
 
220
 
                new loadFile[file.type](
221
 
                                file.path,
222
 
                                function (e, f) {
223
 
 
224
 
                                        _timeoutTimer && clearTimeout(_timeoutTimer);
225
 
 
226
 
                                        self.dom = f && f.dom;
227
 
 
228
 
                                        if (e && self.alternates.length) {
229
 
                                                return loadNext();
230
 
                                        }
231
 
 
232
 
                                        callback(e, self);
233
 
                                });
234
 
        }
235
 
 
236
 
        loadNext();
237
 
}
238
 
 
239
 
function MediaFile (construct) {
240
 
        return function (filename, callback) {
241
 
                var     self = this,
242
 
                        file = construct();
243
 
 
244
 
                function onready () {
245
 
                        file.onload = file.onerror = null;
246
 
                        file.removeEventListener && file.removeEventListener('canplaythrough', onready, true);
247
 
 
248
 
                        callback(null, self);
249
 
                }
250
 
 
251
 
                file.addEventListener && file.addEventListener('canplaythrough', onready, true);
252
 
                file.onload = onready;
253
 
                file.onerror = function (e) {
254
 
                        callback(e, self);
255
 
                };
256
 
 
257
 
                self.dom = file;
258
 
                file.src = filename;
259
 
 
260
 
                file.load && file.load();
261
 
        };
262
 
}
263
 
 
264
 
loadFile.audio = MediaFile(AudioElement);
265
 
loadFile.video = MediaFile(VideoElement);
266
 
loadFile.image = MediaFile(ImageElement);
267
 
 
268
 
loadFile.document = function (file, callback) {
269
 
        var     self            = this,
270
 
                parsedUrl       = /(\[(!)?(.+)?\])?$/.exec(file),
271
 
                mimeType        = parsedUrl[3],
272
 
                xhr             = self.dom = new XHR();
273
 
 
274
 
        if (!xhr) {
275
 
                delay(callback, Error('No XHR!'), self);
276
 
                return;
277
 
        }
278
 
 
279
 
        file            = file.substr(0, file.length - parsedUrl[0].length);
280
 
        file            += parsedUrl[2] ? (file.indexOf('?') === -1 ? '?' : '&') + 'fobarz=' + (+new Date) : '';
281
 
 
282
 
        mimeType && xhr.overrideMimeType(mimeType === '@' ? 'text/plain; charset=x-user-defined' : mimeType);
283
 
 
284
 
        xhr.onreadystatechange = function () {
285
 
                if (xhr.readyState !== 4) return;
286
 
 
287
 
                try {
288
 
                        self.dom = xhr.responseXML && xhr.responseXML.documentElement ?
289
 
                                xhr.responseXML :
290
 
                                String(xhr.responseText || '') ;
291
 
 
292
 
                        xhr.status === 200 ?
293
 
                                callback(null, self) :
294
 
                                callback({e: Error('Request failed: ' + xhr.status)}, self) ;
295
 
                } catch (e) {
296
 
                        callback({e: e}, self);
297
 
                }
298
 
        };
299
 
 
300
 
        xhr.onerror = function (e) {
301
 
                callback(e, self);
302
 
        };
303
 
 
304
 
        xhr.open('GET', file, true);
305
 
        xhr.send();
306
 
};
307
 
 
308
 
(function () {
309
 
        var     dummyAudio = AudioElement(),
310
 
                dummyVideo = VideoElement(),
311
 
                i;
312
 
 
313
 
        support.audio = !!dummyAudio.canPlayType;
314
 
        support.video = !!dummyVideo.canPlayType;
315
 
 
316
 
        support.audioTypes = [];
317
 
        support.videoTypes = [];
318
 
 
319
 
        for (i in codecs) {
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);
329
 
                        }
330
 
                }
331
 
        }
332
 
}());
333
 
 
334
 
if (!support.audio) {
335
 
        loadFile.audio = function (a, callback) {
336
 
                delay(callback, Error('<AUDIO> not supported.'), a);
337
 
        };
338
 
}
339
 
if (!support.video) {
340
 
        loadFile.video = function (a, callback) {
341
 
                delay(callback, Error('<VIDEO> not supported.'), a);
342
 
        };
343
 
}
344
 
 
345
 
function html5Preloader () {
346
 
        var     self = this,
347
 
                args = arguments;
348
 
 
349
 
        if (!(self instanceof html5Preloader)) {
350
 
                self = new html5Preloader();
351
 
                args.length && self.loadFiles.apply(self, args);
352
 
                return self;
353
 
        }
354
 
 
355
 
        self.files = [];
356
 
 
357
 
        html5Preloader.EventEmitter.call(self);
358
 
 
359
 
        self.loadCallback = bind(self.loadCallback, self);
360
 
 
361
 
        args.length && self.loadFiles.apply(self, args);
362
 
}
363
 
 
364
 
html5Preloader.prototype = {
365
 
        active: false,
366
 
        files: null,
367
 
        filesLoading: 0,
368
 
        filesLoaded: 0,
369
 
        filesLoadedMap: {},
370
 
        timeout: null,
371
 
 
372
 
        loadCallback: function (e, f) {
373
 
 
374
 
                if (!this.filesLoadedMap[f.id]) {
375
 
                        this.filesLoaded++;
376
 
                        this.filesLoadedMap[f.id] = f;
377
 
                }
378
 
 
379
 
                this.emit(e ? 'error' : 'fileloaded', e ? [e, f] : [f]);
380
 
 
381
 
                if (this.filesLoading - this.filesLoaded === 0) {
382
 
                        this.active = false;
383
 
                        this.emit('finish');
384
 
                        this.filesLoading = 0;
385
 
                        this.filesLoaded = 0;
386
 
                }
387
 
        },
388
 
 
389
 
        getFile: function (id) {
390
 
                return  typeof id === 'undefined' ? map(this.files, function (f) {
391
 
                                return f.dom;
392
 
                        }) :
393
 
                        typeof id === 'number' ? this.files[id].dom :
394
 
                        typeof id === 'string' ? this.files[ID_PREFIX + id].dom :
395
 
                        null;
396
 
        },
397
 
 
398
 
        removeFile: function (id) {
399
 
                var f, i;
400
 
                switch (typeof id) {
401
 
                case 'undefined':
402
 
                        this.files = [];
403
 
                        break;
404
 
                case 'number':
405
 
                        f = this.files[id];
406
 
                        this.files[ID_PREFIX + f.id] && delete this.files[ID_PREFIX + f.id];
407
 
                        this.files.splice(id, 1);
408
 
                        break;
409
 
                case 'string':
410
 
                        f = this.files[ID_PREFIX + id];
411
 
                        f && delete this.files[ID_PREFIX + id];
412
 
 
413
 
                        for (i=0; i<this.files.length; i++) {
414
 
                                this.files[i] === f && this.files.splice(i--, 1);
415
 
                        }
416
 
                }
417
 
        },
418
 
 
419
 
        loadFiles: function () {
420
 
                var     files   = [].slice.call(arguments),
421
 
                        i, f;
422
 
 
423
 
                for (i=0; i<files.length; i++) {
424
 
                        f = html5Preloader.loadFile(files[i], this.loadCallback, this.timeout);
425
 
                        this.files.push(f);
426
 
                        this.files[ID_PREFIX + f.id] = f;
427
 
                        this.filesLoading++;
428
 
                }
429
 
 
430
 
                this.active = this.active || !!this.filesLoading;
431
 
        },
432
 
 
433
 
        addFiles: function (list) {
434
 
                return this.loadFiles.apply(this, list instanceof Array ? list : arguments);
435
 
        },
436
 
 
437
 
        getProgress: function () {
438
 
                return this.filesLoading ? this.filesLoaded / this.filesLoading : 1.0;
439
 
        }
440
 
};
441
 
 
442
 
html5Preloader.support = support;
443
 
html5Preloader.loadFile = loadFile;
444
 
html5Preloader.EventEmitter = EventEmitter;
445
 
 
446
 
return html5Preloader;
447
 
 
448
 
}());