pageflow 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pageflow might be problematic. Click here for more details.
- checksums.yaml +13 -5
- data/admins/pageflow/accounts.rb +75 -0
- data/admins/pageflow/entry.rb +112 -0
- data/admins/pageflow/folder.rb +59 -0
- data/admins/pageflow/membership.rb +21 -0
- data/admins/pageflow/revisions.rb +33 -0
- data/admins/pageflow/user.rb +184 -0
- data/lib/pageflow/version.rb +1 -1
- data/vendor/assets/javascripts/audio5.min.js +957 -0
- data/vendor/assets/javascripts/cocktail.js +87 -0
- data/vendor/assets/javascripts/html5shiv-printshiv.js +500 -0
- data/vendor/assets/javascripts/iscroll.js +1811 -0
- data/vendor/assets/javascripts/jquery.backgroundSize.js +314 -0
- data/vendor/assets/javascripts/jquery.fullscreen.js +158 -0
- data/vendor/assets/javascripts/jquery.placeholder.js +175 -0
- data/vendor/assets/javascripts/jquery.simulate-events.js +312 -0
- data/vendor/assets/javascripts/polyfills/bind.js +21 -0
- data/vendor/assets/javascripts/polyfills/get_computed_style.js +16 -0
- metadata +85 -69
data/lib/pageflow/version.rb
CHANGED
@@ -0,0 +1,957 @@
|
|
1
|
+
/*!
|
2
|
+
* Audio5js: HTML5 Audio Compatibility Layer
|
3
|
+
* https://github.com/zohararad/audio5js
|
4
|
+
* License MIT (c) Zohar Arad 2013
|
5
|
+
*/
|
6
|
+
(function ($win, ns, factory) {
|
7
|
+
/*global define */
|
8
|
+
/*global swfobject */
|
9
|
+
"use strict";
|
10
|
+
|
11
|
+
if (typeof (module) !== 'undefined' && module.exports) { // CommonJS
|
12
|
+
module.exports = factory(ns, $win);
|
13
|
+
} else if (typeof (define) === 'function' && define.amd) { // AMD
|
14
|
+
define(function () {
|
15
|
+
return factory(ns, $win);
|
16
|
+
});
|
17
|
+
} else { // <script>
|
18
|
+
$win[ns] = factory(ns, $win);
|
19
|
+
}
|
20
|
+
|
21
|
+
}(window, 'Audio5js', function (ns, $win) {
|
22
|
+
|
23
|
+
"use strict";
|
24
|
+
|
25
|
+
var ActiveXObject = $win.ActiveXObject;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* AudioError Class
|
29
|
+
* @param {String} message error message
|
30
|
+
* @constructor
|
31
|
+
*/
|
32
|
+
function AudioError(message) {
|
33
|
+
this.message = message;
|
34
|
+
}
|
35
|
+
|
36
|
+
AudioError.prototype = new Error();
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Clones an object
|
40
|
+
* @param obj object to clone
|
41
|
+
* @return {Object} cloned object
|
42
|
+
*/
|
43
|
+
function cloneObject(obj) {
|
44
|
+
var clone = {}, i;
|
45
|
+
for (i in obj) {
|
46
|
+
if (typeof (obj[i]) === "object") {
|
47
|
+
clone[i] = cloneObject(obj[i]);
|
48
|
+
} else {
|
49
|
+
clone[i] = obj[i];
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return clone;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Extend an object with a mixin
|
57
|
+
* @param {Object} target target object to extend
|
58
|
+
* @param {Object} mixin object to mix into target
|
59
|
+
* @return {*} extended object
|
60
|
+
*/
|
61
|
+
var extend = function (target, mixin) {
|
62
|
+
var name, m = cloneObject(mixin);
|
63
|
+
for (name in m) {
|
64
|
+
if (m.hasOwnProperty(name)) {
|
65
|
+
target[name] = m[name];
|
66
|
+
}
|
67
|
+
}
|
68
|
+
return target;
|
69
|
+
};
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Extend an object's prototype with a mixin
|
73
|
+
* @param {Object} target target object to extend
|
74
|
+
* @param {Object} mixin object to mix into target
|
75
|
+
* @return {*} extended object
|
76
|
+
*/
|
77
|
+
var include = function (target, mixin) {
|
78
|
+
return extend(target.prototype, mixin);
|
79
|
+
};
|
80
|
+
|
81
|
+
var Pubsub = {
|
82
|
+
/**
|
83
|
+
* Subscribe to event on a channel
|
84
|
+
* @param {String} evt name of channel / event to subscribe
|
85
|
+
* @param {Function} fn the callback to execute on message publishing
|
86
|
+
* @param {Object} ctx the context in which the callback should be executed
|
87
|
+
*/
|
88
|
+
on: function (evt, fn, ctx) {
|
89
|
+
this.subscribe(evt, fn, ctx, false);
|
90
|
+
},
|
91
|
+
/**
|
92
|
+
* Subscribe to a one-time event on a channel
|
93
|
+
* @param {String} evt name of channel / event to subscribe
|
94
|
+
* @param {Function} fn the callback to execute on message publishing
|
95
|
+
* @param {Object} ctx the context in which the callback should be executed
|
96
|
+
*/
|
97
|
+
one: function(evt, fn, ctx) {
|
98
|
+
this.subscribe(evt, fn, ctx, true);
|
99
|
+
},
|
100
|
+
/**
|
101
|
+
* Unsubscribe from an event on a channel
|
102
|
+
* @param {String} evt name of channel / event to unsubscribe
|
103
|
+
* @param {Function} fn the callback used when subscribing to the event
|
104
|
+
*/
|
105
|
+
off: function (evt, fn) {
|
106
|
+
if (this.channels[evt] === undefined) { return; }
|
107
|
+
var i, l;
|
108
|
+
for (i = 0, l = this.channels[evt].length; i < l; i++) {
|
109
|
+
var sub = this.channels[evt][i].fn;
|
110
|
+
if (sub === fn) {
|
111
|
+
this.channels[evt].splice(i, 1);
|
112
|
+
break;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
},
|
116
|
+
/**
|
117
|
+
* Add event subscription to channel. Called by `on` and `one`
|
118
|
+
* @param {String} evt name of channel / event to subscribe
|
119
|
+
* @param {Function} fn the callback to execute on message publishing
|
120
|
+
* @param {Object} ctx the context in which the callback should be executed
|
121
|
+
* @param {Boolean} once indicate if event should be triggered once or not
|
122
|
+
*/
|
123
|
+
subscribe: function (evt, fn, ctx, once) {
|
124
|
+
if (this.channels === undefined) {
|
125
|
+
this.channels = {};
|
126
|
+
}
|
127
|
+
this.channels[evt] = this.channels[evt] || [];
|
128
|
+
this.channels[evt].push({fn: fn, ctx: ctx, once: (once || false)});
|
129
|
+
},
|
130
|
+
/**
|
131
|
+
* Publish a message on a channel. Accepts **args** after event name
|
132
|
+
* @param {String} evt name of channel / event to trigger
|
133
|
+
*/
|
134
|
+
trigger: function (evt) {
|
135
|
+
if (this.channels && this.channels.hasOwnProperty(evt)) {
|
136
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
137
|
+
var a = [];
|
138
|
+
while(this.channels[evt].length > 0) {
|
139
|
+
var sub = this.channels[evt].shift();
|
140
|
+
if (typeof (sub.fn) === 'function') {
|
141
|
+
sub.fn.apply(sub.ctx, args);
|
142
|
+
}
|
143
|
+
if ( !sub.once ){
|
144
|
+
a.push(sub);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
this.channels[evt] = a;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
};
|
151
|
+
|
152
|
+
var util = {
|
153
|
+
/**
|
154
|
+
* Flash embed code string with cross-browser support.
|
155
|
+
*/
|
156
|
+
flash_embed_code: (function () {
|
157
|
+
var prefix;
|
158
|
+
var s = '<param name="movie" value="$2?playerInstance=window.' + ns + '_flash.instances[\'$1\']&datetime=$3"/>' +
|
159
|
+
'<param name="wmode" value="transparent"/>' +
|
160
|
+
'<param name="allowscriptaccess" value="always" />' +
|
161
|
+
'</object>';
|
162
|
+
if (ActiveXObject) {
|
163
|
+
prefix = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="1" height="1" id="$1">';
|
164
|
+
} else {
|
165
|
+
prefix = '<object type="application/x-shockwave-flash" data="$2?playerInstance=window.' + ns + '_flash.instances[\'$1\']&datetime=$3" width="1" height="1" id="$1" >';
|
166
|
+
}
|
167
|
+
return prefix + s;
|
168
|
+
}()),
|
169
|
+
/**
|
170
|
+
* Check if browser supports audio mime type.
|
171
|
+
* @param {String} mime_type audio mime type to check
|
172
|
+
* @return {Boolean} whether browser supports passed audio mime type
|
173
|
+
*/
|
174
|
+
can_play: function (mime_type) {
|
175
|
+
var a = document.createElement('audio');
|
176
|
+
var mime_str;
|
177
|
+
switch (mime_type) {
|
178
|
+
case 'mp3':
|
179
|
+
mime_str = 'audio/mpeg; codecs="mp3"';
|
180
|
+
break;
|
181
|
+
case 'vorbis':
|
182
|
+
mime_str = 'audio/ogg; codecs="vorbis"';
|
183
|
+
break;
|
184
|
+
case 'opus':
|
185
|
+
mime_str = 'audio/ogg; codecs="opus"';
|
186
|
+
break;
|
187
|
+
case 'webm':
|
188
|
+
mime_str = 'audio/webm; codecs="vorbis"';
|
189
|
+
break;
|
190
|
+
case 'mp4':
|
191
|
+
mime_str = 'audio/mp4; codecs="mp4a.40.5"';
|
192
|
+
break;
|
193
|
+
case 'wav':
|
194
|
+
mime_str = 'audio/wav; codecs="1"';
|
195
|
+
break;
|
196
|
+
}
|
197
|
+
if (mime_str === undefined) {
|
198
|
+
throw new Error('Unspecified Audio Mime Type');
|
199
|
+
} else {
|
200
|
+
return !!a.canPlayType && a.canPlayType(mime_str) !== '';
|
201
|
+
}
|
202
|
+
},
|
203
|
+
/**
|
204
|
+
* Boolean flag indicating whether the browser has Flash installed or not
|
205
|
+
*/
|
206
|
+
has_flash: (function () {
|
207
|
+
var r = false;
|
208
|
+
if (navigator.plugins && navigator.plugins.length && navigator.plugins['Shockwave Flash']) {
|
209
|
+
r = true;
|
210
|
+
} else if (navigator.mimeTypes && navigator.mimeTypes.length) {
|
211
|
+
var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
|
212
|
+
r = mimeType && mimeType.enabledPlugin;
|
213
|
+
} else {
|
214
|
+
try {
|
215
|
+
var ax = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
|
216
|
+
r = typeof (ax) === 'object';
|
217
|
+
} catch (e) {}
|
218
|
+
}
|
219
|
+
return r;
|
220
|
+
}()),
|
221
|
+
/**
|
222
|
+
* Embed Flash MP3 player SWF to DOM
|
223
|
+
* @param {String} swf_location location of MP3 player SWF
|
224
|
+
* @param {String} id swf unique ID used for resolving callbacks from ExternalInterface to Javascript
|
225
|
+
*/
|
226
|
+
embedFlash: function (swf_location, id) {
|
227
|
+
var d = document.createElement('div');
|
228
|
+
d.style.position = 'absolute';
|
229
|
+
d.style.width = '1px';
|
230
|
+
d.style.height = '1px';
|
231
|
+
d.style.top = '1px';
|
232
|
+
document.body.appendChild(d);
|
233
|
+
if(typeof($win.swfobject) === 'object'){
|
234
|
+
var fv = {
|
235
|
+
playerInstance: 'window.'+ ns + '_flash.instances["'+id+'"]'
|
236
|
+
};
|
237
|
+
var params = {
|
238
|
+
allowscriptaccess: 'always',
|
239
|
+
wmode: 'transparent'
|
240
|
+
};
|
241
|
+
d.innerHTML = '<div id="'+id+'"></div>';
|
242
|
+
swfobject.embedSWF(swf_location + '?ts='+(new Date().getTime() + Math.random()), id, "1", "1", "9.0.0", null, fv, params);
|
243
|
+
} else {
|
244
|
+
var flashSource = this.flash_embed_code.replace(/\$1/g, id);
|
245
|
+
flashSource = flashSource.replace(/\$2/g, swf_location);
|
246
|
+
flashSource = flashSource.replace(/\$3/g, (new Date().getTime() + Math.random())); // Ensure swf is not pulled from cache
|
247
|
+
d.innerHTML = flashSource;
|
248
|
+
}
|
249
|
+
return document.getElementById(id);
|
250
|
+
},
|
251
|
+
/**
|
252
|
+
* Formats seconds into a time string hh:mm:ss.
|
253
|
+
* @param {Number} seconds seconds to format as string
|
254
|
+
* @return {String} formatted time string
|
255
|
+
*/
|
256
|
+
formatTime: function (seconds) {
|
257
|
+
var hours = parseInt(seconds / 3600, 10) % 24;
|
258
|
+
var minutes = parseInt(seconds / 60, 10) % 60;
|
259
|
+
var secs = parseInt(seconds % 60, 10);
|
260
|
+
var result, fragment = (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs);
|
261
|
+
if (hours > 0) {
|
262
|
+
result = (hours < 10 ? "0" + hours : hours) + ":" + fragment;
|
263
|
+
} else {
|
264
|
+
result = fragment;
|
265
|
+
}
|
266
|
+
return result;
|
267
|
+
}
|
268
|
+
};
|
269
|
+
|
270
|
+
util.use_flash = util.can_play('mp3');
|
271
|
+
|
272
|
+
var Audio5js, FlashAudioPlayer, HTML5AudioPlayer;
|
273
|
+
|
274
|
+
/**
|
275
|
+
* Common audio attributes object. Mixed into audio players.
|
276
|
+
* @type {Object}
|
277
|
+
*/
|
278
|
+
var AudioAttributes = {
|
279
|
+
playing: false, /** {Boolean} player playback state */
|
280
|
+
vol: 1, /** {Float} audio volume */
|
281
|
+
duration: 0, /** {Float} audio duration (sec) */
|
282
|
+
position: 0, /** {Float} audio position (sec) */
|
283
|
+
load_percent: 0, /** {Float} audio file load percent (%) */
|
284
|
+
seekable: null, /** {Boolean} is loaded audio seekable */
|
285
|
+
ready: null /** {Boolean} is loaded audio seekable */
|
286
|
+
};
|
287
|
+
|
288
|
+
/**
|
289
|
+
* Global object holding flash-based player instances.
|
290
|
+
* Used to create a bridge between Flash's ExternalInterface calls and FlashAudioPlayer instances
|
291
|
+
* @type {Object}
|
292
|
+
*/
|
293
|
+
var globalAudio5Flash = $win[ns + '_flash'] = $win[ns + '_flash'] || {
|
294
|
+
instances: { }, /** FlashAudioPlayer instance hash */
|
295
|
+
count: 0 /** FlashAudioPlayer instance count */
|
296
|
+
};
|
297
|
+
|
298
|
+
/**
|
299
|
+
* Flash MP3 Audio Player Class
|
300
|
+
* @constructor
|
301
|
+
*/
|
302
|
+
FlashAudioPlayer = function () {
|
303
|
+
if (util.use_flash && !util.has_flash) {
|
304
|
+
throw new Error('Flash Plugin Missing');
|
305
|
+
}
|
306
|
+
};
|
307
|
+
|
308
|
+
FlashAudioPlayer.prototype = {
|
309
|
+
/**
|
310
|
+
* Initialize the player
|
311
|
+
* @param {String} swf_src path to audio player SWF file
|
312
|
+
*/
|
313
|
+
init: function (swf_src) {
|
314
|
+
globalAudio5Flash.count += 1;
|
315
|
+
this.id = ns + globalAudio5Flash.count;
|
316
|
+
globalAudio5Flash.instances[this.id] = this;
|
317
|
+
this.embed(swf_src);
|
318
|
+
},
|
319
|
+
/**
|
320
|
+
* Embed audio player SWF in page and assign reference to audio instance variable
|
321
|
+
* @param {String} swf_src path to audio player SWF file
|
322
|
+
*/
|
323
|
+
embed: function (swf_src) {
|
324
|
+
util.embedFlash(swf_src, this.id);
|
325
|
+
},
|
326
|
+
/**
|
327
|
+
* ExternalInterface callback indicating SWF is ready
|
328
|
+
*/
|
329
|
+
eiReady: function () {
|
330
|
+
this.audio = document.getElementById(this.id);
|
331
|
+
this.trigger('ready');
|
332
|
+
},
|
333
|
+
/**
|
334
|
+
* ExternalInterface audio load started callback. Fires when audio starts loading.
|
335
|
+
*/
|
336
|
+
eiLoadStart: function(){
|
337
|
+
this.trigger('loadstart');
|
338
|
+
},
|
339
|
+
/**
|
340
|
+
* ExternalInterface audio metadata loaded callback. Fires when audio ID3 tags have been loaded.
|
341
|
+
*/
|
342
|
+
eiLoadedMetadata: function(){
|
343
|
+
this.trigger('loadedmetadata');
|
344
|
+
},
|
345
|
+
/**
|
346
|
+
* ExternalInterface audio can play callback. Fires when audio can be played.
|
347
|
+
*/
|
348
|
+
eiCanPlay: function () {
|
349
|
+
this.trigger('canplay');
|
350
|
+
},
|
351
|
+
/**
|
352
|
+
* ExternalInterface timeupdate callback. Fires as long as playhead position is updated (audio is being played).
|
353
|
+
* @param {Float} position audio playback position (sec)
|
354
|
+
* @param {Float} duration audio total duration (sec)
|
355
|
+
* @param {Boolean} seekable is audio seekable or not (download or streaming)
|
356
|
+
*/
|
357
|
+
eiTimeUpdate: function (position, duration, seekable) {
|
358
|
+
this.position = position;
|
359
|
+
this.duration = duration;
|
360
|
+
this.seekable = seekable;
|
361
|
+
this.trigger('timeupdate', position, (this.seekable ? duration : null));
|
362
|
+
},
|
363
|
+
/**
|
364
|
+
* ExternalInterface download progress callback. Fires as long as audio file is downloaded by browser.
|
365
|
+
* @param {Float} percent audio download percent
|
366
|
+
*/
|
367
|
+
eiProgress: function (percent) {
|
368
|
+
this.load_percent = percent;
|
369
|
+
this.trigger('progress', percent);
|
370
|
+
},
|
371
|
+
/**
|
372
|
+
* ExternalInterface audio load error callback.
|
373
|
+
* @param {String} msg error message
|
374
|
+
*/
|
375
|
+
eiLoadError: function (msg) {
|
376
|
+
this.trigger('error', msg);
|
377
|
+
},
|
378
|
+
/**
|
379
|
+
* ExternalInterface audio play callback. Fires when audio starts playing.
|
380
|
+
*/
|
381
|
+
eiPlay: function () {
|
382
|
+
this.playing = true;
|
383
|
+
this.trigger('play');
|
384
|
+
},
|
385
|
+
/**
|
386
|
+
* ExternalInterface audio pause callback. Fires when audio is paused.
|
387
|
+
*/
|
388
|
+
eiPause: function () {
|
389
|
+
this.playing = false;
|
390
|
+
this.trigger('pause');
|
391
|
+
},
|
392
|
+
/**
|
393
|
+
* ExternalInterface audio ended callback. Fires when audio playback ended.
|
394
|
+
*/
|
395
|
+
eiEnded: function () {
|
396
|
+
this.playing = false;
|
397
|
+
this.trigger('ended');
|
398
|
+
},
|
399
|
+
/**
|
400
|
+
* ExternalInterface audio seeking callback. Fires when audio is being seeked.
|
401
|
+
*/
|
402
|
+
eiSeeking: function(){
|
403
|
+
this.trigger('seeking');
|
404
|
+
},
|
405
|
+
/**
|
406
|
+
* ExternalInterface audio seeked callback. Fires when audio has been seeked.
|
407
|
+
*/
|
408
|
+
eiSeeked: function(){
|
409
|
+
this.trigger('seeked');
|
410
|
+
},
|
411
|
+
/**
|
412
|
+
* Resets audio position and parameters. Invoked once audio is loaded.
|
413
|
+
*/
|
414
|
+
reset: function () {
|
415
|
+
this.seekable = false;
|
416
|
+
this.duration = 0;
|
417
|
+
this.position = 0;
|
418
|
+
this.load_percent = 0;
|
419
|
+
},
|
420
|
+
/**
|
421
|
+
* Load audio from url.
|
422
|
+
* @param {String} url URL of audio to load
|
423
|
+
*/
|
424
|
+
load: function (url) {
|
425
|
+
this.reset();
|
426
|
+
this.audio.load(url);
|
427
|
+
},
|
428
|
+
/**
|
429
|
+
* Play audio
|
430
|
+
*/
|
431
|
+
play: function () {
|
432
|
+
this.audio.pplay();
|
433
|
+
},
|
434
|
+
/**
|
435
|
+
* Pause audio
|
436
|
+
*/
|
437
|
+
pause: function () {
|
438
|
+
this.audio.ppause();
|
439
|
+
},
|
440
|
+
/**
|
441
|
+
* Get / Set audio volume
|
442
|
+
* @param {Float} v audio volume to set between 0 - 1.
|
443
|
+
* @return {Float} current audio volume
|
444
|
+
*/
|
445
|
+
volume: function (v) {
|
446
|
+
if (v !== undefined && !isNaN(parseInt(v, 10))) {
|
447
|
+
this.audio.setVolume(v);
|
448
|
+
this.vol = v;
|
449
|
+
} else {
|
450
|
+
return this.vol;
|
451
|
+
}
|
452
|
+
},
|
453
|
+
/**
|
454
|
+
* Seek audio to position
|
455
|
+
* @param {Float} position audio position in seconds to seek to.
|
456
|
+
*/
|
457
|
+
seek: function (position) {
|
458
|
+
try {
|
459
|
+
this.audio.seekTo(position);
|
460
|
+
this.position = position;
|
461
|
+
} catch (e) {}
|
462
|
+
}
|
463
|
+
};
|
464
|
+
|
465
|
+
include(FlashAudioPlayer, Pubsub);
|
466
|
+
include(FlashAudioPlayer, AudioAttributes);
|
467
|
+
|
468
|
+
/**
|
469
|
+
* HTML5 Audio Player
|
470
|
+
* @constructor
|
471
|
+
*/
|
472
|
+
HTML5AudioPlayer = function () {};
|
473
|
+
|
474
|
+
HTML5AudioPlayer.prototype = {
|
475
|
+
/**
|
476
|
+
* Initialize the player instance
|
477
|
+
*/
|
478
|
+
init: function (reusedTag) {
|
479
|
+
this.reusedTag = reusedTag;
|
480
|
+
this.trigger('ready');
|
481
|
+
},
|
482
|
+
createAudio: function(){
|
483
|
+
this.audio = this.reusedTag || new Audio();
|
484
|
+
this.audio.autoplay = false;
|
485
|
+
this.audio.preload = 'auto';
|
486
|
+
this.audio.autobuffer = true;
|
487
|
+
this.bindEvents();
|
488
|
+
},
|
489
|
+
destroyAudio: function(){
|
490
|
+
if(this.audio){
|
491
|
+
this.unbindEvents();
|
492
|
+
delete this.audio;
|
493
|
+
}
|
494
|
+
},
|
495
|
+
/**
|
496
|
+
* Bind DOM events to Audio object
|
497
|
+
*/
|
498
|
+
bindEvents: function () {
|
499
|
+
this.audio.addEventListener('loadstart', this.onLoadStart.bind(this));
|
500
|
+
this.audio.addEventListener('canplay', this.onLoad.bind(this));
|
501
|
+
this.audio.addEventListener('loadedmetadata', this.onLoadedMetadata.bind(this));
|
502
|
+
this.audio.addEventListener('playing', this.onPlay.bind(this));
|
503
|
+
this.audio.addEventListener('pause', this.onPause.bind(this));
|
504
|
+
this.audio.addEventListener('ended', this.onEnded.bind(this));
|
505
|
+
this.audio.addEventListener('error', this.onError.bind(this));
|
506
|
+
this.audio.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
507
|
+
this.audio.addEventListener('seeking', this.onSeeking.bind(this));
|
508
|
+
this.audio.addEventListener('seeked', this.onSeeked.bind(this));
|
509
|
+
},
|
510
|
+
unbindEvents: function(){
|
511
|
+
this.audio.removeEventListener('loadstart', this.onLoadStart.bind(this));
|
512
|
+
this.audio.removeEventListener('canplay', this.onLoad.bind(this));
|
513
|
+
this.audio.removeEventListener('loadedmetadata', this.onLoadedMetadata.bind(this));
|
514
|
+
this.audio.removeEventListener('playing', this.onPlay.bind(this));
|
515
|
+
this.audio.removeEventListener('pause', this.onPause.bind(this));
|
516
|
+
this.audio.removeEventListener('ended', this.onEnded.bind(this));
|
517
|
+
this.audio.removeEventListener('error', this.onError.bind(this));
|
518
|
+
this.audio.removeEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
519
|
+
this.audio.removeEventListener('seeking', this.onSeeking.bind(this));
|
520
|
+
this.audio.removeEventListener('seeked', this.onSeeked.bind(this));
|
521
|
+
},
|
522
|
+
/**
|
523
|
+
* Audio load start event handler. Triggered when audio starts loading
|
524
|
+
*/
|
525
|
+
onLoadStart: function(){
|
526
|
+
this.trigger('loadstart');
|
527
|
+
},
|
528
|
+
/**
|
529
|
+
* Audio canplay event handler. Triggered when audio is loaded and can be played.
|
530
|
+
* Resets player parameters and starts audio download progress timer.
|
531
|
+
*/
|
532
|
+
onLoad: function () {
|
533
|
+
this.seekable = this.audio.seekable && this.audio.seekable.length > 0;
|
534
|
+
if (this.seekable) {
|
535
|
+
this.timer = setInterval(this.onProgress.bind(this), 250);
|
536
|
+
}
|
537
|
+
this.trigger('canplay');
|
538
|
+
},
|
539
|
+
/**
|
540
|
+
* Audio ID3 load event handler. Triggered when ID3 metadata is loaded.
|
541
|
+
*/
|
542
|
+
onLoadedMetadata: function(){
|
543
|
+
this.trigger('loadedmetadata');
|
544
|
+
},
|
545
|
+
/**
|
546
|
+
* Audio play event handler. Triggered when audio starts playing.
|
547
|
+
*/
|
548
|
+
onPlay: function () {
|
549
|
+
this.playing = true;
|
550
|
+
this.trigger('play');
|
551
|
+
},
|
552
|
+
/**
|
553
|
+
* Audio pause event handler. Triggered when audio is paused.
|
554
|
+
*/
|
555
|
+
onPause: function () {
|
556
|
+
this.playing = false;
|
557
|
+
this.trigger('pause');
|
558
|
+
},
|
559
|
+
/**
|
560
|
+
* Audio ended event handler. Triggered when audio playback has ended.
|
561
|
+
*/
|
562
|
+
onEnded: function () {
|
563
|
+
this.playing = false;
|
564
|
+
this.trigger('ended');
|
565
|
+
},
|
566
|
+
/**
|
567
|
+
* Audio timeupdate event handler. Triggered as long as playhead position is updated (audio is being played).
|
568
|
+
*/
|
569
|
+
onTimeUpdate: function () {
|
570
|
+
if (this.audio.buffered !== null && this.playing) {
|
571
|
+
this.position = this.audio.currentTime;
|
572
|
+
this.duration = this.audio.duration === Infinity ? null : this.audio.duration;
|
573
|
+
this.trigger('timeupdate', this.position, this.duration);
|
574
|
+
}
|
575
|
+
},
|
576
|
+
/**
|
577
|
+
* Audio download progress timer callback. Check audio's download percentage.
|
578
|
+
* Called periodically as soon as the audio loads and can be played.
|
579
|
+
* Cancelled when audio has fully download or when a new audio file has been loaded to the player.
|
580
|
+
*/
|
581
|
+
onProgress: function () {
|
582
|
+
if (this.audio.buffered !== null) {
|
583
|
+
this.load_percent = parseInt(((this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration) * 100), 10);
|
584
|
+
this.trigger('progress', this.load_percent);
|
585
|
+
if (this.load_percent >= 100) {
|
586
|
+
this.clearLoadProgress();
|
587
|
+
}
|
588
|
+
}
|
589
|
+
},
|
590
|
+
/**
|
591
|
+
* Audio error event handler
|
592
|
+
* @param e error event
|
593
|
+
*/
|
594
|
+
onError: function (e) {
|
595
|
+
this.trigger('error', e);
|
596
|
+
},
|
597
|
+
/**
|
598
|
+
* Audio seeking event handler. Triggered when audio seek starts.
|
599
|
+
*/
|
600
|
+
onSeeking: function(){
|
601
|
+
this.trigger('seeking');
|
602
|
+
},
|
603
|
+
/**
|
604
|
+
* Audio seeked event handler. Triggered when audio has been seeked.
|
605
|
+
*/
|
606
|
+
onSeeked: function(){
|
607
|
+
this.trigger('seeked');
|
608
|
+
},
|
609
|
+
/**
|
610
|
+
* Clears periodical audio download progress callback.
|
611
|
+
*/
|
612
|
+
clearLoadProgress: function () {
|
613
|
+
if (this.timer !== undefined) {
|
614
|
+
clearInterval(this.timer);
|
615
|
+
delete this.timer;
|
616
|
+
}
|
617
|
+
},
|
618
|
+
/**
|
619
|
+
* Resets audio position and parameters.
|
620
|
+
*/
|
621
|
+
reset: function () {
|
622
|
+
this.clearLoadProgress();
|
623
|
+
this.seekable = false;
|
624
|
+
this.duration = 0;
|
625
|
+
this.position = 0;
|
626
|
+
this.load_percent = 0;
|
627
|
+
},
|
628
|
+
/**
|
629
|
+
* Load audio from url.
|
630
|
+
* @param {String} url URL of audio to load
|
631
|
+
*/
|
632
|
+
load: function (url) {
|
633
|
+
this.reset();
|
634
|
+
this.destroyAudio();
|
635
|
+
this.createAudio();
|
636
|
+
this.audio.setAttribute('src', url);
|
637
|
+
this.audio.load();
|
638
|
+
},
|
639
|
+
/**
|
640
|
+
* Play audio
|
641
|
+
*/
|
642
|
+
play: function () {
|
643
|
+
this.audio.play();
|
644
|
+
},
|
645
|
+
/**
|
646
|
+
* Pause audio
|
647
|
+
*/
|
648
|
+
pause: function () {
|
649
|
+
this.audio.pause();
|
650
|
+
},
|
651
|
+
/**
|
652
|
+
* Get / Set audio volume
|
653
|
+
* @param {Float} v audio volume to set between 0 - 1.
|
654
|
+
* @return {Float} current audio volume
|
655
|
+
*/
|
656
|
+
volume: function (v) {
|
657
|
+
if (v !== undefined && !isNaN(parseInt(v, 10))) {
|
658
|
+
var vol = v < 0 ? 0 : Math.min(1, v);
|
659
|
+
this.audio.volume = vol;
|
660
|
+
this.vol = vol;
|
661
|
+
} else {
|
662
|
+
return this.vol;
|
663
|
+
}
|
664
|
+
},
|
665
|
+
/**
|
666
|
+
* Seek audio to position
|
667
|
+
* @param {Float} position audio position in seconds to seek to.
|
668
|
+
*/
|
669
|
+
seek: function (position) {
|
670
|
+
var playing = this.playing;
|
671
|
+
this.position = position;
|
672
|
+
this.audio.currentTime = position;
|
673
|
+
if (playing) {
|
674
|
+
this.play();
|
675
|
+
} else {
|
676
|
+
if (this.audio.buffered !== null) {
|
677
|
+
this.trigger('timeupdate', this.position, this.duration);
|
678
|
+
}
|
679
|
+
}
|
680
|
+
}
|
681
|
+
};
|
682
|
+
|
683
|
+
include(HTML5AudioPlayer, Pubsub);
|
684
|
+
include(HTML5AudioPlayer, AudioAttributes);
|
685
|
+
|
686
|
+
/**
|
687
|
+
* Default settings object
|
688
|
+
* @type {Object}
|
689
|
+
*/
|
690
|
+
var settings = {
|
691
|
+
/**
|
692
|
+
* {String} path to Flash audio player SWF file
|
693
|
+
*/
|
694
|
+
swf_path: '/swf/audiojs.swf',
|
695
|
+
/**
|
696
|
+
* {Boolean} flag indicating whether to throw errors to the page or trigger an error event
|
697
|
+
*/
|
698
|
+
throw_errors: true,
|
699
|
+
/**
|
700
|
+
* {Boolean} flag indicating whether to format player duration and position to hh:mm:ss or pass as raw seconds
|
701
|
+
*/
|
702
|
+
format_time: true,
|
703
|
+
/**
|
704
|
+
* {Array} list of codecs to try and use when initializing the player. Used to selectively initialize the internal audio player based on codec support
|
705
|
+
*/
|
706
|
+
codecs: ['mp3']
|
707
|
+
};
|
708
|
+
|
709
|
+
/**
|
710
|
+
* Audio5js Audio Player
|
711
|
+
* @param {Object} s player settings object
|
712
|
+
* @constructor
|
713
|
+
*/
|
714
|
+
Audio5js = function (s) {
|
715
|
+
s = s || {};
|
716
|
+
var k;
|
717
|
+
for (k in settings) {
|
718
|
+
if (settings.hasOwnProperty(k) && !s.hasOwnProperty(k)) {
|
719
|
+
s[k] = settings[k];
|
720
|
+
}
|
721
|
+
}
|
722
|
+
this.init(s);
|
723
|
+
};
|
724
|
+
|
725
|
+
/**
|
726
|
+
* Check if browser can play a given audio mime type.
|
727
|
+
* @param {String} mime_type audio mime type to check.
|
728
|
+
* @return {Boolean} is audio mime type supported by browser or not
|
729
|
+
*/
|
730
|
+
Audio5js.can_play = function (mime_type) {
|
731
|
+
return util.can_play(mime_type);
|
732
|
+
};
|
733
|
+
|
734
|
+
Audio5js.prototype = {
|
735
|
+
/**
|
736
|
+
* Initialize player instance.
|
737
|
+
* @param {Object} s player settings object
|
738
|
+
*/
|
739
|
+
init: function (s) {
|
740
|
+
this.formatTime = util.formatTime;
|
741
|
+
this.ready = false;
|
742
|
+
this.settings = s;
|
743
|
+
this.audio = this.getPlayer();
|
744
|
+
this.bindAudioEvents();
|
745
|
+
if (this.settings.use_flash) {
|
746
|
+
this.audio.init(s.swf_path);
|
747
|
+
} else {
|
748
|
+
this.audio.init(s.reusedTag);
|
749
|
+
}
|
750
|
+
},
|
751
|
+
/**
|
752
|
+
* Gets a new audio player instance based on codec support as defined in settings.codecs array.
|
753
|
+
* Defaults to MP3 player either HTML or Flash based.
|
754
|
+
* @return {FlashAudioPlayer,HTML5AudioPlayer} audio player instance
|
755
|
+
*/
|
756
|
+
getPlayer: function () {
|
757
|
+
var i, l, player;
|
758
|
+
for (i = 0, l = this.settings.codecs.length; i < l; i++) {
|
759
|
+
var codec = this.settings.codecs[i];
|
760
|
+
if (Audio5js.can_play(codec)) {
|
761
|
+
player = new HTML5AudioPlayer();
|
762
|
+
this.settings.use_flash = false;
|
763
|
+
this.settings.player = {
|
764
|
+
engine: 'html',
|
765
|
+
codec: codec
|
766
|
+
};
|
767
|
+
break;
|
768
|
+
}
|
769
|
+
}
|
770
|
+
if (player === undefined) {
|
771
|
+
// here we double check for mp3 support instead of defaulting to Flash in case user overrode the settings.codecs array with an empty array.
|
772
|
+
this.settings.use_flash = !Audio5js.can_play('mp3');
|
773
|
+
player = this.settings.use_flash ? new FlashAudioPlayer() : new HTML5AudioPlayer();
|
774
|
+
this.settings.player = {
|
775
|
+
engine: (this.settings.use_flash ? 'flash' : 'html'),
|
776
|
+
codec: 'mp3'
|
777
|
+
};
|
778
|
+
}
|
779
|
+
return player;
|
780
|
+
},
|
781
|
+
/**
|
782
|
+
* Bind events from audio object to internal callbacks
|
783
|
+
*/
|
784
|
+
bindAudioEvents: function () {
|
785
|
+
this.audio.on('ready', this.onReady, this);
|
786
|
+
this.audio.on('loadstart', this.onLoadStart, this);
|
787
|
+
this.audio.on('loadedmetadata', this.onLoadedMetadata, this);
|
788
|
+
this.audio.on('play', this.onPlay, this);
|
789
|
+
this.audio.on('pause', this.onPause, this);
|
790
|
+
this.audio.on('ended', this.onEnded, this);
|
791
|
+
this.audio.on('canplay', this.onCanPlay, this);
|
792
|
+
this.audio.on('timeupdate', this.onTimeUpdate, this);
|
793
|
+
this.audio.on('progress', this.onProgress, this);
|
794
|
+
this.audio.on('error', this.onError, this);
|
795
|
+
this.audio.on('seeking', this.onSeeking, this);
|
796
|
+
this.audio.on('seeked', this.onSeeked, this);
|
797
|
+
},
|
798
|
+
/**
|
799
|
+
* Load audio from URL
|
800
|
+
* @param {String} url URL of audio to load
|
801
|
+
*/
|
802
|
+
load: function (url) {
|
803
|
+
var f = function(u){
|
804
|
+
this.audio.load(u);
|
805
|
+
this.trigger('load');
|
806
|
+
}.bind(this, url);
|
807
|
+
|
808
|
+
if(this.ready){
|
809
|
+
f();
|
810
|
+
} else {
|
811
|
+
this.on('ready', f);
|
812
|
+
}
|
813
|
+
},
|
814
|
+
/**
|
815
|
+
* Play audio
|
816
|
+
*/
|
817
|
+
play: function () {
|
818
|
+
if(!this.playing){
|
819
|
+
this.audio.play();
|
820
|
+
}
|
821
|
+
},
|
822
|
+
/**
|
823
|
+
* Pause audio
|
824
|
+
*/
|
825
|
+
pause: function () {
|
826
|
+
if(this.playing){
|
827
|
+
this.audio.pause();
|
828
|
+
}
|
829
|
+
},
|
830
|
+
/**
|
831
|
+
* Toggle audio play / pause
|
832
|
+
*/
|
833
|
+
playPause: function () {
|
834
|
+
this[this.playing ? 'pause' : 'play']();
|
835
|
+
},
|
836
|
+
/**
|
837
|
+
* Get / Set audio volume
|
838
|
+
* @param {Float} v audio volume to set between 0 - 1.
|
839
|
+
* @return {Float} current audio volume
|
840
|
+
*/
|
841
|
+
volume: function (v) {
|
842
|
+
if (v !== undefined && !isNaN(parseInt(v, 10))) {
|
843
|
+
this.audio.volume(v);
|
844
|
+
this.vol = v;
|
845
|
+
} else {
|
846
|
+
return this.vol;
|
847
|
+
}
|
848
|
+
},
|
849
|
+
/**
|
850
|
+
* Seek audio to position
|
851
|
+
* @param {Float} position audio position in seconds to seek to.
|
852
|
+
*/
|
853
|
+
seek: function (position) {
|
854
|
+
this.audio.seek(position);
|
855
|
+
this.position = position;
|
856
|
+
},
|
857
|
+
/**
|
858
|
+
* Callback for audio ready event. Indicates audio is ready for playback.
|
859
|
+
* Looks for ready callback in settings object and invokes it in the context of player instance
|
860
|
+
*/
|
861
|
+
onReady: function () {
|
862
|
+
this.ready = true;
|
863
|
+
if (typeof (this.settings.ready) === 'function') {
|
864
|
+
this.settings.ready.call(this, this.settings.player);
|
865
|
+
}
|
866
|
+
this.trigger('ready');
|
867
|
+
},
|
868
|
+
/**
|
869
|
+
* Audio load start event handler
|
870
|
+
*/
|
871
|
+
onLoadStart: function(){
|
872
|
+
this.trigger('loadstart');
|
873
|
+
},
|
874
|
+
/**
|
875
|
+
* Audio metadata loaded event handler
|
876
|
+
*/
|
877
|
+
onLoadedMetadata: function(){
|
878
|
+
this.trigger('loadedmetadata');
|
879
|
+
},
|
880
|
+
/**
|
881
|
+
* Audio play event handler
|
882
|
+
*/
|
883
|
+
onPlay: function () {
|
884
|
+
this.playing = true;
|
885
|
+
this.trigger('play');
|
886
|
+
},
|
887
|
+
/**
|
888
|
+
* Audio pause event handler
|
889
|
+
*/
|
890
|
+
onPause: function () {
|
891
|
+
this.playing = false;
|
892
|
+
this.trigger('pause');
|
893
|
+
},
|
894
|
+
/**
|
895
|
+
* Playback end event handler
|
896
|
+
*/
|
897
|
+
onEnded: function () {
|
898
|
+
this.playing = false;
|
899
|
+
this.trigger('ended');
|
900
|
+
},
|
901
|
+
/**
|
902
|
+
* Audio error event handler
|
903
|
+
*/
|
904
|
+
onError: function () {
|
905
|
+
var error = new AudioError('Audio Error. Failed to Load Audio');
|
906
|
+
if (this.settings.throw_errors) {
|
907
|
+
throw error;
|
908
|
+
} else {
|
909
|
+
this.trigger('error', error);
|
910
|
+
}
|
911
|
+
},
|
912
|
+
/**
|
913
|
+
* Audio canplay event handler. Triggered when enough audio has been loaded to by played.
|
914
|
+
*/
|
915
|
+
onCanPlay: function () {
|
916
|
+
this.trigger('canplay');
|
917
|
+
},
|
918
|
+
/**
|
919
|
+
* Audio seeking event handler
|
920
|
+
*/
|
921
|
+
onSeeking: function(){
|
922
|
+
this.trigger('seeking');
|
923
|
+
},
|
924
|
+
/**
|
925
|
+
* Audio seeked event handler
|
926
|
+
*/
|
927
|
+
onSeeked: function(){
|
928
|
+
this.trigger('seeked');
|
929
|
+
},
|
930
|
+
/**
|
931
|
+
* Playback time update event handler
|
932
|
+
* @param {Float} position play head position (sec)
|
933
|
+
* @param {Float} duration audio duration (sec)
|
934
|
+
*/
|
935
|
+
onTimeUpdate: function (position, duration) {
|
936
|
+
this.position = this.settings.format_time ? util.formatTime(position) : position;
|
937
|
+
if (this.duration !== duration) {
|
938
|
+
this.duration = this.settings.format_time && duration !== null ? util.formatTime(duration) : duration;
|
939
|
+
}
|
940
|
+
this.trigger('timeupdate', this.position, this.duration);
|
941
|
+
},
|
942
|
+
/**
|
943
|
+
* Audio download progress event handler
|
944
|
+
* @param {Float} loaded audio download percent
|
945
|
+
*/
|
946
|
+
onProgress: function (loaded) {
|
947
|
+
this.load_percent = loaded;
|
948
|
+
this.trigger('progress', loaded);
|
949
|
+
}
|
950
|
+
};
|
951
|
+
|
952
|
+
include(Audio5js, Pubsub);
|
953
|
+
include(Audio5js, AudioAttributes);
|
954
|
+
|
955
|
+
return Audio5js;
|
956
|
+
|
957
|
+
}));
|