PageSourceSearch

https://mannafm.hu/wp-content/themes/onair2/components/soundmanage…/qt-360player-volumecontroller.js?ver=1.0.0

js mannafm.hu collected 2026-10-02 21:54:54 UTC 48,739 bytes, 1,513 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.