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.

@@ -1,3 +1,3 @@
1
1
  module Pageflow
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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
+ }));