gluttonberg-core 2.5.7 → 2.5.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/app/assets/images/gb_360-button-pause-light.gif +0 -0
  2. data/app/assets/images/gb_360-button-pause-light.png +0 -0
  3. data/app/assets/images/gb_360-button-pause.gif +0 -0
  4. data/app/assets/images/gb_360-button-pause.png +0 -0
  5. data/app/assets/images/gb_360-button-play-light.gif +0 -0
  6. data/app/assets/images/gb_360-button-play-light.png +0 -0
  7. data/app/assets/images/gb_360-button-play.gif +0 -0
  8. data/app/assets/images/gb_360-button-play.png +0 -0
  9. data/app/assets/images/gb_360-button-vis-pause-light.gif +0 -0
  10. data/app/assets/images/gb_360-button-vis-pause-light.png +0 -0
  11. data/app/assets/images/gb_360-button-vis-pause.gif +0 -0
  12. data/app/assets/images/gb_360-button-vis-pause.png +0 -0
  13. data/app/assets/images/gb_360-button-vis-play-light.gif +0 -0
  14. data/app/assets/images/gb_360-button-vis-play-light.png +0 -0
  15. data/app/assets/images/gb_360-button-vis-play.gif +0 -0
  16. data/app/assets/images/gb_360-button-vis-play.png +0 -0
  17. data/app/assets/javascripts/gb_360player.js +1396 -0
  18. data/app/assets/javascripts/gb_application.js +49 -4
  19. data/app/assets/javascripts/gb_berniecode-animator.js +674 -0
  20. data/app/assets/javascripts/gb_soundmanager2-jsmin.js +106 -0
  21. data/app/assets/stylesheets/gb_360player.css +271 -0
  22. data/app/assets/stylesheets/gb_admin-override.sass +6 -0
  23. data/app/helpers/gluttonberg/asset_library.rb +5 -1
  24. data/app/views/gluttonberg/admin/asset_library/assets/show.html.haml +7 -1
  25. data/app/views/gluttonberg/admin/asset_library/shared/_asset_panels.html.haml +9 -3
  26. data/app/views/gluttonberg/admin/shared/_asset_panel.html.haml +12 -6
  27. data/app/views/layouts/gluttonberg.html.haml +4 -1
  28. data/lib/gluttonberg/version.rb +1 -1
  29. metadata +38 -18
