PageSourceSearch

http://radiohay.am/wp-content/themes/onair2/components/soundmanage…/qt-360player-volumecontroller.js?ver=1.0.0

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

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.