@@ -0,0 +1,1396 @@
1
+ /**
2
+ *
3
+ * SoundManager 2 Demo: 360-degree / "donut player"
4
+ * ------------------------------------------------
5
+ * http://schillmania.com/projects/soundmanager2/
6
+ *
7
+ * An inline player with a circular UI.
8
+ * Based on the original SM2 inline player.
9
+ * Inspired by Apple's preview feature in the
10
+ * iTunes music store (iPhone), among others.
11
+ *
12
+ * Requires SoundManager 2 Javascript API.
13
+ * Also uses Bernie's Better Animation Class (BSD):
14
+ * http://www.berniecode.com/writing/animator.html
15
+ *
16
+ */
17
+
18
+ /*jslint white: false, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true, regexp: false, newcap: true, immed: true */
19
+ /*global document, window, soundManager, navigator */
20
+
21
+ var threeSixtyPlayer, // instance
22
+ ThreeSixtyPlayer; // constructor
23
+
24
+ (function(window) {
25
+
26
+ function ThreeSixtyPlayer() {
27
+
28
+ var self = this,
29
+ pl = this,
30
+ sm = soundManager, // soundManager instance
31
+ uA = navigator.userAgent,
32
+ isIE = (uA.match(/msie/i)),
33
+ isOpera = (uA.match(/opera/i)),
34
+ isSafari = (uA.match(/safari/i)),
35
+ isChrome = (uA.match(/chrome/i)),
36
+ isFirefox = (uA.match(/firefox/i)),
37
+ isTouchDevice = (uA.match(/ipad|iphone/i)),
38
+ hasRealCanvas = (typeof window.G_vmlCanvasManager === 'undefined' && typeof document.createElement('canvas').getContext('2d') !== 'undefined'),
39
+ // I dunno what Opera doesn't like about this. I'm probably doing it wrong.
40
+ fullCircle = (isOpera||isChrome?359.9:360);
41
+
42
+ // CSS class for ignoring MP3 links
43
+ this.excludeClass = 'threesixty-exclude';
44
+ this.links = [];
45
+ this.sounds = [];
46
+ this.soundsByURL = [];
47
+ this.indexByURL = [];
48
+ this.lastSound = null;
49
+ this.lastTouchedSound = null;
50
+ this.soundCount = 0;
51
+ this.oUITemplate = null;
52
+ this.oUIImageMap = null;
53
+ this.vuMeter = null;
54
+ this.callbackCount = 0;
55
+ this.peakDataHistory = [];
56
+
57
+ // 360player configuration options
58
+ this.config = {
59
+
60
+ playNext: false, // stop after one sound, or play through list until end
61
+ autoPlay: false, // start playing the first sound right away
62
+ allowMultiple: false, // let many sounds play at once (false = only one sound playing at a time)
63
+ loadRingColor: '#ccc', // how much has loaded
64
+ playRingColor: '#000', // how much has played
65
+ backgroundRingColor: '#eee', // color shown underneath load + play ("not yet loaded" color)
66
+
67
+ // optional segment/annotation (metadata) stuff..
68
+ segmentRingColor: 'rgba(255,255,255,0.33)', // metadata/annotation (segment) colors
69
+ segmentRingColorAlt: 'rgba(0,0,0,0.1)',
70
+ loadRingColorMetadata: '#ddd', // "annotations" load color
71
+ playRingColorMetadata: 'rgba(128,192,256,0.9)', // how much has played when metadata is present
72
+
73
+ circleDiameter: null, // set dynamically according to values from CSS
74
+ circleRadius: null,
75
+ animDuration: 500,
76
+ animTransition: window.Animator.tx.bouncy, // http://www.berniecode.com/writing/animator.html
77
+ showHMSTime: false, // hours:minutes:seconds vs. seconds-only
78
+ scaleFont: true, // also set the font size (if possible) while animating the circle
79
+
80
+ // optional: spectrum or EQ graph in canvas (not supported in IE <9, too slow via ExCanvas)
81
+ useWaveformData: false,
82
+ waveformDataColor: '#0099ff',
83
+ waveformDataDownsample: 3, // use only one in X (of a set of 256 values) - 1 means all 256
84
+ waveformDataOutside: false,
85
+ waveformDataConstrain: false, // if true, +ve values only - keep within inside circle
86
+ waveformDataLineRatio: 0.64,
87
+
88
+ // "spectrum frequency" option
89
+ useEQData: false,
90
+ eqDataColor: '#339933',
91
+ eqDataDownsample: 4, // use only one in X (of 256 values)
92
+ eqDataOutside: true,
93
+ eqDataLineRatio: 0.54,
94
+
95
+ // enable "amplifier" (canvas pulses like a speaker) effect
96
+ usePeakData: true,
97
+ peakDataColor: '#ff33ff',
98
+ peakDataOutside: true,
99
+ peakDataLineRatio: 0.5,
100
+
101
+ useAmplifier: true, // "pulse" like a speaker
102
+
103
+ fontSizeMax: null, // set according to CSS
104
+
105
+ scaleArcWidth: 1, // thickness factor of playback progress ring
106
+
107
+ useFavIcon: false // Experimental (also requires usePeakData: true).. Try to draw a "VU Meter" in the favicon area, if browser supports it (Firefox + Opera as of 2009)
108
+
109
+ };
110
+
111
+ this.css = {
112
+
113
+ // CSS class names appended to link during various states
114
+ sDefault: 'sm2_link', // default state
115
+ sBuffering: 'sm2_buffering',
116
+ sPlaying: 'sm2_playing',
117
+ sPaused: 'sm2_paused'
118
+
119
+ };
120
+
121
+ this.addEventHandler = (typeof window.addEventListener !== 'undefined' ? function(o, evtName, evtHandler) {
122
+ return o.addEventListener(evtName,evtHandler,false);
123
+ } : function(o, evtName, evtHandler) {
124
+ o.attachEvent('on'+evtName,evtHandler);
125
+ });
126
+
127
+ this.removeEventHandler = (typeof window.removeEventListener !== 'undefined' ? function(o, evtName, evtHandler) {
128
+ return o.removeEventListener(evtName,evtHandler,false);
129
+ } : function(o, evtName, evtHandler) {
130
+ return o.detachEvent('on'+evtName,evtHandler);
131
+ });
132
+
133
+ this.hasClass = function(o,cStr) {
134
+ return typeof(o.className)!=='undefined'?o.className.match(new RegExp('(\\s|^)'+cStr+'(\\s|$)')):false;
135
+ };
136
+
137
+ this.addClass = function(o,cStr) {
138
+
139
+ if (!o || !cStr || self.hasClass(o,cStr)) {
140
+ return false;
141
+ }
142
+ o.className = (o.className?o.className+' ':'')+cStr;
143
+
144
+ };
145
+
146
+ this.removeClass = function(o,cStr) {
147
+
148
+ if (!o || !cStr || !self.hasClass(o,cStr)) {
149
+ return false;
150
+ }
151
+ o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),'');
152
+
153
+ };
154
+
155
+ this.getElementsByClassName = function(className,tagNames,oParent) {
156
+
157
+ var doc = (oParent||document),
158
+ matches = [], i,j, nodes = [];
159
+ if (typeof tagNames !== 'undefined' && typeof tagNames !== 'string') {
160
+ for (i=tagNames.length; i--;) {
161
+ if (!nodes || !nodes[tagNames[i]]) {
162
+ nodes[tagNames[i]] = doc.getElementsByTagName(tagNames[i]);
163
+ }
164
+ }
165
+ } else if (tagNames) {
166
+ nodes = doc.getElementsByTagName(tagNames);
167
+ } else {
168
+ nodes = doc.all||doc.getElementsByTagName('*');
169
+ }
170
+ if (typeof(tagNames)!=='string') {
171
+ for (i=tagNames.length; i--;) {
172
+ for (j=nodes[tagNames[i]].length; j--;) {
173
+ if (self.hasClass(nodes[tagNames[i]][j],className)) {
174
+ matches.push(nodes[tagNames[i]][j]);
175
+ }
176
+ }
177
+ }
178
+ } else {
179
+ for (i=0; i<nodes.length; i++) {
180
+ if (self.hasClass(nodes[i],className)) {
181
+ matches.push(nodes[i]);
182
+ }
183
+ }
184
+ }
185
+ return matches;
186
+
187
+ };
188
+
189
+ this.getParentByNodeName = function(oChild,sParentNodeName) {
190
+
191
+ if (!oChild || !sParentNodeName) {
192
+ return false;
193
+ }
194
+ sParentNodeName = sParentNodeName.toLowerCase();
195
+ while (oChild.parentNode && sParentNodeName !== oChild.parentNode.nodeName.toLowerCase()) {
196
+ oChild = oChild.parentNode;
197
+ }
198
+ return (oChild.parentNode && sParentNodeName === oChild.parentNode.nodeName.toLowerCase()?oChild.parentNode:null);
199
+
200
+ };
201
+
202
+ this.getParentByClassName = function(oChild,sParentClassName) {
203
+
204
+ if (!oChild || !sParentClassName) {
205
+ return false;
206
+ }
207
+ while (oChild.parentNode && !self.hasClass(oChild.parentNode,sParentClassName)) {
208
+ oChild = oChild.parentNode;
209
+ }
210
+ return (oChild.parentNode && self.hasClass(oChild.parentNode,sParentClassName)?oChild.parentNode:null);
211
+
212
+ };
213
+
214
+ this.getSoundByURL = function(sURL) {
215
+ return (typeof self.soundsByURL[sURL] !== 'undefined'?self.soundsByURL[sURL]:null);
216
+ };
217
+
218
+ this.isChildOfNode = function(o,sNodeName) {
219
+
220
+ if (!o || !o.parentNode) {
221
+ return false;
222
+ }
223
+ sNodeName = sNodeName.toLowerCase();
224
+ do {
225
+ o = o.parentNode;
226
+ } while (o && o.parentNode && o.nodeName.toLowerCase() !== sNodeName);
227
+ return (o && o.nodeName.toLowerCase() === sNodeName?o:null);
228
+
229
+ };
230
+
231
+ this.isChildOfClass = function(oChild,oClass) {
232
+
233
+ if (!oChild || !oClass) {
234
+ return false;
235
+ }
236
+ while (oChild.parentNode && !self.hasClass(oChild,oClass)) {
237
+ oChild = self.findParent(oChild);
238
+ }
239
+ return (self.hasClass(oChild,oClass));
240
+
241
+ };
242
+
243
+ this.findParent = function(o) {
244
+
245
+ if (!o || !o.parentNode) {
246
+ return false;
247
+ }
248
+ o = o.parentNode;
249
+ if (o.nodeType === 2) {
250
+ while (o && o.parentNode && o.parentNode.nodeType === 2) {
251
+ o = o.parentNode;
252
+ }
253
+ }
254
+ return o;
255
+
256
+ };
257
+
258
+ this.getStyle = function(o,sProp) {
259
+
260
+ // http://www.quirksmode.org/dom/getstyles.html
261
+ try {
262
+ if (o.currentStyle) {
263
+ return o.currentStyle[sProp];
264
+ } else if (window.getComputedStyle) {
265
+ return document.defaultView.getComputedStyle(o,null).getPropertyValue(sProp);
266
+ }
267
+ } catch(e) {
268
+ // oh well
269
+ }
270
+ return null;
271
+
272
+ };
273
+
274
+ this.findXY = function(obj) {
275
+
276
+ var curleft = 0, curtop = 0;
277
+ do {
278
+ curleft += obj.offsetLeft;
279
+ curtop += obj.offsetTop;
280
+ } while (!!(obj = obj.offsetParent));
281
+ return [curleft,curtop];
282
+
283
+ };
284
+
285
+ this.getMouseXY = function(e) {
286
+
287
+ // http://www.quirksmode.org/js/events_properties.html
288
+ e = e?e:window.event;
289
+ if (isTouchDevice && e.touches) {
290
+ e = e.touches[0];
291
+ }
292
+ if (e.pageX || e.pageY) {
293
+ return [e.pageX,e.pageY];
294
+ } else if (e.clientX || e.clientY) {
295
+ return [e.clientX+self.getScrollLeft(),e.clientY+self.getScrollTop()];
296
+ }
297
+
298
+ };
299
+
300
+ this.getScrollLeft = function() {
301
+ return (document.body.scrollLeft+document.documentElement.scrollLeft);
302
+ };
303
+
304
+ this.getScrollTop = function() {
305
+ return (document.body.scrollTop+document.documentElement.scrollTop);
306
+ };
307
+
308
+ this.events = {
309
+
310
+ // handlers for sound events as they're started/stopped/played
311
+
312
+ play: function() {
313
+ pl.removeClass(this._360data.oUIBox,this._360data.className);
314
+ this._360data.className = pl.css.sPlaying;
315
+ pl.addClass(this._360data.oUIBox,this._360data.className);
316
+ self.fanOut(this);
317
+ },
318
+
319
+ stop: function() {
320
+ pl.removeClass(this._360data.oUIBox,this._360data.className);
321
+ this._360data.className = '';
322
+ self.fanIn(this);
323
+ },
324
+
325
+ pause: function() {
326
+ pl.removeClass(this._360data.oUIBox,this._360data.className);
327
+ this._360data.className = pl.css.sPaused;
328
+ pl.addClass(this._360data.oUIBox,this._360data.className);
329
+ },
330
+
331
+ resume: function() {
332
+ pl.removeClass(this._360data.oUIBox,this._360data.className);
333
+ this._360data.className = pl.css.sPlaying;
334
+ pl.addClass(this._360data.oUIBox,this._360data.className);
335
+ },
336
+
337
+ finish: function() {
338
+ var nextLink;
339
+ pl.removeClass(this._360data.oUIBox,this._360data.className);
340
+ this._360data.className = '';
341
+ // self.clearCanvas(this._360data.oCanvas);
342
+ this._360data.didFinish = true; // so fan draws full circle
343
+ self.fanIn(this);
344
+ if (pl.config.playNext) {
345
+ nextLink = (pl.indexByURL[this._360data.oLink.href]+1);
346
+ if (nextLink<pl.links.length) {
347
+ pl.handleClick({'target':pl.links[nextLink]});
348
+ }
349
+ }
350
+ },
351
+
352
+ whileloading: function() {
353
+ if (this.paused) {
354
+ self.updatePlaying.apply(this);
355
+ }
356
+ },
357
+
358
+ whileplaying: function() {
359
+ self.updatePlaying.apply(this);
360
+ this._360data.fps++;
361
+ },
362
+
363
+ bufferchange: function() {
364
+ if (this.isBuffering) {
365
+ pl.addClass(this._360data.oUIBox,pl.css.sBuffering);
366
+ } else {
367
+ pl.removeClass(this._360data.oUIBox,pl.css.sBuffering);
368
+ }
369
+ }
370
+
371
+ };
372
+
373
+ this.stopEvent = function(e) {
374
+
375
+ if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') {
376
+ e.preventDefault();
377
+ } else if (typeof window.event !== 'undefined' && typeof window.event.returnValue !== 'undefined') {
378
+ window.event.returnValue = false;
379
+ }
380
+ return false;
381
+
382
+ };
383
+
384
+ this.getTheDamnLink = (isIE)?function(e) {
385
+ // I really didn't want to have to do this.
386
+ return (e && e.target?e.target:window.event.srcElement);
387
+ }:function(e) {
388
+ return e.target;
389
+ };
390
+
391
+ this.handleClick = function(e) {
392
+
393
+ // a sound link was clicked
394
+ if (e.button > 1) {
395
+ // only catch left-clicks
396
+ return true;
397
+ }
398
+
399
+ var o = self.getTheDamnLink(e),
400
+ sURL, soundURL, thisSound, oContainer, has_vis, diameter;
401
+
402
+ if (o.nodeName.toLowerCase() !== 'a') {
403
+ o = self.isChildOfNode(o,'a');
404
+ if (!o) {
405
+ return true;
406
+ }
407
+ }
408
+
409
+ if (!self.isChildOfClass(o,'ui360')) {
410
+ // not a link we're interested in
411
+ return true;
412
+ }
413
+
414
+ sURL = o.getAttribute('href');
415
+
416
+ if (!o.href || !sm.canPlayLink(o) || self.hasClass(o,self.excludeClass)) {
417
+ return true; // pass-thru for non-MP3/non-links
418
+ }
419
+
420
+ sm._writeDebug('handleClick()');
421
+ soundURL = (o.href);
422
+ thisSound = self.getSoundByURL(soundURL);
423
+
424
+ if (thisSound) {
425
+
426
+ // already exists
427
+ if (thisSound === self.lastSound) {
428
+ // and was playing (or paused)
429
+ thisSound.togglePause();
430
+ } else {
431
+ // different sound
432
+ thisSound.togglePause(); // start playing current
433
+ sm._writeDebug('sound different than last sound: '+self.lastSound.id);
434
+ if (!self.config.allowMultiple && self.lastSound) {
435
+ self.stopSound(self.lastSound);
436
+ }
437
+ }
438
+
439
+ } else {
440
+
441
+ // append some dom shiz, make noise
442
+
443
+ oContainer = o.parentNode;
444
+ has_vis = (self.getElementsByClassName('ui360-vis','div',oContainer.parentNode).length);
445
+
446
+ // create sound
447
+ thisSound = sm.createSound({
448
+ id:'ui360Sound'+(self.soundCount++),
449
+ url:soundURL,
450
+ onplay:self.events.play,
451
+ onstop:self.events.stop,
452
+ onpause:self.events.pause,
453
+ onresume:self.events.resume,
454
+ onfinish:self.events.finish,
455
+ onbufferchange:self.events.bufferchange,
456
+ type:(o.type||null),
457
+ whileloading:self.events.whileloading,
458
+ whileplaying:self.events.whileplaying,
459
+ useWaveformData:(has_vis && self.config.useWaveformData),
460
+ useEQData:(has_vis && self.config.useEQData),
461
+ usePeakData:(has_vis && self.config.usePeakData)
462
+ });
463
+
464
+ // tack on some custom data
465
+
466
+ diameter = parseInt(self.getElementsByClassName('sm2-360ui','div',oContainer)[0].offsetWidth, 10);
467
+
468
+ thisSound._360data = {
469
+ oUI360: self.getParentByClassName(o,'ui360'), // the (whole) entire container
470
+ oLink: o, // DOM node for reference within SM2 object event handlers
471
+ className: self.css.sPlaying,
472
+ oUIBox: self.getElementsByClassName('sm2-360ui','div',oContainer)[0],
473
+ oCanvas: self.getElementsByClassName('sm2-canvas','canvas',oContainer)[0],
474
+ oButton: self.getElementsByClassName('sm2-360btn','span',oContainer)[0],
475
+ oTiming: self.getElementsByClassName('sm2-timing','div',oContainer)[0],
476
+ oCover: self.getElementsByClassName('sm2-cover','div',oContainer)[0],
477
+ circleDiameter: diameter,
478
+ circleRadius: diameter/2,
479
+ lastTime: null,
480
+ didFinish: null,
481
+ pauseCount:0,
482
+ radius:0,
483
+ fontSize: 1,
484
+ fontSizeMax: self.config.fontSizeMax,
485
+ scaleFont: (has_vis && self.config.scaleFont),
486
+ showHMSTime: has_vis,
487
+ amplifier: (has_vis && self.config.usePeakData?0.9:1), // TODO: x1 if not being used, else use dynamic "how much to amplify by" value
488
+ radiusMax: diameter*0.175, // circle radius
489
+ width:0,
490
+ widthMax: diameter*0.4, // width of the outer ring
491
+ lastValues: {
492
+ bytesLoaded: 0,
493
+ bytesTotal: 0,
494
+ position: 0,
495
+ durationEstimate: 0
496
+ }, // used to track "last good known" values before sound finish/reset for anim
497
+ animating: false,
498
+ oAnim: new window.Animator({
499
+ duration: self.config.animDuration,
500
+ transition:self.config.animTransition,
501
+ onComplete: function() {
502
+ // var thisSound = this;
503
+ // thisSound._360data.didFinish = false; // reset full circle
504
+ }
505
+ }),
506
+ oAnimProgress: function(nProgress) {
507
+ var thisSound = this;
508
+ thisSound._360data.radius = parseInt(thisSound._360data.radiusMax*thisSound._360data.amplifier*nProgress, 10);
509
+ thisSound._360data.width = parseInt(thisSound._360data.widthMax*thisSound._360data.amplifier*nProgress, 10);
510
+ if (thisSound._360data.scaleFont && thisSound._360data.fontSizeMax !== null) {
511
+ thisSound._360data.oTiming.style.fontSize = parseInt(Math.max(1,thisSound._360data.fontSizeMax*nProgress), 10)+'px';
512
+ thisSound._360data.oTiming.style.opacity = nProgress;
513
+ }
514
+ if (thisSound.paused || thisSound.playState === 0 || thisSound._360data.lastValues.bytesLoaded === 0 || thisSound._360data.lastValues.position === 0) {
515
+ self.updatePlaying.apply(thisSound);
516
+ }
517
+ },
518
+ fps: 0
519
+ };
520
+
521
+ // "Metadata" (annotations)
522
+ if (typeof self.Metadata !== 'undefined' && self.getElementsByClassName('metadata','div',thisSound._360data.oUI360).length) {
523
+ thisSound._360data.metadata = new self.Metadata(thisSound,self);
524
+ }
525
+
526
+ // minimize ze font
527
+ if (thisSound._360data.scaleFont && thisSound._360data.fontSizeMax !== null) {
528
+ thisSound._360data.oTiming.style.fontSize = '1px';
529
+ }
530
+
531
+ // set up ze animation
532
+ thisSound._360data.oAnim.addSubject(thisSound._360data.oAnimProgress,thisSound);
533
+
534
+ // animate the radius out nice
535
+ self.refreshCoords(thisSound);
536
+
537
+ self.updatePlaying.apply(thisSound);
538
+
539
+ self.soundsByURL[soundURL] = thisSound;
540
+ self.sounds.push(thisSound);
541
+ if (!self.config.allowMultiple && self.lastSound) {
542
+ self.stopSound(self.lastSound);
543
+ }
544
+ thisSound.play();
545
+
546
+ }
547
+
548
+ self.lastSound = thisSound; // reference for next call
549
+
550
+ if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') {
551
+ e.preventDefault();
552
+ } else if (typeof window.event !== 'undefined') {
553
+ window.event.returnValue = false;
554
+ }
555
+ return false;
556
+
557
+ };
558
+
559
+ this.fanOut = function(oSound) {
560
+
561
+ var thisSound = oSound;
562
+ if (thisSound._360data.animating === 1) {
563
+ return false;
564
+ }
565
+ thisSound._360data.animating = 0;
566
+ soundManager._writeDebug('fanOut: '+thisSound.id+': '+thisSound._360data.oLink.href);
567
+ thisSound._360data.oAnim.seekTo(1); // play to end
568
+ window.setTimeout(function() {
569
+ // oncomplete hack
570
+ thisSound._360data.animating = 0;
571
+ },self.config.animDuration+20);
572
+
573
+ };
574
+
575
+ this.fanIn = function(oSound) {
576
+
577
+ var thisSound = oSound;
578
+ if (thisSound._360data.animating === -1) {
579
+ return false;
580
+ }
581
+ thisSound._360data.animating = -1;
582
+ soundManager._writeDebug('fanIn: '+thisSound.id+': '+thisSound._360data.oLink.href);
583
+ // massive hack
584
+ thisSound._360data.oAnim.seekTo(0); // play to end
585
+ window.setTimeout(function() {
586
+ // reset full 360 fill after animation has completed (oncomplete hack)
587
+ thisSound._360data.didFinish = false;
588
+ thisSound._360data.animating = 0;
589
+ self.resetLastValues(thisSound);
590
+ }, self.config.animDuration+20);
591
+
592
+ };
593
+
594
+ this.resetLastValues = function(oSound) {
595
+ oSound._360data.lastValues.position = 0;
596
+ };
597
+
598
+ this.refreshCoords = function(thisSound) {
599
+
600
+ thisSound._360data.canvasXY = self.findXY(thisSound._360data.oCanvas);
601
+ thisSound._360data.canvasMid = [thisSound._360data.circleRadius,thisSound._360data.circleRadius];
602
+ thisSound._360data.canvasMidXY = [thisSound._360data.canvasXY[0]+thisSound._360data.canvasMid[0], thisSound._360data.canvasXY[1]+thisSound._360data.canvasMid[1]];
603
+
604
+ };
605
+
606
+ this.stopSound = function(oSound) {
607
+
608
+ soundManager._writeDebug('stopSound: '+oSound.id);
609
+ soundManager.stop(oSound.id);
610
+ if (!isTouchDevice) { // iOS 4.2+ security blocks onfinish() -> playNext() if we set a .src in-between(?)
611
+ soundManager.unload(oSound.id);
612
+ }
613
+
614
+ };
615
+
616
+ this.buttonClick = function(e) {
617
+
618
+ var o = e?(e.target?e.target:e.srcElement):window.event.srcElement;
619
+ self.handleClick({target:self.getParentByClassName(o,'sm2-360ui').nextSibling}); // link next to the nodes we inserted
620
+ return false;
621
+
622
+ };
623
+
624
+ this.buttonMouseDown = function(e) {
625
+
626
+ // user might decide to drag from here
627
+ // watch for mouse move
628
+ if (!isTouchDevice) {
629
+ document.onmousemove = function(e) {
630
+ // should be boundary-checked, really (eg. move 3px first?)
631
+ self.mouseDown(e);
632
+ };
633
+ } else {
634
+ self.addEventHandler(document,'touchmove',self.mouseDown);
635
+ }
636
+ self.stopEvent(e);
637
+ return false;
638
+
639
+ };
640
+
641
+ this.mouseDown = function(e) {
642
+
643
+ if (!isTouchDevice && e.button > 1) {
644
+ return true; // ignore non-left-click
645
+ }
646
+
647
+ if (!self.lastSound) {
648
+ self.stopEvent(e);
649
+ return false;
650
+ }
651
+
652
+ var evt = e?e:window.event,
653
+ target, thisSound, oData;
654
+
655
+ if (isTouchDevice && evt.touches) {
656
+ evt = evt.touches[0];
657
+ }
658
+ target = (evt.target||evt.srcElement);
659
+
660
+ thisSound = self.getSoundByURL(self.getElementsByClassName('sm2_link','a',self.getParentByClassName(target,'ui360'))[0].href); // self.lastSound; // TODO: In multiple sound case, figure out which sound is involved etc.
661
+ // just in case, update coordinates (maybe the element moved since last time.)
662
+ self.lastTouchedSound = thisSound;
663
+ self.refreshCoords(thisSound);
664
+ oData = thisSound._360data;
665
+ self.addClass(oData.oUIBox,'sm2_dragging');
666
+ oData.pauseCount = (self.lastTouchedSound.paused?1:0);
667
+ // self.lastSound.pause();
668
+ self.mmh(e?e:window.event);
669
+
670
+ if (isTouchDevice) {
671
+ self.removeEventHandler(document,'touchmove',self.mouseDown);
672
+ self.addEventHandler(document,'touchmove',self.mmh);
673
+ self.addEventHandler(document,'touchend',self.mouseUp);
674
+ } else {
675
+ // incredibly old-skool. TODO: Modernize.
676
+ document.onmousemove = self.mmh;
677
+ document.onmouseup = self.mouseUp;
678
+ }
679
+
680
+ self.stopEvent(e);
681
+ return false;
682
+
683
+ };
684
+
685
+ this.mouseUp = function(e) {
686
+
687
+ var oData = self.lastTouchedSound._360data;
688
+ self.removeClass(oData.oUIBox,'sm2_dragging');
689
+ if (oData.pauseCount === 0) {
690
+ self.lastTouchedSound.resume();
691
+ }
692
+ if (!isTouchDevice) {
693
+ document.onmousemove = null;
694
+ document.onmouseup = null;
695
+ } else {
696
+ self.removeEventHandler(document,'touchmove',self.mmh);
697
+ self.removeEventHandler(document,'touchend',self.mouseUP);
698
+ }
699
+
700
+ };
701
+
702
+ this.mmh = function(e) {
703
+
704
+ if (typeof e === 'undefined') {
705
+ e = window.event;
706
+ }
707
+ var oSound = self.lastTouchedSound,
708
+ coords = self.getMouseXY(e),
709
+ x = coords[0],
710
+ y = coords[1],
711
+ deltaX = x-oSound._360data.canvasMidXY[0],
712
+ deltaY = y-oSound._360data.canvasMidXY[1],
713
+ angle = Math.floor(fullCircle-(self.rad2deg(Math.atan2(deltaX,deltaY))+180));
714
+
715
+ oSound.setPosition(oSound.durationEstimate*(angle/fullCircle));
716
+ self.stopEvent(e);
717
+ return false;
718
+
719
+ };
720
+
721
+ // assignMouseDown();
722
+
723
+ this.drawSolidArc = function(oCanvas, color, radius, width, radians, startAngle, noClear) {
724
+
725
+ // thank you, http://www.snipersystems.co.nz/community/polarclock/tutorial.html
726
+
727
+ var x = radius,
728
+ y = radius,
729
+ canvas = oCanvas,
730
+ ctx, innerRadius, doesntLikeZero, endPoint;
731
+
732
+ if (canvas.getContext){
733
+ // use getContext to use the canvas for drawing
734
+ ctx = canvas.getContext('2d');
735
+ }
736
+
737
+ // re-assign canvas as the actual context
738
+ oCanvas = ctx;
739
+
740
+ if (!noClear) {
741
+ self.clearCanvas(canvas);
742
+ }
743
+ // ctx.restore();
744
+
745
+ if (color) {
746
+ ctx.fillStyle = color;
747
+ }
748
+
749
+ oCanvas.beginPath();
750
+
751
+ if (isNaN(radians)) {
752
+ radians = 0;
753
+ }
754
+
755
+ innerRadius = radius-width;
756
+ doesntLikeZero = (isOpera || isSafari); // safari 4 doesn't actually seem to mind.
757
+
758
+ if (!doesntLikeZero || (doesntLikeZero && radius > 0)) {
759
+ oCanvas.arc(0, 0, radius, startAngle, radians, false);
760
+ endPoint = self.getArcEndpointCoords(innerRadius, radians);
761
+ oCanvas.lineTo(endPoint.x, endPoint.y);
762
+ oCanvas.arc(0, 0, innerRadius, radians, startAngle, true);
763
+ oCanvas.closePath();
764
+ oCanvas.fill();
765
+ }
766
+
767
+ };
768
+
769
+ this.getArcEndpointCoords = function(radius, radians) {
770
+
771
+ return {
772
+ x: radius * Math.cos(radians),
773
+ y: radius * Math.sin(radians)
774
+ };
775
+
776
+ };
777
+
778
+ this.deg2rad = function(nDeg) {
779
+ return (nDeg * Math.PI/180);
780
+ };
781
+
782
+ this.rad2deg = function(nRad) {
783
+ return (nRad * 180/Math.PI);
784
+ };
785
+
786
+ this.getTime = function(nMSec,bAsString) {
787
+
788
+ // convert milliseconds to mm:ss, return as object literal or string
789
+ var nSec = Math.floor(nMSec/1000),
790
+ min = Math.floor(nSec/60),
791
+ sec = nSec-(min*60);
792
+ // if (min === 0 && sec === 0) return null; // return 0:00 as null
793
+ return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec});
794
+
795
+ };
796
+
797
+ this.clearCanvas = function(oCanvas) {
798
+
799
+ var canvas = oCanvas,
800
+ ctx = null,
801
+ width, height;
802
+ if (canvas.getContext){
803
+ // use getContext to use the canvas for drawing
804
+ ctx = canvas.getContext('2d');
805
+ }
806
+ width = canvas.offsetWidth;
807
+ height = canvas.offsetHeight;
808
+ ctx.clearRect(-(width/2), -(height/2), width, height);
809
+
810
+ };
811
+
812
+ this.updatePlaying = function() {
813
+
814
+ var timeNow = (this._360data.showHMSTime?self.getTime(this.position,true):parseInt(this.position/1000, 10));
815
+ var ringScaleFactor = self.config.scaleArcWidth;
816
+
817
+ if (this.bytesLoaded) {
818
+ this._360data.lastValues.bytesLoaded = this.bytesLoaded;
819
+ this._360data.lastValues.bytesTotal = this.bytesTotal;
820
+ }
821
+
822
+ if (this.position) {
823
+ this._360data.lastValues.position = this.position;
824
+ }
825
+
826
+ if (this.durationEstimate) {
827
+ this._360data.lastValues.durationEstimate = this.durationEstimate;
828
+ }
829
+
830
+ // background ring
831
+ self.drawSolidArc(this._360data.oCanvas,self.config.backgroundRingColor,this._360data.width,this._360data.radius * ringScaleFactor,self.deg2rad(fullCircle),false);
832
+
833
+ // loaded ring
834
+ self.drawSolidArc(this._360data.oCanvas,(this._360data.metadata?self.config.loadRingColorMetadata:self.config.loadRingColor),this._360data.width,this._360data.radius * ringScaleFactor,self.deg2rad(fullCircle*(this._360data.lastValues.bytesLoaded/this._360data.lastValues.bytesTotal)),0,true);
835
+
836
+ // don't draw if 0 (full black circle in Opera)
837
+ if (this._360data.lastValues.position !== 0) {
838
+ self.drawSolidArc(this._360data.oCanvas,(this._360data.metadata?self.config.playRingColorMetadata:self.config.playRingColor),this._360data.width,this._360data.radius * ringScaleFactor,self.deg2rad((this._360data.didFinish===1?fullCircle:fullCircle*(this._360data.lastValues.position/this._360data.lastValues.durationEstimate))),0,true);
839
+ }
840
+
841
+ // metadata goes here
842
+ if (this._360data.metadata) {
843
+ this._360data.metadata.events.whileplaying();
844
+ }
845
+
846
+ if (timeNow !== this._360data.lastTime) {
847
+ this._360data.lastTime = timeNow;
848
+ this._360data.oTiming.innerHTML = timeNow;
849
+ }
850
+
851
+ // draw spectrum, if applicable
852
+ if ((this.instanceOptions.useWaveformData || this.instanceOptions.useEQData) && hasRealCanvas) { // IE <9 can render maybe 3 or 4 FPS when including the wave/EQ, so don't bother.
853
+ self.updateWaveform(this);
854
+ }
855
+
856
+ if (self.config.useFavIcon && self.vuMeter) {
857
+ self.vuMeter.updateVU(this);
858
+ }
859
+
860
+ };
861
+
862
+ this.updateWaveform = function(oSound) {
863
+
864
+ if ((!self.config.useWaveformData && !self.config.useEQData) || (!sm.features.waveformData && !sm.features.eqData)) {
865
+ // feature not enabled..
866
+ return false;
867
+ }
868
+
869
+ if (!oSound.waveformData.left.length && !oSound.eqData.length && !oSound.peakData.left) {
870
+ // no data (or errored out/paused/unavailable?)
871
+ return false;
872
+ }
873
+
874
+ /* use for testing the data */
875
+ /*
876
+ for (i=0; i<256; i++) {
877
+ oSound.eqData[i] = 1-(i/256);
878
+ }
879
+ */
880
+
881
+ var oCanvas = oSound._360data.oCanvas.getContext('2d'),
882
+ offX = 0,
883
+ offY = parseInt(oSound._360data.circleDiameter/2, 10),
884
+ scale = offY/2, // Y axis (+/- this distance from 0)
885
+ // lineWidth = Math.floor(oSound._360data.circleDiameter-(oSound._360data.circleDiameter*0.175)/(oSound._360data.circleDiameter/255)); // width for each line
886
+ lineWidth = 1,
887
+ lineHeight = 1,
888
+ thisY = 0,
889
+ offset = offY,
890
+ i, j, direction, downSample, dataLength, sampleCount, startAngle, endAngle, waveData, innerRadius, perItemAngle, yDiff, eqSamples, playedAngle, iAvg, nPeak;
891
+
892
+ if (self.config.useWaveformData) {
893
+ // raw waveform
894
+ downSample = self.config.waveformDataDownsample; // only sample X in 256 (greater number = less sample points)
895
+ downSample = Math.max(1,downSample); // make sure it's at least 1
896
+ dataLength = 256;
897
+ sampleCount = (dataLength/downSample);
898
+ startAngle = 0;
899
+ endAngle = 0;
900
+ waveData = null;
901
+ innerRadius = (self.config.waveformDataOutside?1:(self.config.waveformDataConstrain?0.5:0.565));
902
+ scale = (self.config.waveformDataOutside?0.7:0.75);
903
+ perItemAngle = self.deg2rad((360/sampleCount)*self.config.waveformDataLineRatio); // 0.85 = clean pixel lines at 150? // self.deg2rad(360*(Math.max(1,downSample-1))/sampleCount);
904
+ for (i=0; i<dataLength; i+=downSample) {
905
+ startAngle = self.deg2rad(360*(i/(sampleCount)*1/downSample)); // +0.67 - counter for spacing
906
+ endAngle = startAngle+perItemAngle;
907
+ waveData = oSound.waveformData.left[i];
908
+ if (waveData<0 && self.config.waveformDataConstrain) {
909
+ waveData = Math.abs(waveData);
910
+ }
911
+ self.drawSolidArc(oSound._360data.oCanvas,self.config.waveformDataColor,oSound._360data.width*innerRadius*(2-self.config.scaleArcWidth),oSound._360data.radius*scale*1.25*waveData,endAngle,startAngle,true);
912
+ }
913
+ }
914
+
915
+ if (self.config.useEQData) {
916
+ // EQ spectrum
917
+ downSample = self.config.eqDataDownsample; // only sample N in 256
918
+ yDiff = 0;
919
+ downSample = Math.max(1,downSample); // make sure it's at least 1
920
+ eqSamples = 192; // drop the last 25% of the spectrum (>16500 Hz), most stuff won't actually use it.
921
+ sampleCount = (eqSamples/downSample);
922
+ innerRadius = (self.config.eqDataOutside?1:0.565);
923
+ direction = (self.config.eqDataOutside?-1:1);
924
+ scale = (self.config.eqDataOutside?0.5:0.75);
925
+ startAngle = 0;
926
+ endAngle = 0;
927
+ perItemAngle = self.deg2rad((360/sampleCount)*self.config.eqDataLineRatio); // self.deg2rad(360/(sampleCount+1));
928
+ playedAngle = self.deg2rad((oSound._360data.didFinish===1?360:360*(oSound._360data.lastValues.position/oSound._360data.lastValues.durationEstimate)));
929
+ j=0;
930
+ iAvg = 0;
931
+ for (i=0; i<eqSamples; i+=downSample) {
932
+ startAngle = self.deg2rad(360*(i/eqSamples));
933
+ endAngle = startAngle+perItemAngle;
934
+ self.drawSolidArc(oSound._360data.oCanvas,(endAngle>playedAngle?self.config.eqDataColor:self.config.playRingColor),oSound._360data.width*innerRadius,oSound._360data.radius*scale*(oSound.eqData.left[i]*direction),endAngle,startAngle,true);
935
+ }
936
+ }
937
+
938
+ if (self.config.usePeakData) {
939
+ if (!oSound._360data.animating) {
940
+ nPeak = (oSound.peakData.left||oSound.peakData.right);
941
+ // GIANT HACK: use EQ spectrum data for bass frequencies
942
+ eqSamples = 3;
943
+ for (i=0; i<eqSamples; i++) {
944
+ nPeak = (nPeak||oSound.eqData[i]);
945
+ }
946
+ oSound._360data.amplifier = (self.config.useAmplifier?(0.9+(nPeak*0.1)):1);
947
+ oSound._360data.radiusMax = oSound._360data.circleDiameter*0.175*oSound._360data.amplifier;
948
+ oSound._360data.widthMax = oSound._360data.circleDiameter*0.4*oSound._360data.amplifier;
949
+ oSound._360data.radius = parseInt(oSound._360data.radiusMax*oSound._360data.amplifier, 10);
950
+ oSound._360data.width = parseInt(oSound._360data.widthMax*oSound._360data.amplifier, 10);
951
+ }
952
+ }
953
+
954
+ };
955
+
956
+ this.getUIHTML = function(diameter) {
957
+
958
+ return [
959
+ '<canvas class="sm2-canvas" width="'+diameter+'" height="'+diameter+'"></canvas>',
960
+ ' <span class="sm2-360btn sm2-360btn-default"></span>', // note use of imageMap, edit or remove if you use a different-size image.
961
+ ' <div class="sm2-timing'+(navigator.userAgent.match(/safari/i)?' alignTweak':'')+'"></div>', // + Ever-so-slight Safari horizontal alignment tweak
962
+ ' <div class="sm2-cover"></div>'
963
+ ];
964
+
965
+ };
966
+
967
+ this.uiTest = function(sClass) {
968
+
969
+ // fake a 360 UI so we can get some numbers from CSS, etc.
970
+
971
+ var oTemplate = document.createElement('div'),
972
+ oFakeUI, oFakeUIBox, oTemp, fakeDiameter, uiHTML, circleDiameter, circleRadius, fontSizeMax, oTiming;
973
+
974
+ oTemplate.className = 'sm2-360ui';
975
+
976
+ oFakeUI = document.createElement('div');
977
+ oFakeUI.className = 'ui360'+(sClass?' '+sClass:''); // ui360 ui360-vis
978
+
979
+ oFakeUIBox = oFakeUI.appendChild(oTemplate.cloneNode(true));
980
+
981
+ oFakeUI.style.position = 'absolute';
982
+ oFakeUI.style.left = '-9999px';
983
+
984
+ oTemp = document.body.appendChild(oFakeUI);
985
+
986
+ fakeDiameter = oFakeUIBox.offsetWidth;
987
+
988
+ uiHTML = self.getUIHTML(fakeDiameter);
989
+
990
+ oFakeUIBox.innerHTML = uiHTML[1]+uiHTML[2]+uiHTML[3];
991
+
992
+ circleDiameter = parseInt(oFakeUIBox.offsetWidth, 10);
993
+ circleRadius = parseInt(circleDiameter/2, 10);
994
+
995
+ oTiming = self.getElementsByClassName('sm2-timing','div',oTemp)[0];
996
+ fontSizeMax = parseInt(self.getStyle(oTiming,'font-size'), 10);
997
+ if (isNaN(fontSizeMax)) {
998
+ // getStyle() etc. didn't work.
999
+ fontSizeMax = null;
1000
+ }
1001
+
1002
+ // soundManager._writeDebug('diameter, font size: '+circleDiameter+','+fontSizeMax);
1003
+
1004
+ oFakeUI.parentNode.removeChild(oFakeUI);
1005
+
1006
+ uiHTML = oFakeUI = oFakeUIBox = oTemp = null;
1007
+
1008
+ return {
1009
+ circleDiameter: circleDiameter,
1010
+ circleRadius: circleRadius,
1011
+ fontSizeMax: fontSizeMax
1012
+ };
1013
+
1014
+ };
1015
+
1016
+ this.init = function() {
1017
+ sm._writeDebug('threeSixtyPlayer.init()');
1018
+ var oItems = self.getElementsByClassName('ui360','div'),
1019
+ i, j, oLinks = [], is_vis = false, foundItems = 0, oCanvas, oCanvasCTX, oCover, diameter, radius, uiData, uiDataVis, oUI, oBtn, o, o2, oID;
1020
+
1021
+
1022
+
1023
+ for (i=0,j=oItems.length; i<j; i++) {
1024
+ oLinks.push(oItems[i].getElementsByTagName('a')[0]);
1025
+ // remove "fake" play button (unsupported case)
1026
+ oItems[i].style.backgroundImage = 'none';
1027
+ }
1028
+ // grab all links, look for .mp3
1029
+
1030
+ self.oUITemplate = document.createElement('div');
1031
+ self.oUITemplate.className = 'sm2-360ui';
1032
+
1033
+ self.oUITemplateVis = document.createElement('div');
1034
+ self.oUITemplateVis.className = 'sm2-360ui';
1035
+
1036
+ uiData = self.uiTest();
1037
+
1038
+ self.config.circleDiameter = uiData.circleDiameter;
1039
+ self.config.circleRadius = uiData.circleRadius;
1040
+ // self.config.fontSizeMax = uiData.fontSizeMax;
1041
+
1042
+ uiDataVis = self.uiTest('ui360-vis');
1043
+
1044
+ self.config.fontSizeMax = uiDataVis.fontSizeMax;
1045
+
1046
+ // canvas needs inline width and height, doesn't quite work otherwise
1047
+ self.oUITemplate.innerHTML = self.getUIHTML(self.config.circleDiameter).join('');
1048
+
1049
+ self.oUITemplateVis.innerHTML = self.getUIHTML(uiDataVis.circleDiameter).join('');
1050
+
1051
+ for (i=0,j=oLinks.length; i<j; i++) {
1052
+ if (sm.canPlayLink(oLinks[i]) && !self.hasClass(oLinks[i],self.excludeClass) && !self.hasClass(oLinks[i],self.css.sDefault)) {
1053
+ self.addClass(oLinks[i],self.css.sDefault); // add default CSS decoration
1054
+ self.links[foundItems] = (oLinks[i]);
1055
+ self.indexByURL[oLinks[i].href] = foundItems; // hack for indexing
1056
+ foundItems++;
1057
+
1058
+ is_vis = self.hasClass(oLinks[i].parentNode, 'ui360-vis');
1059
+
1060
+ diameter = (is_vis ? uiDataVis : uiData).circleDiameter;
1061
+ radius = (is_vis ? uiDataVis : uiData).circleRadius;
1062
+
1063
+ // add canvas shiz
1064
+ oUI = oLinks[i].parentNode.insertBefore((is_vis?self.oUITemplateVis:self.oUITemplate).cloneNode(true),oLinks[i]);
1065
+
1066
+ if (isIE && typeof window.G_vmlCanvasManager !== 'undefined') { // IE only
1067
+ o = oLinks[i].parentNode;
1068
+ o2 = document.createElement('canvas');
1069
+ o2.className = 'sm2-canvas';
1070
+ oID = 'sm2_canvas_'+parseInt(Math.random()*1048576, 10);
1071
+ o2.id = oID;
1072
+ o2.width = diameter;
1073
+ o2.height = diameter;
1074
+ oUI.appendChild(o2);
1075
+ window.G_vmlCanvasManager.initElement(o2); // Apply ExCanvas compatibility magic
1076
+ oCanvas = document.getElementById(oID);
1077
+ } else {
1078
+ // add a handler for the button
1079
+ oCanvas = oLinks[i].parentNode.getElementsByTagName('canvas')[0];
1080
+ }
1081
+ oCover = self.getElementsByClassName('sm2-cover','div',oLinks[i].parentNode)[0];
1082
+ oBtn = oLinks[i].parentNode.getElementsByTagName('span')[0];
1083
+ self.addEventHandler(oBtn,'click',self.buttonClick);
1084
+ if (!isTouchDevice) {
1085
+ self.addEventHandler(oCover,'mousedown',self.mouseDown);
1086
+ } else {
1087
+ self.addEventHandler(oCover,'touchstart',self.mouseDown);
1088
+ }
1089
+ oCanvasCTX = oCanvas.getContext('2d');
1090
+ oCanvasCTX.translate(radius, radius);
1091
+ oCanvasCTX.rotate(self.deg2rad(-90)); // compensate for arc starting at EAST // http://stackoverflow.com/questions/319267/tutorial-for-html-canvass-arc-function
1092
+ }
1093
+ }
1094
+ if (foundItems>0) {
1095
+ self.addEventHandler(document,'click',self.handleClick);
1096
+ if (self.config.autoPlay) {
1097
+ self.handleClick({target:self.links[0],preventDefault:function(){}});
1098
+ }
1099
+ }
1100
+ sm._writeDebug('threeSixtyPlayer.init(): Found '+foundItems+' relevant items.');
1101
+
1102
+ if (self.config.useFavIcon && typeof this.VUMeter !== 'undefined') {
1103
+ this.vuMeter = new this.VUMeter(this);
1104
+ }
1105
+
1106
+ };
1107
+
1108
+ }
1109
+
1110
+ // Optional: VU Meter component
1111
+
1112
+ ThreeSixtyPlayer.prototype.VUMeter = function(oParent) {
1113
+
1114
+ var self = oParent,
1115
+ me = this,
1116
+ _head = document.getElementsByTagName('head')[0],
1117
+ isOpera = (navigator.userAgent.match(/opera/i)),
1118
+ isFirefox = (navigator.userAgent.match(/firefox/i));
1119
+
1120
+ this.vuMeterData = [];
1121
+ this.vuDataCanvas = null;
1122
+
1123
+ this.setPageIcon = function(sDataURL) {
1124
+
1125
+ if (!self.config.useFavIcon || !self.config.usePeakData || !sDataURL) {
1126
+ return false;
1127
+ }
1128
+
1129
+ var link = document.getElementById('sm2-favicon');
1130
+ if (link) {
1131
+ _head.removeChild(link);
1132
+ link = null;
1133
+ }
1134
+ if (!link) {
1135
+ link = document.createElement('link');
1136
+ link.id = 'sm2-favicon';
1137
+ link.rel = 'shortcut icon';
1138
+ link.type = 'image/png';
1139
+ link.href = sDataURL;
1140
+ document.getElementsByTagName('head')[0].appendChild(link);
1141
+ }
1142
+
1143
+ };
1144
+
1145
+ this.resetPageIcon = function() {
1146
+
1147
+ if (!self.config.useFavIcon) {
1148
+ return false;
1149
+ }
1150
+ var link = document.getElementById('favicon');
1151
+ if (link) {
1152
+ link.href = '/favicon.ico';
1153
+ }
1154
+
1155
+ };
1156
+
1157
+ this.updateVU = function(oSound) {
1158
+
1159
+ if (soundManager.flashVersion >= 9 && self.config.useFavIcon && self.config.usePeakData) {
1160
+ me.setPageIcon(me.vuMeterData[parseInt(16*oSound.peakData.left, 10)][parseInt(16*oSound.peakData.right, 10)]);
1161
+ }
1162
+
1163
+ };
1164
+
1165
+ this.createVUData = function() {
1166
+
1167
+ var i=0, j=0,
1168
+ canvas = me.vuDataCanvas.getContext('2d'),
1169
+ vuGrad = canvas.createLinearGradient(0, 16, 0, 0),
1170
+ bgGrad = canvas.createLinearGradient(0, 16, 0, 0),
1171
+ outline = 'rgba(0,0,0,0.2)';
1172
+
1173
+ vuGrad.addColorStop(0,'rgb(0,192,0)');
1174
+ vuGrad.addColorStop(0.30,'rgb(0,255,0)');
1175
+ vuGrad.addColorStop(0.625,'rgb(255,255,0)');
1176
+ vuGrad.addColorStop(0.85,'rgb(255,0,0)');
1177
+ bgGrad.addColorStop(0,outline);
1178
+ bgGrad.addColorStop(1,'rgba(0,0,0,0.5)');
1179
+ for (i=0; i<16; i++) {
1180
+ me.vuMeterData[i] = [];
1181
+ }
1182
+ for (i=0; i<16; i++) {
1183
+ for (j=0; j<16; j++) {
1184
+ // reset/erase canvas
1185
+ me.vuDataCanvas.setAttribute('width',16);
1186
+ me.vuDataCanvas.setAttribute('height',16);
1187
+ // draw new stuffs
1188
+ canvas.fillStyle = bgGrad;
1189
+ canvas.fillRect(0,0,7,15);
1190
+ canvas.fillRect(8,0,7,15);
1191
+ /*
1192
+ // shadow
1193
+ canvas.fillStyle = 'rgba(0,0,0,0.1)';
1194
+ canvas.fillRect(1,15-i,7,17-(17-i));
1195
+ canvas.fillRect(9,15-j,7,17-(17-j));
1196
+ */
1197
+ canvas.fillStyle = vuGrad;
1198
+ canvas.fillRect(0,15-i,7,16-(16-i));
1199
+ canvas.fillRect(8,15-j,7,16-(16-j));
1200
+ // and now, clear out some bits.
1201
+ canvas.clearRect(0,3,16,1);
1202
+ canvas.clearRect(0,7,16,1);
1203
+ canvas.clearRect(0,11,16,1);
1204
+ me.vuMeterData[i][j] = me.vuDataCanvas.toDataURL('image/png');
1205
+ // for debugging VU images
1206
+ /*
1207
+ var o = document.createElement('img');
1208
+ o.style.marginRight = '5px';
1209
+ o.src = vuMeterData[i][j];
1210
+ document.documentElement.appendChild(o);
1211
+ */
1212
+ }
1213
+ }
1214
+
1215
+ };
1216
+
1217
+ this.testCanvas = function() {
1218
+
1219
+ // canvas + toDataURL();
1220
+ var c = document.createElement('canvas'),
1221
+ ctx = null, ok;
1222
+ if (!c || typeof c.getContext === 'undefined') {
1223
+ return null;
1224
+ }
1225
+ ctx = c.getContext('2d');
1226
+ if (!ctx || typeof c.toDataURL !== 'function') {
1227
+ return null;
1228
+ }
1229
+ // just in case..
1230
+ try {
1231
+ ok = c.toDataURL('image/png');
1232
+ } catch(e) {
1233
+ // no canvas or no toDataURL()
1234
+ return null;
1235
+ }
1236
+ // assume we're all good.
1237
+ return c;
1238
+
1239
+ };
1240
+
1241
+ this.init = function() {
1242
+
1243
+ if (self.config.useFavIcon) {
1244
+ me.vuDataCanvas = me.testCanvas();
1245
+ if (me.vuDataCanvas && (isFirefox || isOpera)) {
1246
+ // these browsers support dynamically-updating the favicon
1247
+ me.createVUData();
1248
+ } else {
1249
+ // browser doesn't support doing this
1250
+ self.config.useFavIcon = false;
1251
+ }
1252
+ }
1253
+
1254
+ };
1255
+
1256
+ this.init();
1257
+
1258
+ };
1259
+
1260
+ // completely optional: Metadata/annotations/segments code
1261
+
1262
+ ThreeSixtyPlayer.prototype.Metadata = function(oSound, oParent) {
1263
+
1264
+ soundManager._wD('Metadata()');
1265
+
1266
+ var me = this,
1267
+ oBox = oSound._360data.oUI360,
1268
+ o = oBox.getElementsByTagName('ul')[0],
1269
+ oItems = o.getElementsByTagName('li'),
1270
+ isFirefox = (navigator.userAgent.match(/firefox/i)),
1271
+ isAlt = false, i, oDuration;
1272
+
1273
+ this.lastWPExec = 0;
1274
+ this.refreshInterval = 250;
1275
+ this.totalTime = 0;
1276
+
1277
+ this.events = {
1278
+
1279
+ whileplaying: function() {
1280
+
1281
+ var width = oSound._360data.width,
1282
+ radius = oSound._360data.radius,
1283
+ fullDuration = (oSound.durationEstimate||(me.totalTime*1000)),
1284
+ isAlt = null, i, j, d;
1285
+
1286
+ for (i=0,j=me.data.length; i<j; i++) {
1287
+ isAlt = (i%2===0);
1288
+ oParent.drawSolidArc(oSound._360data.oCanvas,(isAlt?oParent.config.segmentRingColorAlt:oParent.config.segmentRingColor),isAlt?width:width, isAlt?radius/2:radius/2, oParent.deg2rad(360*(me.data[i].endTimeMS/fullDuration)), oParent.deg2rad(360*((me.data[i].startTimeMS||1)/fullDuration)), true);
1289
+ }
1290
+ d = new Date();
1291
+ if (d-me.lastWPExec>me.refreshInterval) {
1292
+ me.refresh();
1293
+ me.lastWPExec = d;
1294
+ }
1295
+
1296
+ }
1297
+
1298
+ };
1299
+
1300
+ this.refresh = function() {
1301
+
1302
+ // Display info as appropriate
1303
+ var i, j, index = null,
1304
+ now = oSound.position,
1305
+ metadata = oSound._360data.metadata.data;
1306
+
1307
+ for (i=0, j=metadata.length; i<j; i++) {
1308
+ if (now >= metadata[i].startTimeMS && now <= metadata[i].endTimeMS) {
1309
+ index = i;
1310
+ break;
1311
+ }
1312
+ }
1313
+ if (index !== metadata.currentItem && index < metadata.length) {
1314
+ // update
1315
+ oSound._360data.oLink.innerHTML = metadata.mainTitle+' <span class="metadata"><span class="sm2_divider"> | </span><span class="sm2_metadata">'+metadata[index].title+'</span></span>';
1316
+ // self.setPageTitle(metadata[index].title+' | '+metadata.mainTitle);
1317
+ metadata.currentItem = index;
1318
+ }
1319
+
1320
+ };
1321
+
1322
+ this.strToTime = function(sTime) {
1323
+ var segments = sTime.split(':'),
1324
+ seconds = 0, i;
1325
+ for (i=segments.length; i--;) {
1326
+ seconds += parseInt(segments[i], 10)*Math.pow(60,segments.length-1-i); // hours, minutes
1327
+ }
1328
+ return seconds;
1329
+ };
1330
+
1331
+ this.data = [];
1332
+ this.data.givenDuration = null;
1333
+ this.data.currentItem = null;
1334
+ this.data.mainTitle = oSound._360data.oLink.innerHTML;
1335
+
1336
+ for (i=0; i<oItems.length; i++) {
1337
+ this.data[i] = {
1338
+ o: null,
1339
+ title: oItems[i].getElementsByTagName('p')[0].innerHTML,
1340
+ startTime: oItems[i].getElementsByTagName('span')[0].innerHTML,
1341
+ startSeconds: me.strToTime(oItems[i].getElementsByTagName('span')[0].innerHTML.replace(/[()]/g,'')),
1342
+ duration: 0,
1343
+ durationMS: null,
1344
+ startTimeMS: null,
1345
+ endTimeMS: null,
1346
+ oNote: null
1347
+ };
1348
+ }
1349
+ oDuration = oParent.getElementsByClassName('duration','div',oBox);
1350
+ this.data.givenDuration = (oDuration.length?me.strToTime(oDuration[0].innerHTML)*1000:0);
1351
+ for (i=0; i<this.data.length; i++) {
1352
+ this.data[i].duration = parseInt(this.data[i+1]?this.data[i+1].startSeconds:(me.data.givenDuration?me.data.givenDuration:oSound.durationEstimate)/1000, 10)-this.data[i].startSeconds;
1353
+ this.data[i].startTimeMS = this.data[i].startSeconds*1000;
1354
+ this.data[i].durationMS = this.data[i].duration*1000;
1355
+ this.data[i].endTimeMS = this.data[i].startTimeMS+this.data[i].durationMS;
1356
+ this.totalTime += this.data[i].duration;
1357
+ }
1358
+
1359
+ };
1360
+
1361
+ if (navigator.userAgent.match(/webkit/i) && navigator.userAgent.match(/mobile/i)) {
1362
+ // iPad, iPhone etc.
1363
+ soundManager.setup({
1364
+ useHTML5Audio: true
1365
+ });
1366
+ }
1367
+
1368
+ soundManager.setup({
1369
+ html5PollingInterval: 50, // increased framerate for whileplaying() etc.
1370
+ debugMode: (window.location.href.match(/debug=1/i)), // disable or enable debug output
1371
+ consoleOnly: true,
1372
+ flashVersion: 9,
1373
+ useHighPerformance: true,
1374
+ useFlashBlock: true
1375
+ });
1376
+
1377
+ // FPS data, testing/debug only
1378
+ if (soundManager.debugMode) {
1379
+ window.setInterval(function() {
1380
+ var p = window.threeSixtyPlayer;
1381
+ if (p && p.lastSound && p.lastSound._360data.fps && typeof window.isHome === 'undefined') {
1382
+ soundManager._writeDebug('fps: ~'+p.lastSound._360data.fps);
1383
+ p.lastSound._360data.fps = 0;
1384
+ }
1385
+ },1000);
1386
+ }
1387
+
1388
+ window.ThreeSixtyPlayer = ThreeSixtyPlayer; // constructor
1389
+
1390
+ }(window));
1391
+
1392
+ threeSixtyPlayer = new ThreeSixtyPlayer();
1393
+
1394
+ // hook into SM2 init
1395
+ soundManager.onready(threeSixtyPlayer.init);
1396
+