PageSourceSearch

https://frecuenciamillennial.com/includes/components/soundmanager/…/script/qt-360player-volumecontroller.js

js frecuenciamillennial.com collected 2026-10-08 05:31:15 UTC 59,354 bytes, 1,993 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	var self = this,
29			pl = this,
30			sm = soundManager, // soundManager instance
31			uA = navigator.userAgent,
32			isIE = (uA.match(/msie/i)),
33			isOpera = (uA.match(/opera/i)),
34			isSafari = (uA.match(/safari/i)),
35			isChrome = (uA.match(/chrome/i)),
36			isFirefox = (uA.match(/firefox/i)),
37			isTouchDevice = (uA.match(/ipad|iphone/i)),
38			hasRealCanvas = (typeof window.G_vmlCanvasManager === 'undefined' && typeof document.createElement('canvas').getContext('2d') !== 'undefined'),
39			// I dunno what Opera doesn't like about this. I'm probably doing it wrong.
40			fullCircle = (isOpera||isChrome?359.9:360),
41			// exclude old IE from hi-DPI / "retina"-scale display size
42			hiDPIScale = (navigator.userAgent.match(/msie [678]/i)? 1 : 2);
43
44	if(undefined === window.soundManagerVolume) {
45		window.soundManagerVolume = 100;
46	}
47	if(undefined === window.soundManagerVolumeRad) {
48		window.soundManagerVolumeRad = 360;
49	}
50
51
52	// CSS class for ignoring MP3 links
53	this.excludeClass = 'threesixty-exclude';
54	this.links = [];
55	this.sounds = [];
56	this.soundsByURL = [];
57	this.indexByURL = [];
58	this.lastSound = null;
59	this.lastTouchedSound = null;
60	this.soundCount = 0;
61	this.oUITemplate = null;
62	this.oUIImageMap = null;
63	this.vuMeter = null;
64	this.callbackCount = 0;
65	this.peakDataHistory = [];
66
67	// 360player configuration options
68	this.config = {
69
70		playNext: false,   // stop after one sound, or play through list until end
71		autoPlay: false,   // start playing the first sound right away
72		allowMultiple: false,  // let many sounds play at once (false = only one sound playing at a time)
73		loadRingColor: '#ccc', // how much has loaded
74		playRingColor: '#000', // how much has played
75		backgroundRingColor: '#eee', // color shown underneath load + play ("not yet loaded" color)
76
77		// optional segment/annotation (metadata) stuff..
78		segmentRingColor: 'rgba(255,255,255,0.33)', // metadata/annotation (segment) colors
79		segmentRingColorAlt: 'rgba(0,0,0,0.1)',
80		loadRingColorMetadata: '#ddd', // "annotations" load color
81		playRingColorMetadata: 'rgba(128,192,256,0.9)', // how much has played when metadata is present
82
83		circleDiameter: null, // set dynamically according to values from CSS
84		circleRadius: null,
85		animDuration: 500,
86		animTransition: window.Animator.tx.bouncy, // http://www.berniecode.com/writing/animator.html
87		showHMSTime: false, // hours:minutes:seconds vs. seconds-only
88		scaleFont: true,  // also set the font size (if possible) while animating the circle
89
90		// optional: spectrum or EQ graph in canvas (not supported in IE <9, too slow via ExCanvas)
91		useWaveformData: false,
92		waveformDataColor: '#0099ff',
93		waveformDataDownsample: 3, // use only one in X (of a set of 256 values) - 1 means all 256
94		waveformDataOutside: false,
95		waveformDataConstrain: false, // if true, +ve values only - keep within inside circle
96		waveformDataLineRatio: 0.64,
97
98		// "spectrum frequency" option
99		useEQData: false,
100		eqDataColor: '#339933',
101		eqDataDownsample: 4, // use only one in X (of 256 values)
102		eqDataOutside: true,
103		eqDataLineRatio: 0.54,
104
105		// enable "amplifier" (canvas pulses like a speaker) effect
106		usePeakData: true,
107		peakDataColor: '#ff33ff',
108		peakDataOutside: true,
109		peakDataLineRatio: 0.5,
110		useAmplifier: true, // "pulse" like a speaker
111		fontSizeMax: null, // set according to CSS
112		scaleArcWidth: 1,  // thickness factor of playback progress ring
113		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)
114		playervolume: false, // igor // to use round control as volume controller depending on data-
114playervolume attribute
115	};
116
117	if(navigator.userAgent.match(/mobile/i)){
118		this.config.playervolume = false;
119	}
120
121	this.css = {
122
123		// CSS class names appended to link during various states
124		sDefault: 'sm2_link', // default state
125		sBuffering: 'sm2_buffering',
126		sPlaying: 'sm2_playing',
127		sPaused: 'sm2_paused'
128
129	};
130
131	this.addEventHandler = (typeof window.addEventListener !== 'undefined' ? function(o, evtName, evtHandler) {
132		return o.addEventListener(evtName,evtHandler,false);
133	} : function(o, evtName, evtHandler) {
134		o.attachEvent('on'+evtName,evtHandler);
135	});
136
137	this.removeEventHandler = (typeof window.removeEventListener !== 'undefined' ? function(o, evtName, evtHandler) {
138		return o.removeEventListener(evtName,evtHandler,false);
139	} : function(o, evtName, evtHandler) {
140		return o.detachEvent('on'+evtName,evtHandler);
141	});
142
143	this.hasClass = function(o,cStr) {
144		return typeof(o.className)!=='undefined'?o.className.match(new RegExp('(\\s|^)'+cStr+'(\\s|$)')):false;
145	};
146
147	this.addClass = function(o,cStr) {
148
149		if (!o || !cStr || self.hasClass(o,cStr)) {
150			return false;
151		}
152		o.className = (o.className?o.className+' ':'')+cStr;
153
154	};
155
156	this.removeClass = function(o,cStr) {
157
158		if (!o || !cStr || !self.hasClass(o,cStr)) {
159			return false;
160		}
161		o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),'');
162
163	};
164
165	this.getElementsByClassName = function(className,tagNames,oParent) {
166
167		var doc = (oParent||document),
168				matches = [], i,j, nodes = [];
169		if (typeof tagNames !== 'undefined' && typeof tagNames !== 'string') {
170			for (i=tagNames.length; i--;) {
171				if (!nodes || !nodes[tagNames[i]]) {
172					nodes[tagNames[i]] = doc.getElementsByTagName(tagNames[i]);
173				}
174			}
175		} else if (tagNames) {
176			nodes = doc.getElementsByTagName(tagNames);
177		} else {
178			nodes = doc.all||doc.getElementsByTagName('*');
179		}
180		if (typeof(tagNames)!=='string') {
181			for (i=tagNames.length; i--;) {
182				for (j=nodes[tagNames[i]].length; j--;) {
183					if (self.hasClass(nodes[tagNames[i]][j],className)) {
184						matches.push(nodes[tagNames[i]][j]);
185					}
186				}
187			}
188		} else {
189			for (i=0; i<nodes.length; i++) {
190				if (self.hasClass(nodes[i],className)) {
191					matches.push(nodes[i]);
192				}
193			}
194		}
195		return matches;
196
197	};
198
199	this.getParentByNodeName = function(oChild,sParentNodeName) {
200
201		if (!oChild || !sParentNodeName) {
202			return false;
203		}
204		sParentNodeName = sParentNodeName.toLowerCase();
205		while (oChild.parentNode && sParentNodeName !== oChild.parentNode.nodeName.toLowerCase()) {
206			oChild = oChild.parentNode;
207		}
208		return (oChild.parentNode && sParentNodeName === oChild.parentNode.nodeName.toLowerCase()?oChild.parentNode:null);
209
210	};
211
212	this.getParentByClassName = function(oChild,sParentClassName) {
213
214		if (!oChild || !sParentClassName) {
215			return false;
216		}
217		while (oChild.parentNode && !self.hasClass(oChild.parentNode,sParentClassName)) {
218			oChild = oChild.parentNode;
219		}
220		return (oChild.parentNode && self.hasClass(oChild.parentNode,sParentClassName)?oChild.parentNode:null);
221
222	};
223
224	this.getSoundByURL = function(sURL) {
225		return (typeof self.soundsByURL[sURL] !== 'undefined'?self.soundsByURL[sURL]:null);
226	};
227
228	this.isChildOfNode = function(o,sNodeName) {
229
230		if (!o || !o.parentNode) {
231			return false;
232		}
233		sNodeName = sNodeName.toLowerCase();
234		do {
235			o = o.parentNode;
236		} while (o && o.parentNode && o.nodeName.toLowerCase() !== sNodeName);
237		return (o && o.nodeName.toLowerCase() === sNodeName?o:null);
238
239	};
240
241	this.isChildOfClass = function(oChild,oClass) {
242
243		if (!oChild || !oClass) {
244			return false;
245		}
246		while (oChild.parentNode && !self.hasClass(oChild,oClass)) {
247			oChild = self.findParent(oChild);
248		}
249		return (self.hasClass(oChild,oClass));
250
251	};
252
253	this.findParent = function(o) {
254
255		if (!o || !o.parentNode) {
256			return false;
257		}
258		o = o.parentNode;
259		if (o.nodeType === 2) {
260			while (o && o.parentNode && o.parentNode.nodeType === 2) {
261				o = o.parentNode;
262			}
263		}
264		return o;
265
266	};
267
268	this.getStyle = function(o,sProp) {
269
270		// http://www.quirksmode.org/dom/getstyles.html
271		try {
272			if (o.currentStyle) {
273				return o.currentStyle[sProp];
274			} else if (window.getComputedStyle) {
275				return document.defaultView.getComputedStyle(o,null).getPropertyValue(sProp);
276			}
277		} catch(e) {
278			// oh well
279		}
280		return null;
281
282	};
283
284	this.findXY = function(obj) {
285
286		var curleft = 0, curtop = 0;
287		do {
288			curleft += obj.offsetLeft;
289			curtop += obj.offsetTop;
290		} while (!!(obj = obj.offsetParent));
291		return [curleft,curtop];
292
293	};
294
295	this.getMouseXY = function(e) {
296
297		// http://www.quirksmode.org/js/events_properties.html
298		e = e?e:window.event;
299		if (isTouchDevice && e.touches) {
300			e = e.touches[0];
301		}
302		if (e.pageX || e.pageY) {
303			return [e.pageX,e.pageY];
304		} else if (e.clientX || e.clientY) {
305			return [e.clientX+self.getScrollLeft(),e.clientY+self.getScrollTop()];
306		}
307
308	};
309
310	this.getScrollLeft = function() {
311		return (document.body.scrollLeft+document.documentElement.scrollLeft);
312	};
313
314	this.getScrollTop = function() {
315		return (document.body.scrollTop+document.documentElement.scrollTop);
316	};
317
318
319
320
321
322
323
324
325	/* ============================= EVENT ============================= */
326
327	this.events = {
328
329		// handlers for sound events as they're started/stopped/played
330
331		play: function() {
332			pl.removeClass(this._360data.oUIBox,this._360data.className);
333			this._360data.className = pl.css.sPlaying;
334			pl.addClass(this._360data.oUIBox,this._360data.className);
335			self.fanOut(this);
336		},
337
338		stop: function() {
339			pl.removeClass(this._360data.oUIBox,this._360data.className);
340			this._360data.className = '';
341			self.fanIn(this);
342		},
343
344		pause: function() {
345			pl.removeClass(this._360data.oUIBox,this._360data.className);
346			this._360data.className = pl.css.sPaused;
347			pl.addClass(this._360data.oUIBox,this._360data.className);
348		},
349
350		resume: function() {
351			pl.removeClass(this._360data.oUIBox,this._360data.className);
352			this._360data.className = pl.css.sPlaying;
353			pl.addClass(this._360data.oUIBox,this._360data.className);      
354		},
355
356		finish: function() {
357			var nextLink;
358			pl.removeClass(this._360data.oUIBox,this._360data.className);
359			this._360data.className = '';
360			// self.clearCanvas(this._360data.oCanvas);
361			this._360data.didFinish = true; // so fan draws full circle
362			self.fanIn(this);
363			if (pl.config.playNext) {
364				nextLink = (pl.indexByURL[this._360data.oLink.href]+1);
365				if (nextLink<pl.links.length) {
366					pl.handleClick({'target':pl.links[nextLink]});
367				}
368			}
369		},
370
371		whileloading: function() {
372			if (this.paused) {
373				self.updatePlaying.apply(this);
374			}
375		},
376
377		whileplaying: function() {
378			self.updatePlaying.apply(this);
379			this._360data.fps++;
380		},
381
382		bufferchange: function() {
383			if (this.isBuffering) {
384				pl.addClass(this._360data.oUIBox,pl.css.sBuffering);
385			} else {
386				pl.removeClass(this._360data.oUIBox,pl.css.sBuffering);
387			}
388		}
389
390	};
391
392
393
394
395	/* ============================= EVENT END ============================= */
396
397
398
399
400	this.stopEvent = function(e) {
401
402	 if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') {
403			e.preventDefault();
404		} else if (typeof window.event !== 'undefined' && typeof window.event.returnValue !== 'undefined') {
405			window.event.returnValue = false;
406		}
407		return false;
408
409	};
410
411	this.getTheDamnLink = (isIE)?function(e) {
412		// I really didn't want to have to do this.
413		return (e && e.target?e.target:window.event.srcElement);
414	}:function(e) {
415		return e.target;
416	};
417
418	this.handleClick = function(e) {
419
420		// a sound link was clicked
421		if (e.button > 1) {
422			// only catch left-clicks
423			return true;
424		}
425
426		var o = self.getTheDamnLink(e),
427				canvasElements, sURL, soundURL, thisSound, oContainer, has_vis, diameter;
428
429		if (o.nodeName.toLowerCase() !== 'a') {
430			o = self.isChildOfNode(o,'a');
431			if (!o) {
432				return true;
433			}
434		}
435
436		if (!self.isChildOfClass(o,'ui360')) {
437			// not a link we're interested in
438			return true;
439		}
440
441		sURL = o.getAttribute('href');
442
443		if (!o.href || !sm.canPlayLink(o) || self.hasClass(o,self.excludeClass)) {
444			console.log("Non MP3 link detected");
445			return true; // pass-thru for non-MP3/non-links
446		}
447
448		sm._writeDebug('handleClick()');
449		soundURL = (o.href);
450		thisSound = self.getSoundByURL(soundURL);
451
452		if (thisSound) {
453
454			// already exists
455			if (thisSound === self.lastSound) {
456				// and was playing (or paused)
457				thisSound.togglePause();
458			} else {
459				// different sound
460				thisSound.togglePause(); // start playing current
461				sm._writeDebug('sound different than last sound: '+self.lastSound.id);
462				if (!self.config.allowMultiple && self.lastSound) {
463					self.stopSound(self.lastSound);
464				}
465			}
466
467		} else {
468
469			// append some dom shiz, make noise
470
471			oContainer = o.parentNode;
472			has_vis = (self.getElementsByClassName('ui360-vis','div',oContainer.parentNode).length);
473
474			// create sound
475			thisSound = sm.createSound({
476				id:'radioSound',
477				url:soundURL,
478				onplay:self.events.play,
479				onstop:self.events.stop,
480				onpause:self.events.pause,
481				onresume:self.events.resume,
482				onfinish:self.events.finish,
483				onbufferchange:self.events.bufferchange,
484				type:(o.type||null),
485				whileloading:self.events.whileloading,
486				whileplaying:self.events.whileplaying,
487				useWaveformData:(has_vis && self.config.useWaveformData),
488				useEQData:(has_vis && self.config.useEQData),
489				usePeakData:(has_vis && self.config.usePeakData)
490			});
491
492			// tack on some custom data
493
494			diameter = parseInt(self.getElementsByClassName('sm2-360ui','div',oContainer)[0].offsetWidth * hiDPIScale, 10);
495
496			// see note re: IE <9 and excanvas when Modernizr is included, making weird things happen with <canvas>.
497			canvasElements = self.getElementsByClassName('sm2-canvas','canvas',oContainer),
498
499			thisSound._360data = {
500				oUI360: self.getParentByClassName(o,'ui360'), // the (whole) entire container
501				oLink: o, // DOM node for reference within SM2 object event handlers
502				className: self.css.sPlaying,
503				oUIBox: self.getElementsByClassName('sm2-360ui','div',oContainer)[0],
504				oCanvas: canvasElements[canvasElements.length-1],
505				oButton: self.getElementsByClassName('sm2-360btn','span',oContainer)[0],
506				oTiming: self.getElementsByClassName('sm2-timing','div',oContainer)[0],
507				oCover: self.getElementsByClassName('sm2-cover','div',oContainer)[0],
508				circleDiameter: diameter,
509				circleRadius: diameter/2,
510				lastTime: null,
511				didFinish: null,
512				pauseCount:0,
513				radius:0,
514				fontSize: 1,
515				fontSizeMax: self.config.fontSizeMax,
516				scaleFont: (has_vis && self.config.scaleFont),
517				showHMSTime: has_vis,
518				amplifier: (has_vis && self.config.usePeakData?0.9:1), // TODO: x1 if not being used, else use dynamic "how much to amplify by" value
519				radiusMax: diameter*0.175, // circle radius
520				width:0,
521				widthMax: diameter*0.4, // width of the outer ring
522				lastValues: {
523					bytesLoaded: 0,
524					bytesTotal: 0,
525					position: 0,
526					durationEstimate: 0
527				}, // used to track "last good known" values before sound finish/reset for anim
528				animating: false,
529				oAnim: new window.Animator({
530					duration: self.config.animDuration,
531					transition:self.config.animTransition,
532					onComplete: function() {
533						// var thisSound = this;
534						// thisSound._360data.didFinish = false; // reset full circle
535					}
536				}),
537				oAnimProgress: function(nProgress) {
538					var thisSound = this;
539					thisSound._360data.radius = parseInt(thisSound._360data.radiusMax*thisSound._360data.amplifier*nProgress, 10);
540					thisSound._360data.width = parseInt(thisSound._360data.widthMax*thisSound._360data.amplifier*nProgress, 10);
541					if (thisSound._360data.scaleFont && thisSound._360data.fontSizeMax !== null) {
542						thisSound._360data.oTiming.style.fontSize = parseInt(Math.max(1,thisSound._360data.fontSizeMax*nProgress), 10)+'px';
543						thisSound._360data.oTiming.style.opacity = nProgress;
544					}
545					if (thisSound.paused || thisSound.playState === 0 || thisSound._360data.lastValues.bytesLoaded === 0 || thisSound._360data.lastValues.position === 0) {
546						self.updatePlaying.apply(thisSound);
547					}
548				},
549				fps: 0
550			};
551
552			// "Metadata" (annotations)
553			if (typeof self.Metadata !== 'undefined' && self.getElementsByClassName('metadata','div',thisSound._360data.oUI360).length) {
554				thisSound._360data.metadata = new self.Metadata(thisSound,self);
555			}
556
557			// minimize ze font
558			if (thisSound._360data.scaleFont && thisSound._360data.fontSizeMax !== null) {
559				thisSound._360data.oTiming.style.fontSize = '1px';
560			}
561
562			// set up ze animation
563			thisSound._360data.oAnim.addSubject(thisSound._360data.oAnimProgress,thisSound);
564
565			// animate the radius out nice
566			self.refreshCoords(thisSound);
567
568			self.updatePlaying.apply(thisSound);
569
570			self.soundsByURL[soundURL] = thisSound;
571			self.sounds.push(thisSound);
572			if (!self.config.allowMultiple && self.lastSound) {
573				self.stopSound(self.lastSound);
574			}
575			thisSound.play();
576
577		}
578
579		self.lastSound = thisSound; // reference for next call
580
581		if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') {
582			e.preventDefault();
583		} else if (typeof window.event !== 'undefined') {
584			window.event.returnValue = false;
585		}
586		return false;
587
588	};
589
590	this.fanOut = function(oSound) {
591
592		 var thisSound = oSound;
593		 if (thisSound._360data.animating === 1) {
594			 return false;
595		 }
596		 thisSound._360data.animating = 0;
597		 soundManager._writeDebug('fanOut: '+thisSound.id+': '+thisSound._360data.oLink.href);
598		 thisSound._360data.oAnim.seekTo(1); // play to end
599		 window.setTimeout(function() {
600			 // oncomplete hack
601			 thisSound._360data.animating = 0;
602		 },self.config.animDuration+20);
603
604	};
605
606	this.fanIn = function(oSound) {
607
608		 var thisSound = oSound;
609		 if (thisSound._360data.animating === -1) {
610			 return false;
611		 }
612		 thisSound._360data.animating = -1;
613		 soundManager._writeDebug('fanIn: '+thisSound.id+': '+thisSound._360data.oLink.href);
614		 // massive hack
615		 thisSound._360data.oAnim.seekTo(0); // play to end
616		 window.setTimeout(function() {
617			 // reset full 360 fill after animation has completed (oncomplete hack)
618			 thisSound._360data.didFinish = false;
619			 thisSound._360data.animating = 0;
620			 self.resetLastValues(thisSound);
621		 }, self.config.animDuration+20);
622
623	};
624
625	this.resetLastValues = function(oSound) {
626		oSound._360data.lastValues.position = 0;
627	};
628
629	this.refreshCoords = function(thisSound) {
630
631		thisSound._360data.canvasXY = self.findXY(thisSound._360data.oCanvas);
632		thisSound._360data.canvasMid = [thisSound._360data.circleRadius,thisSound._360data.circleRadius];
633		thisSound._360data.canvasMidXY = [thisSound._360data.canvasXY[0]+thisSound._360data.canvasMid[0], thisSound._360data.canvasXY[1]+thisSound._360data.canvasMid[1]];
634
635	};
636
637	this.stopSound = function(oSound) {
638
639		soundManager._writeDebug('stopSound: '+oSound.id);
640		soundManager.stop(oSound.id);
641		if (!isTouchDevice) { // iOS 4.2+ security blocks onfinish() -> playNext() if we set a .src in-between(?)
642			soundManager.unload(oSound.id);
643		}
644
645	};
646
647	this.buttonClick = function(e) {
648
649		var o = e?(e.target?e.target:e.srcElement):window.event.srcElement;
650		self.handleClick({target:self.getParentByClassName(o,'sm2-360ui').nextSibling}); // link next to the nodes we inserted
651		return false;
652
653	};
654
655	this.buttonMouseDown = function(e) {
656
657		// user might decide to drag from here
658		// watch for mouse move
659		if (!isTouchDevice) {
660			document.onmousemove = function(e) {
661				// should be boundary-checked, really (eg. move 3px first?)
662				self.mouseDown(e);
663			};
664		} else {
665			self.addEventHandler(document,'touchmove',self.mouseDown);
666		}
667		self.stopEvent(e);
668		return false;
669
670	};
671
672	this.mouseDown = function(e) {
673
674		if (!isTouchDevice && e.button > 1) {
675			return true; // ignore non-left-click
676		}
677
678		if (!self.lastSound) {
679			self.stopEvent(e);
680			return false;
681		}
682
683		var evt = e?e:window.event,
684				target, thisSound, oData;
685
686		if (isTouchDevice && evt.touches) {
687			evt = evt.touches[0];
688		}
689		target = (evt.target||evt.srcElement);
690
691		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.
692		// just in case, update coordinates (maybe the element moved since last time.)
693		self.lastTouchedSound = thisSound;
694		self.refreshCoords(thisSound);
695		oData = thisSound._360data;
696		self.addClass(oData.oUIBox,'sm2_dragging');
697		oData.pauseCount = (self.lastTouchedSound.paused?1:0);
698		// self.lastSound.pause();
699		self.mmh(e?e:window.event);
700
701
702
703		if (isTouchDevice) {
704			self.removeEventHandler(document,'touchmove',self.mouseDown);
705			self.addEventHandler(document,'touchmove',self.mmh);
706			self.addEventHandler(document,'touchend',self.mouseUp);
707		} else {
708			// incredibly old-skool. TODO: Modernize.
709			document.onmousemove = self.mmh;
710			document.onmouseup = self.mouseUp;
711		}
712
713
714		self.stopEvent(e);
715		return false;
716
717	};
718
719	this.mouseUp = function(e) {
720
721		var oData = self.lastTouchedSound._360data;
722		self.removeClass(oData.oUIBox,'sm2_dragging');
723		if (oData.pauseCount === 0) {
724			self.lastTouchedSound.resume();
725		}
726		if (!isTouchDevice) {
727			document.onmousemove = null;
728			document.onmouseup = null;
729		} else {
730			self.removeEventHandler(document,'touchmove',self.mmh);
731			self.removeEventHandler(document,'touchend',self.mouseUP);
732		}
733
734	};
735
736	this.mmh = function(e) {
737
738		if (typeof e === 'undefined') {
739			e = window.event;
740		}
741		var oSound = self.lastTouchedSound,
742				coords = self.getMouseXY(e),
743				x = coords[0],
744				y = coords[1],
745				deltaX = x-oSound._360data.canvasMidXY[0],
746				deltaY = y-oSound._360data.canvasMidXY[1],
747				angle = Math.floor(fullCircle-(self.rad2deg(Math.atan2(deltaX,deltaY))+180)),
748				volume = 100 * (angle/fullCircle);
749
750
751		
752		
753		//igor =========================================================
754		//
755		// the player can control volume or timer dep on the playervolume attribute
756		// 
757		// 
758		// 
759		// 
760		// 
761		// =========================================================
762		
763		if( true === self.config.playervolume){
764			if(!navigator.userAgent.match(/mobile/i)) {
765				window.soundManagerVolume = volume;
766				window.soundManagerVolumeRad = angle;
767				soundManager.setVolume("radioSound",Math.round(volume));
768			} else {
769				console.log("Is a mobile device");
770			}
771		}  else {
772			oSound.setPosition(oSound.durationEstimate*(angle/fullCircle));
773		}
774
775		self.stopEvent(e);
776		return false;
777
778	};
779
780	// assignMouseDown();
781
782	this.drawSolidArc = function(oCanvas, color, radius, width, radians, startAngle, noClear) {
783
784		// thank you, http://www.snipersystems.co.nz/community/polarclock/tutorial.html
785		// console.log("Draw");
786		var x = radius,
787				y = radius,
788				canvas = oCanvas,
789				ctx, innerRadius, doesntLikeZero, endPoint;
790
791		if (canvas.getContext) {
792			// use getContext to use the canvas for drawing
793			ctx = canvas.getContext('2d');
794		}
795
796		// re-assign canvas as the actual context
797		oCanvas = ctx;
798
799		if (!noClear) {
800			self.clearCanvas(canvas);
801		}
802		// ctx.restore();
803
804		if (color) {
805			ctx.fillStyle = color;
806		}
807
808		oCanvas.beginPath();
809
810		if (isNaN(radians)) {
811			radians = 0;
812		}
813
814		innerRadius = radius-width;
815		doesntLikeZero = (isOpera || isSafari); // safari 4 doesn't actually seem to mind.
816
817		if (!doesntLikeZero || (doesntLikeZero && radius > 0)) {
818			oCanvas.arc(0, 0, radius, startAngle, radians, false);
819			endPoint = self.getArcEndpointCoords(innerRadius, radians);
820			oCanvas.lineTo(endPoint.x, endPoint.y);
821			oCanvas.arc(0, 0, innerRadius, radians, startAngle, true);
822			oCanvas.closePath();
823			oCanvas.fill();
824		}
825
826	};
827
828	this.getArcEndpointCoords = function(radius, radians) {
829
830		return {
831			x: radius * Math.cos(radians), 
832			y: radius * Math.sin(radians)
833		};
834
835	};
836
837	this.deg2rad = function(nDeg) {
838		return (nDeg * Math.PI/180);
839	};
840
841	this.rad2deg = function(nRad) {
842		return (nRad * 180/Math.PI);
843	};
844
845	this.getTime = function(nMSec,bAsString) {
846
847		// convert milliseconds to mm:ss, return as object literal or string
848		var nSec = Math.floor(nMSec/1000),
849				min = Math.floor(nSec/60),
850				sec = nSec-(min*60);
851		// if (min === 0 && sec === 0) return null; // return 0:00 as null
852		return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec});
853
854	};
855
856	this.clearCanvas = function(oCanvas) {
857
858		var canvas = oCanvas,
859				ctx = null,
860				width, height;
861
862		if (canvas.getContext) {
863			// use getContext to use the canvas for drawing
864			ctx = canvas.getContext('2d');
865		}
866
867		if (ctx) {
868			width = canvas.offsetWidth;
869			height = canvas.offsetHeight;
870			ctx.clearRect(-(width/2), -(height/2), width, height);
871		}
872
873	};
874
875	this.updatePlaying = function() {
876
877		var timeNow = (this._360data.showHMSTime?self.getTime(this.position,true):parseInt(this.position/1000, 10));
878		var ringScaleFactor = self.config.scaleArcWidth;
879
880		if (this.bytesLoaded) {
881			this._360data.lastValues.bytesLoaded = this.bytesLoaded;
882			this._360data.lastValues.bytesTotal = this.bytesTotal;
883		}
884
885		if (this.position) {
886			this._360data.lastValues.position = this.position;
887		}
888
889		if (this.durationEstimate) {
890			this._360data.lastValues.durationEstimate = this.durationEstimate;
891		}
892
893		// background ring
894		self.drawSolidArc(
895			this._360data.oCanvas,
896			self.config.backgroundRingColor,
897			this._360data.width,
898			this._360data.radius * ringScaleFactor,
899			self.deg2rad(fullCircle),
900			false
901			);
902
903		// play ring
904		// we change scope and use this for gthe volume
905		/*
906		*/
907		// igor
908		// window.soundManagerVolume
909		if( true === self.config.playervolume  && !navigator.userAgent.match(/mobile/i)){
910			self.drawSolidArc(
911				this._360data.oCanvas,
912				(this._360data.metadata?self.config.loadRingColorMetadata:self.config.playRingColor),
913				this._360data.width,this._360data.radius * ringScaleFactor,
914				self.deg2rad(window.soundManagerVolumeRad),
915				0,
916				true
917			);
918
919		} else {
920			// console.log("Displaying track circle");
921			// play
922			
923    		if (this._360data.lastValues.position !== 0) {
924				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);
925			}
926		}
927
928
929
930
931		// loaded ring
932		
933		 // 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);
934
935
936		// metadata goes here
937		if (this._360data.metadata) {
938			this._360data.metadata.events.whileplaying();
939		}
940
941		if (timeNow !== this._360data.lastTime) {
942			this._360data.lastTime = timeNow;
943			if( false === self.config.playervolume || navigator.userAgent.match(/mobile/i)){
944				this._360data.oTiming.innerHTML = timeNow;
945			}
946		}
947
948
949
950	    //
951	    if( true === self.config.playervolume && !navigator.userAgent.match(/mobile/i)){
952	    	this._360data.oTiming.innerHTML = Math.round(window.soundManagerVolume);
953		}
954
955		// draw spectrum, if applicable
956		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.
957			self.updateWaveform(this);
958		}
959
960		if (self.config.useFavIcon && self.vuMeter) {
961			self.vuMeter.updateVU(this);
962		}
963
964	};
965
966	this.updateWaveform = function(oSound) {
967
968		if ((!self.config.useWaveformData && !self.config.useEQData) || (!sm.features.waveformData && !sm.features.eqData)) {
969			// feature not enabled..
970			return false;
971		}
972
973		if (!oSound.waveformData.left.length && !oSound.eqData.length && !oSound.peakData.left) {
974			// no data (or errored out/paused/unavailable?)
975			return false;
976		}
977
978		/* use for testing the data */
979		/*
980		 for (i=0; i<256; i++) {
981			 oSound.eqData[i] = 1-(i/256);
982		 }
983		*/
984
985		var oCanvas = oSound._360data.oCanvas.getContext('2d'),
986				offX = 0,
987				offY = parseInt(oSound._360data.circleDiameter/2, 10),
988				scale = offY/2, // Y axis (+/- this distance from 0)
989				// lineWidth = Math.floor(oSound._360data.circleDiameter-(oSound._360data.circleDiameter*0.175)/(oSound._360data.circleDiameter/255)); // width for each line
990				lineWidth = 1,
991				lineHeight = 1,
992				thisY = 0,
993				offset = offY,
994				i, j, direction, downSample, dataLength, sampleCount, startAngle, endAngle, waveData, innerRadius, perItemAngle, yDiff, eqSamples, playedAngle, iAvg, nPeak;
995
996		if (self.config.useWaveformData) {
997			// raw waveform
998			downSample = self.config.waveformDataDownsample; // only sample X in 256 (greater number = less sample points)
999			downSample = Math.max(1,downSample); // make sure it's at least 1
1000			dataLength = 256;
1001			sampleCount = (dataLength/downSample);
1002			startAngle = 0;
1003			endAngle = 0;
1004			waveData = null;
1005			innerRadius = (self.config.waveformDataOutside?1:(self.config.waveformDataConstrain?0.5:0.565));
1006			scale = (self.config.waveformDataOutside?0.7:0.75);
1007			perItemAngle = self.deg2rad((360/sampleCount)*self.config.waveformDataLineRatio); // 0.85 = clean pixel lines at 150? // self.deg2rad(360*(Math.max(1,downSample-1))/sampleCount);
1008			for (i=0; i<dataLength; i+=downSample) {
1009				startAngle = self.deg2rad(360*(i/(sampleCount)*1/downSample)); // +0.67 - counter for spacing
1010				endAngle = startAngle+perItemAngle;
1011				waveData = oSound.waveformData.left[i];
1012				if (waveData<0 && self.config.waveformDataConstrain) {
1013					waveData = Math.abs(waveData);
1014				}
1015				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);
1016			}
1017		}
1018
1019		if (self.config.useEQData) {
1020			// EQ spectrum
1021			downSample = self.config.eqDataDownsample; // only sample N in 256
1022			yDiff = 0;
1023			downSample = Math.max(1,downSample); // make sure it's at least 1
1024			eqSamples = 192; // drop the last 25% of the spectrum (>16500 Hz), most stuff won't actually use it.
1025			sampleCount = (eqSamples/downSample);
1026			innerRadius = (self.config.eqDataOutside?1:0.565);
1027			direction = (self.config.eqDataOutside?-1:1);
1028			scale = (self.config.eqDataOutside?0.5:0.75);
1029			startAngle = 0;
1030			endAngle = 0;
1031			perItemAngle = self.deg2rad((360/sampleCount)*self.config.eqDataLineRatio); // self.deg2rad(360/(sampleCount+1));
1032			playedAngle = self.deg2rad((oSound._360data.didFinish===1?360:360*(oSound._360data.lastValues.position/oSound._360data.lastValues.durationEstimate)));
1033			j=0;
1034			iAvg = 0;
1035			for (i=0; i<eqSamples; i+=downSample) {
1036				startAngle = self.deg2rad(360*(i/eqSamples));
1037				endAngle = startAngle+perItemAngle;
1038				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);
1039			}
1040		}
1041
1042		if (self.config.usePeakData) {
1043			if (!oSound._360data.animating) {
1044				nPeak = (oSound.peakData.left||oSound.peakData.right);
1045				// GIANT HACK: use EQ spectrum data for bass frequencies
1046				eqSamples = 3;
1047				for (i=0; i<eqSamples; i++) {
1048					nPeak = (nPeak||oSound.eqData[i]);
1049				}
1050				oSound._360data.amplifier = (self.config.useAmplifier?(0.9+(nPeak*0.1)):1);
1051				oSound._360data.radiusMax = oSound._360data.circleDiameter*0.175*oSound._360data.amplifier;
1052				oSound._360data.widthMax = oSound._360data.circleDiameter*0.4*oSound._360data.amplifier;
1053				oSound._360data.radius = parseInt(oSound._360data.radiusMax*oSound._360data.amplifier, 10);
1054				oSound._360data.width = parseInt(oSound._360data.widthMax*oSound._360data.amplifier, 10);
1055			}
1056		}
1057
1058	};
1059
1060	this.getUIHTML = function(diameter) {
1061
1062		return [
1063		 '<canvas class="sm2-canvas" width="'+diameter+'" height="'+diameter+'"></canvas>',
1064		 ' <span class="sm2-360btn sm2-360btn-default"></span>', // note use of imageMap, edit or remove if you use a different-size image.
1065		 ' <div class="sm2-timing'+(navigator.userAgent.match(/safari/i)?' alignTweak':'')+'"></div>', // + Ever-so-slight Safari horizontal alignment tweak
1066		 ' <div class="sm2-cover"></div>'
1067		];
1068
1069	};
1070
1071	this.uiTest = function(sClass) {
1072
1073		// fake a 360 UI so we can get some numbers from CSS, etc.
1074
1075		var oTemplate = document.createElement('div'),
1076				oFakeUI, oFakeUIBox, oTemp, fakeDiameter, uiHTML, circleDiameter, circleRadius, fontSizeMax, oTiming;
1077
1078		oTemplate.className = 'sm2-360ui';
1079
1080		oFakeUI = document.createElement('div');
1081		oFakeUI.className = 'ui360'+(sClass?' '+sClass:''); // ui360 ui360-vis
1082
1083		oFakeUIBox = oFakeUI.appendChild(oTemplate.cloneNode(true));
1084
1085		oFakeUI.style.position = 'absolute';
1086		oFakeUI.style.left = '-9999px';
1087
1088		oTemp = document.body.appendChild(oFakeUI);
1089
1090		fakeDiameter = oFakeUIBox.offsetWidth * hiDPIScale;
1091
1092		uiHTML = self.getUIHTML(fakeDiameter);
1093
1094		oFakeUIBox.innerHTML = uiHTML[1]+uiHTML[2]+uiHTML[3];
1095
1096		circleDiameter = parseInt(fakeDiameter, 10);
1097		circleRadius = parseInt(circleDiameter/2, 10);
1098
1099		oTiming = self.getElementsByClassName('sm2-timing','div',oTemp)[0];
1100		fontSizeMax = parseInt(self.getStyle(oTiming,'font-size'), 10);
1101		if (isNaN(fontSizeMax)) {
1102			// getStyle() etc. didn't work.
1103			fontSizeMax = null;
1104		}
1105
1106		// soundManager._writeDebug('diameter, font size: '+circleDiameter+','+fontSizeMax);
1107
1108		oFakeUI.parentNode.removeChild(oFakeUI);
1109
1110		uiHTML = oFakeUI = oFakeUIBox = oTemp = null;
1111
1112		return {
1113			circleDiameter: circleDiameter,
1114			circleRadius: circleRadius,
1115			fontSizeMax: fontSizeMax
1116		};
1117
1118	};
1119
1120	this.init = function() {
1121
1122		sm._writeDebug('threeSixtyPlayer.init()');
1123
1124		var oItems = self.getElementsByClassName('ui360','div'),
1125				i, j, oLinks = [], is_vis = false, foundItems = 0, canvasElements, oCanvas, oCanvasCTX, oCover, diameter, radius, uiData, uiDataVis, oUI, oBtn, o, o2, oID;
1126
1127		for (i=0,j=oItems.length; i<j; i++) {
1128			oLinks.push(oItems[i].getElementsByTagName('a')[0]);
1129			// remove "fake" play button (unsupported case)
1130			oItems[i].style.backgroundImage = 'none';
1131		}
1132		// grab all links, look for .mp3
1133
1134		self.oUITemplate = document.createElement('div');
1135		self.oUITemplate.className = 'sm2-360ui';
1136
1137		self.oUITemplateVis = document.createElement('div');
1138		self.oUITemplateVis.className = 'sm2-360ui';
1139
1140		uiData = self.uiTest();
1141
1142		self.config.circleDiameter = uiData.circleDiameter;
1143		self.config.circleRadius = uiData.circleRadius;
1144		// self.config.fontSizeMax = uiData.fontSizeMax;
1145
1146		uiDataVis = self.uiTest('ui360-vis');
1147
1148		self.config.fontSizeMax = uiDataVis.fontSizeMax;
1149
1150		// canvas needs inline width and height, doesn't quite work otherwise
1151		self.oUITemplate.innerHTML = self.getUIHTML(self.config.circleDiameter).join('');
1152
1153		self.oUITemplateVis.innerHTML = self.getUIHTML(uiDataVis.circleDiameter).join('');
1154
1155		for (i=0,j=oLinks.length; i<j; i++) {
1156			if (sm.canPlayLink(oLinks[i]) && !self.hasClass(oLinks[i],self.excludeClass) && !self.hasClass(oLinks[i],self.css.sDefault)) {
1157				self.addClass(oLinks[i],self.css.sDefault); // add default CSS decoration
1158				self.links[foundItems] = (oLinks[i]);
1159				self.indexByURL[oLinks[i].href] = foundItems; // hack for indexing
1160				foundItems++;
1161
1162				is_vis = self.hasClass(oLinks[i].parentNode, 'ui360-vis');
1163
1164				diameter = (is_vis ? uiDataVis : uiData).circleDiameter;
1165				radius = (is_vis ? uiDataVis : uiData).circleRadius;
1166
1167				// add canvas shiz
1168				oUI = oLinks[i].parentNode.insertBefore((is_vis?self.oUITemplateVis:self.oUITemplate).cloneNode(true),oLinks[i]);
1169
1170				if (isIE && typeof window.G_vmlCanvasManager !== 'undefined') { // IE only
1171					o = oLinks[i].parentNode;
1172					o2 = document.createElement('canvas');
1173					o2.className = 'sm2-canvas';
1174					oID = 'sm2_canvas_'+i+(new Date().getTime());
1175					o2.id = oID;
1176					o2.width = diameter;
1177					o2.height = diameter;
1178					oUI.appendChild(o2);
1179					window.G_vmlCanvasManager.initElement(o2); // Apply ExCanvas compatibility magic
1180					oCanvas = document.getElementById(oID);
1181					/**
1182					 * 05/2013: If present, Modernizr results in two canvas elements or something being made, one being <:canvas>.
1183					 * When this is the case, the first doesn't have getContext('2d') and such - so, use the second.
1184					 */
1185					 canvasElements = oCanvas.parentNode.getElementsByTagName('canvas');
1186					 if (canvasElements.length > 1) {
1187						 oCanvas = canvasElements[canvasElements.length-1];
1188					 }
1189				} else { 
1190					// add a handler for the button
1191					oCanvas = oLinks[i].parentNode.getElementsByTagName('canvas')[0];
1192				}
1193				// enable hi-DPI / retina features?
1194				if (hiDPIScale > 1) {
1195					self.addClass(oCanvas, 'hi-dpi');
1196				}
1197				oCover = self.getElementsByClassName('sm2-cover','div',oLinks[i].parentNode)[0];
1198				oBtn = oLinks[i].parentNode.getElementsByTagName('span')[0];
1199				self.addEventHandler(oBtn,'click',self.buttonClick);
1200				if (!isTouchDevice) {
1201					self.addEventHandler(oCover,'mousedown',self.mouseDown);
1202				} else {
1203					self.addEventHandler(oCover,'touchstart',self.mouseDown);
1204				}
1205				oCanvasCTX = oCanvas.getContext('2d');
1206				oCanvasCTX.translate(radius, radius);
1207				oCanvasCTX.rotate(self.deg2rad(-90)); // compensate for arc starting at EAST // http://stackoverflow.com/questions/319267/tutorial-for-html-canvass-arc-function
1208			}
1209		}
1210		if (foundItems>0) {
1211			self.addEventHandler(document,'click',self.handleClick);
1212			if (self.config.autoPlay) {
1213				self.handleClick({target:self.links[0],preventDefault:function(){}});
1214			}
1215		}
1216		sm._writeDebug('threeSixtyPlayer.init(): Found '+foundItems+' relevant items.');
1217
1218		if (self.config.useFavIcon && typeof this.VUMeter !== 'undefined') {
1219			this.vuMeter = new this.VUMeter(this);
1220		}
1221
1222	};
1223
1224}
1225
1226// Optional: VU Meter component
1227
1228ThreeSixtyPlayer.prototype.VUMeter = function(oParent) {
1229
1230	var self = oParent,
1231			me = this,
1232			_head = document.getElementsByTagName('head')[0],
1233			isOpera = (navigator.userAgent.match(/opera/i)),
1234			isFirefox = (navigator.userAgent.match(/firefox/i));
1235
1236	this.vuMeterData = [];
1237	this.vuDataCanvas = null;
1238
1239	this.setPageIcon = function(sDataURL) {
1240
1241		if (!self.config.useFavIcon || !self.config.usePeakData || !sDataURL) {
1242			return false;
1243		}
1244
1245		var link = document.getElementById('sm2-favicon');
1246		if (link) {
1247			_head.removeChild(link);
1248			link = null;
1249		}
1250		if (!link) {
1251			link = document.createElement('link');
1252			link.id = 'sm2-favicon';
1253			link.rel = 'shortcut icon';
1254			link.type = 'image/png';
1255			link.href = sDataURL;
1256			document.getElementsByTagName('head')[0].appendChild(link);
1257		}
1258
1259	};
1260
1261	this.resetPageIcon = function() {
1262
1263		if (!self.config.useFavIcon) {
1264			return false;
1265		}
1266		var link = document.getElementById('favicon');
1267		if (link) {
1268			link.href = '/favicon.ico';
1269		}
1270
1271	};
1272
1273	this.updateVU = function(oSound) {
1274
1275		if (soundManager.flashVersion >= 9 && self.config.useFavIcon && self.config.usePeakData) {
1276			me.setPageIcon(me.vuMeterData[parseInt(16*oSound.peakData.left, 10)][parseInt(16*oSound.peakData.right, 10)]);
1277		}
1278
1279	};
1280
1281	this.createVUData = function() {
1282
1283		var i=0, j=0,
1284				canvas = me.vuDataCanvas.getContext('2d'),
1285				vuGrad = canvas.createLinearGradient(0, 16, 0, 0),
1286				bgGrad = canvas.createLinearGradient(0, 16, 0, 0),
1287				outline = 'rgba(0,0,0,0.2)';
1288
1289		vuGrad.addColorStop(0,'rgb(0,192,0)');
1290		vuGrad.addColorStop(0.30,'rgb(0,255,0)');
1291		vuGrad.addColorStop(0.625,'rgb(255,255,0)');
1292		vuGrad.addColorStop(0.85,'rgb(255,0,0)');
1293		bgGrad.addColorStop(0,outline);
1294		bgGrad.addColorStop(1,'rgba(0,0,0,0.5)');
1295		for (i=0; i<16; i++) {
1296			me.vuMeterData[i] = [];
1297		}
1298		for (i=0; i<16; i++) {
1299			for (j=0; j<16; j++) {
1300				// reset/erase canvas
1301				me.vuDataCanvas.setAttribute('width',16);
1302				me.vuDataCanvas.setAttribute('height',16);
1303				// draw new stuffs
1304				canvas.fillStyle = bgGrad;
1305				canvas.fillRect(0,0,7,15);
1306				canvas.fillRect(8,0,7,15);
1307				/*
1308				// shadow
1309				canvas.fillStyle = 'rgba(0,0,0,0.1)';
1310				canvas.fillRect(1,15-i,7,17-(17-i));
1311				canvas.fillRect(9,15-j,7,17-(17-j));
1312				*/
1313				canvas.fillStyle = vuGrad;
1314				canvas.fillRect(0,15-i,7,16-(16-i));
1315				canvas.fillRect(8,15-j,7,16-(16-j));
1316				// and now, clear out some bits.
1317				canvas.clearRect(0,3,16,1);
1318				canvas.clearRect(0,7,16,1);
1319				canvas.clearRect(0,11,16,1);
1320				me.vuMeterData[i][j] = me.vuDataCanvas.toDataURL('image/png');
1321				// for debugging VU images
1322				/*
1323				var o = document.createElement('img');
1324				o.style.marginRight = '5px'; 
1325				o.src = vuMeterData[i][j];
1326				document.documentElement.appendChild(o);
1327				*/
1328			}
1329		}
1330
1331	};
1332
1333	this.testCanvas = function() {
1334
1335		// canvas + toDataURL();
1336		var c = document.createElement('canvas'),
1337				ctx = null, ok;
1338		if (!c || typeof c.getContext === 'undefined') {
1339			return null;
1340		}
1341		ctx = c.getContext('2d');
1342		if (!ctx || typeof c.toDataURL !== 'function') {
1343			return null;
1344		}
1345		// just in case..
1346		try {
1347			ok = c.toDataURL('image/png');
1348		} catch(e) {
1349			// no canvas or no toDataURL()
1350			return null;
1351		}
1352		// assume we're all good.
1353		return c;
1354
1355	};
1356
1357	this.init = function() {
1358
1359		if (self.config.useFavIcon) {
1360			me.vuDataCanvas = me.testCanvas();
1361			if (me.vuDataCanvas && (isFirefox || isOpera)) {
1362				// these browsers support dynamically-updating the favicon
1363				me.createVUData();
1364			} else {
1365				// browser doesn't support doing this
1366				self.config.useFavIcon = false;
1367			}
1368		}
1369
1370	};
1371
1372	this.init();
1373
1374};
1375
1376// completely optional: Metadata/annotations/segments code
1377
1378ThreeSixtyPlayer.prototype.Metadata = function(oSound, oParent) {
1379
1380	soundManager._wD('Metadata()');
1381
1382	var me = this,
1383			oBox = oSound._360data.oUI360,
1384			o = oBox.getElementsByTagName('ul')[0],
1385			oItems = o.getElementsByTagName('li'),
1386			isFirefox = (navigator.userAgent.match(/firefox/i)),
1387			isAlt = false, i, oDuration;
1388
1389	this.lastWPExec = 0;
1390	this.refreshInterval = 250;
1391	this.totalTime = 0;
1392
1393	this.events = {
1394
1395		whileplaying: function() {
1396
1397			var width = oSound._360data.width,
1398					radius = oSound._360data.radius,
1399					fullDuration = (oSound.durationEstimate||(me.totalTime*1000)),
1400					isAlt = null, i, j, d;
1401
1402			for (i=0,j=me.data.length; i<j; i++) {
1403				isAlt = (i%2===0);
1404				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);
1405			}
1406			d = new Date();
1407			if (d-me.lastWPExec>me.refreshInterval) {
1408				me.refresh();
1409				me.lastWPExec = d;
1410			}
1411
1412		}
1413
1414	};
1415
1416	this.refresh = function() {
1417
1418		// Display info as appropriate
1419		var i, j, index = null,
1420				now = oSound.position,
1421				metadata = oSound._360data.metadata.data;
1422
1423		for (i=0, j=metadata.length; i<j; i++) {
1424			if (now >= metadata[i].startTimeMS && now <= metadata[i].endTimeMS) {
1425				index = i;
1426				break;
1427			}
1428		}
1429		if (index !== metadata.currentItem && index < metadata.length) {
1430			// update
1431			oSound._360data.oLink.innerHTML = metadata.mainTitle+' <span class="metadata"><span class="sm2_divider"> | </span><span class="sm2_metadata">'+metadata[index].title+'</span></span>';
1432			// self.setPageTitle(metadata[index].title+' | '+metadata.mainTitle);
1433			metadata.currentItem = index;
1434		}
1435
1436	};
1437
1438	this.strToTime = function(sTime) {
1439		var segments = sTime.split(':'),
1440				seconds = 0, i;
1441		for (i=segments.length; i--;) {
1442			seconds += parseInt(segments[i], 10)*Math.pow(60,segments.length-1-i); // hours, minutes
1443		}
1444		return seconds;
1445	};
1446
1447	this.data = [];
1448	this.data.givenDuration = null;
1449	this.data.currentItem = null;
1450	this.data.mainTitle = oSound._360data.oLink.innerHTML;
1451
1452	for (i=0; i<oItems.length; i++) {
1453		this.data[i] = {
1454			o: null,
1455			title: oItems[i].getElementsByTagName('p')[0].innerHTML,
1456			startTime: oItems[i].getElementsByTagName('span')[0].innerHTML,
1457			startSeconds: me.strToTime(oItems[i].getElementsByTagName('span')[0].innerHTML.replace(/[()]/g,'')),
1458			duration: 0,
1459			durationMS: null,
1460			startTimeMS: null,
1461			endTimeMS: null,
1462			oNote: null
1463		};
1464	}
1465	oDuration = oParent.getElementsByClassName('duration','div',oBox);
1466	this.data.givenDuration = (oDuration.length?me.strToTime(oDuration[0].innerHTML)*1000:0);
1467	for (i=0; i<this.data.length; i++) {
1468		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;
1469		this.data[i].startTimeMS = this.data[i].startSeconds*1000;
1470		this.data[i].durationMS = this.data[i].duration*1000;
1471		this.data[i].endTimeMS = this.data[i].startTimeMS+this.data[i].durationMS;
1472		this.totalTime += this.data[i].duration;
1473	}
1474
1475};
1476
1477if (navigator.userAgent.match(/webkit/i) && navigator.userAgent.match(/mobile/i)) {
1478	// iPad, iPhone etc.
1479	soundManager.setup({
1480		useHTML5Audio: true
1481	});
1482}
1483
1484soundManager.setup({
1485	html5PollingInterval: 50, // increased framerate for whileplaying() etc.
1486	debugMode: (window.location.href.match(/debug=1/i)), // disable or enable debug output
1487	consoleOnly: true,
1488	flashVersion: 9,
1489	useHighPerformance: true/*,
1490	useFlashBlock: true*/
1491});
1492
1493// FPS data, testing/debug only
1494if (soundManager.debugMode) {
1495	window.setInterval(function() {
1496		var p = window.threeSixtyPlayer;
1497		if (p && p.lastSound && p.lastSound._360data.fps && typeof window.isHome === 'undefined') {
1498			soundManager._writeDebug('fps: ~'+p.lastSound._360data.fps);
1499			p.lastSound._360data.fps = 0;
1500		}
1501	},1000);
1502}
1503
1504window.ThreeSixtyPlayer = ThreeSixtyPlayer; // constructor
1505
1506}(window));
1507
1508threeSixtyPlayer = new ThreeSixtyPlayer();
1509
1510// hook into SM2 init
1511soundManager.onready(threeSixtyPlayer.init);
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522/**
1523 *
1524 *
1525 *	Template init
1526 *
1527 *
1528 *
1529 *
1530 *
1531 *
1532 * 
1533 */
1534
1535
1536
1537
1538
1539(function ($) {
1540
1541	/**====================================================================
1542	*
1543	*  31. Sound destroy
1544	* 
1545	====================================================================*/
1546	$.fn.destroyAll360Sounds = function(){
1547		//console.log("Destroy all 360 sounds: ");
1548		if(threeSixtyPlayer !== undefined) {
1549			threeSixtyPlayer.sounds.forEach(function(element, index, array){
1550				soundManager.stop(element.id);
1551		  		soundManager.destroySound(element.id);
1552			});			
1553		}
1554
1555	}
1556
1557	$.fn.qt360player = function(targetContainer, action){
1558		console.log("Soundmanager URL: "+$("#qtplayercontainer").attr("data-soundmanagerurl"));
1559		var playerContainer = $("#qtplayercontainer"),
1560			qtBody = $("body");
1561
1562
1563		soundManager.setup({
1564	        url: playerContainer.attr("data-soundmanagerurl"),
1565	        allowScriptAccess: 'always',
1566	        useHighPerformance: true,
1567	        consoleOnly: true,
1568	        debugMode: false,
1569	       	debugFlash: false,
1570
1571		});
1572		soundManager.flash9Options.useWaveformData = true; 
1573	  	soundManager.flash9Options.useEQData = true;
1574	  	soundManager.flash9Options.usePeakData = true;
1575	  	soundManager.preferFlash = true;
1576	   	soundManager.flashVersion = 9;
1577
1578	   	var playerVolume = false;
1579	   	if(!navigator.userAgent.match(/mobile/i) && playerContainer.attr("data-playervolume") === "true") {
1580	   		playerVolume = true;
1581	   	}
1582
1583	   	threeSixtyPlayer.config = {
1584			playNext: false,
1585			autoPlay: false,
1586			allowMultiple: false,
1587			playervolume:  true,
1588			loadRingColor: playerContainer.attr("data-textcolor"), // ACCENT COLOR
1589			playRingColor:  playerContainer.attr("data-accentcolor"), // DARKER
1590			backgroundRingColor:playerContainer.attr("data-accentcolordark"),
1591			circleDiameter: 280,
1592			circleRadius: 140,
1593			animDuration: 500,
1594			animTransition: Animator.tx.bouncy,
1595			showHMSTime: true,
1596			useWaveformData: true,
1597			waveformDataColor: '#fff',
1598			waveformDataDownsample: 3,
1599			waveformDataOutside: false,
1600			waveformDataConstrain: false,
1601			waveformDataLineRatio: 0.8,
1602			useEQData: true,
1603			eqDataColor: '#FFF',
1604			eqDataDownsample: 2,
1605			eqDataOutside: true,
1606			eqDataLineRatio: 0.73,
1607			usePeakData: true,
1608			peakDataColor: '#FFF',
1609			peakDataOutside: true,
1610			peakDataLineRatio: 1.8,
1611			scaleArcWidth: 0.80,
1612			useAmplifier: true,
1613			useFavIcon: true
1614
1615		}
1616
1617		threeSixtyPlayer.config.useWaveformData = true;
1618		threeSixtyPlayer.config.useEQData = true;
1619
1620		// enable this in SM2 as well, as needed
1621
1622		if (threeSixtyPlayer.config.useWaveformData) {
1623		  soundManager.flash9Options.useWaveformData = true;
1624		}
1625		if (threeSixtyPlayer.config.useEQData) {
1626		  soundManager.flash9Options.useEQData = true;
1627		}
1628		if (threeSixtyPlayer.config.usePeakData) {
1629		  soundManager.flash9Options.usePeakData = true;
1630		}
1631
1632		if (threeSixtyPlayer.config.useWaveformData || threeSixtyPlayer.flash9Options.useEQData || threeSixtyPlayer.flash9Options.usePeakData) {
1633		  // even if HTML5 supports MP3, prefer flash so the visualization features can be used.
1634		  soundManager.preferFlash = true;
1635		}
1636
1637		if(action === "destroy") {
1638			$.fn.destroyAll360Sounds();
1639		}
1640
1641
1642		if(undefined === targetContainer) {
1643			targetContainer = "body";
1644		};
1645
1646
1647		var url, actualplaying, player, target, smState = false, playtrack, firstloaded = false, 
1648			qtTracktitle = $(targetContainer).find("#qtradiotitle"),
1649			qtradiosubtitle = $(targetContainer).find("#qtradiosubtitle"),
1650			playClass = 'dripicons-media-play',
1651	    	pauseClass= "dripicons-media-pause",
1652	    	loadingClass = "dripicons-media-play", 
1653	    	trackextension = '';
1654
1655
1656
1657		// Load a track in the player
1658		//================================================
1659
1660		function loadInPlayer (c, autoplay){
1661			console.log("\n ======== loadInPlayer init ==========");
1662			$.fn.destroyAll360Sounds();
1663			playtrack = c.attr( "data-playtrack");
1664			if(playtrack === undefined) {
1665				return;
1666			}
1667			mp3url =  c.attr( "data-playtrack").split("geo-sample").join("sample");
1668			soundManager.stopAll();
1669			var player = $('.qt-ui360');
1670			player.empty();
1671			var random = Math.floor(Math.random()*1000000);
1672			player.append('<a id="playerlink" href="' + mp3url + '?ver=' + random + '"></a>');
1673			if(autoplay){
1674				console.log("Autoplay executing");
1675				threeSixtyPlayer.config.autoPlay = true;
1676				$(targetContainer).find("a.beingplayed").removeClass("beingplayed").find("i").removeClass(pauseClass).addClass(playClass);
1677				actualplaying = mp3url;
1678				c.addClass("beingplayed").find("i").removeClass(playClass).addClass(pauseClass);
1679
1680
1681
1682
1683
1684
1685			} else {
1686				$(targetContainer).find("a.beingplayed").removeClass("beingplayed").find("i").removeClass(pauseClass).addClass(playClass);				
1687				threeSixtyPlayer.config.autoPlay = true;
1688			}
1689
1690
1691			var background = c.attr("data-background"),
1692				logo = c.attr("data-logo"),
1693				title = c.attr("data-title"),
1694				subtitle = c.attr("data-subtitle");
1695				
1696			qtTracktitle.html(title);
1697			qtradiosubtitle.html(subtitle);
1698			console.log("======================================>>>>>>> "+background);
1699			$("#playerimage").attr("data-bgimage", background).css({"background-image": "url("+background+")"});
1700			$("#playerimage img").attr("src", background);
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710			threeSixtyPlayer.init();
1711			return;
1712		};
1713
1714
1715		// MP3 link click
1716		//================================================
1717		qtBody.off("click", "a[data-playtrack]");
1718
1719		qtBody.on("click", "a[data-playtrack]", function(e) {
1720			console.log("Click mp3 link");
1721			e.preventDefault();
1722			var c = $(this);
1723			if(c.hasClass("beingplayed")){
1724				loadInPlayer($(this), false);
1725			} else {
1726				loadInPlayer($(this), true);
1727			}
1728
1729			// We update the feed here:==================
1730			// 
1731			var qtShoutcastFeedData = $("#qtShoutcastFeedData"); // where we store the data (in the player container hidden div)
1732			
1733			var	qtradiofeedHost = c.attr("data-host"),
1734				qtradiofeedPort =  c.attr("data-port"),
1735				qtradiofeedChannel =  c.attr("data-channel"),
1736				qrFeedStyle = qtShoutcastFeedData.attr("data-style");
1737
1738			if(qtradiofeedChannel === '' || qtradiofeedChannel === undefined){
1739				qtradiofeedChannel = '1';
1740			}
1741
1742			qtShoutcastFeedData.attr("data-host",qtradiofeedHost);
1743			qtShoutcastFeedData.attr("data-port",qtradiofeedPort);
1744			qtShoutcastFeedData.attr("data-channel",qtradiofeedChannel);
1745
1746			$.SHOUTcast().stopStats();
1747			$.SHOUTcast().stopStats();
1748			$.SHOUTcast().stopStats();
1749
1750			$.SHOUTcast({
1751				host : qtradiofeedHost,
1752				port : qtradiofeedPort,
1753				interval: 5000,
1754				stream : qtradiofeedChannel,
1755				stats : function(){
1756					if(this.onAir()){
1757						// console.log("NOW ADDING SONG FEED: "+qrFeedStyle);
1758						// $.cookie('feedHost', qtradiofeedHost, { expires: 1, path: '/' }); // cookie removed
1759						// $.cookie('feedPort', qtradiofeedPort, { expires: 1, path: '/' });// cookie removed
1760						
1761						// console.log("SONG TITLE: "+this.get('songtitle'));
1762
1763						var result = this.get('songtitle');
1764						var feedsplit = result.split(" - "),
1765							author = feedsplit[0],
1766							title = feedsplit[1];
1767
1768						// console.log("author: "+author);
1769						// console.log("title: "+title);
1770
1771
1772						qtFeedPlayerTrack.html(title);
1773						qtFeedPlayerAuthor.html(author);
1774					 } else {
1775					 	// Add disconnect here
1776					 	$.SHOUTcast().stopStats();
1777					 	qtFeedPlayerTrack.html("Información no disponible");
1778						qtFeedPlayerAuthor.html("No disponible sin conexión");
1779					 }
1780				}
1781			}).startStats();
1782
1783			$.SHOUTcast().stopStats();
1784			$.SHOUTcast({
1785				host : qtradiofeedHost,
1786				port : qtradiofeedPort,
1787				interval: 5000,
1788				stream : qtradiofeedChannel,
1789				stats : function(){
1790					if(this.onAir()){
1791						// console.log("NOW ADDING SONG FEED: "+qrFeedStyle);
1792						// $.cookie('feedHost', qtradiofeedHost, { expires: 1, path: '/' }); // cookie removed
1793						// $.cookie('feedPort', qtradiofeedPort, { expires: 1, path: '/' });// cookie removed
1794						
1795						// console.log("SONG TITLE: "+this.get('songtitle'));
1796
1797						var result = this.get('songtitle');
1798						var feedsplit = result.split(" - "),
1799							author = feedsplit[0],
1800							title = feedsplit[1];
1801
1802						// console.log("author: "+author);
1803						// console.log("title: "+title);
1804
1805
1806						qtFeedPlayerTrack.html(title);
1807						qtFeedPlayerAuthor.html(author);
1808					 } else {
1809					 	// Add disconnect here
1810					 	$.SHOUTcast().stopStats();
1811					 	qtFeedPlayerTrack.html("Información no disponible");
1812						qtFeedPlayerAuthor.html("No disponible sin conexión");
1813					 }
1814				}
1815			}).startStats();
1816
1817
1818			if(qtradiofeedHost != ''){
1819				// we transfer host and port from the list to the player
1820				
1821			} else {
1822				console.log("qtradiofeedHost is empty");
1823			}
1824
1825
1826			return false;/**/
1827		});
1828
1829	
1830
1831
1832	} // 360 player end
1833	$.fn.qt360player();
1834
1835
1836
1837	/*
1838	    *
1839	    *
1840	    *	SHOTCAST XML Feed Info
1841	    *
1842	    *
1843	    *
1844	    */
1845	    
1846	    $.cookie('feedHost', "", { expires: 1, path: '/' });
1847
1848
1849
1850		$.fn.qtShoutcastFeed = function(host = '', port = '', theChannel = ''){
1851			console.log("\n ============================================= SHOUTCAST FEED ");
1852			$.SHOUTcast({
1853					interval : 9999999999,
1854					host : "",
1855					port : ""
1856				}).stopStats();
1857
1858			var qtShoutcastFeedData = $("#qtShoutcastFeedData"),
1859				qtradiofeedHost = qtShoutcastFeedData.attr("data-host"),
1860				qtradiofeedPort =  qtShoutcastFeedData.attr("data-port"),
1861				qrFeedStyle = qtShoutcastFeedData.attr("data-style"),
1862				qtFeedPlayerTrack = $('#qtFeedPlayerTrack'),
1863				qtFeedPlayerAuthor = $('#qtFeedPlayerAuthor'),
1864				theChannel = qtShoutcastFeedData.attr("data-channel");
1865
1866
1867			// cookie removed
1868			/*
1869			if($.cookie('feedHost') != '' && $.cookie('feedHost') !== null){
1870				qtradiofeedHost = $.cookie('feedHost');
1871				qtradiofeedPort = $.cookie('feedPort');
1872				console.log("restoring feed Host and Port from cookies");
1873			}*/
1874
1875		/*	if(qtradiofeedHost === '' || qtradiofeedPort === '') {
1876				console.log(">>>>>> HEY STOP HERE IS NOT VALID");
1877				$.SHOUTcast().stopStats({});
1878				qtFeedPlayerTrack.html("No stats");
1879				qtFeedPlayerAuthor.html("There are no stats for this channel");
1880				console.log("Host ["+host+"] or port empty. Aborting feed ]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
1881				return;
1882			}*/
1883
1884
1885			console.log("Host is: "+qtradiofeedHost);
1886			console.log("Port is: "+qtradiofeedPort);
1887
1888			if(qrFeedStyle === '0') {
1889				$.SHOUTcast().stopStats();
1890				console.log("Shoutcast Feed DISABLED in customizer");
1891				return;
1892			}
1893
1894			/* Cookie playing status */
1895
1896
1897			qtFeedPlayerTrack.html("");
1898			qtFeedPlayerAuthor.html("");
1899
1900			console.log("qtradiofeedHost ===> "+qtradiofeedHost);
1901			if(qtradiofeedHost === '' || qtradiofeedPort === ''){
1902				$.SHOUTcast({
1903					interval : 9999999999,
1904					host : "",
1905					port : ""
1906				}).stopStats();
1907				console.log("Shoutcast Feed URL invalid");
1908				qtFeedPlayerTrack.html("No stats");
1909				qtFeedPlayerAuthor.html("There are no stats for this channel");
1910				return;
1911			} else {
1912				$.SHOUTcast().stopStats();
1913			    $.SHOUTcast({
1914					host : qtradiofeedHost,
1915					port : qtradiofeedPort,
1916					interval: 5000,
1917					stream : theChannel,
1918					stats : function(){
1919						if(this.onAir()){
1920							// console.log("NOW ADDING SONG FEED: "+qrFeedStyle);
1921							// $.cookie('feedHost', qtradiofeedHost, { expires: 1, path: '/' }); // cookie removed
1922							// $.cookie('feedPort', qtradiofeedPort, { expires: 1, path: '/' });// cookie removed
1923							
1924							// console.log("SONG TITLE: "+this.get('songtitle'));
1925
1926							var result = this.get('songtitle');
1927							var feedsplit = result.split(" - "),
1928								author = feedsplit[0],
1929								title = feedsplit[1];
1930
1931							// console.log("author: "+author);
1932							// console.log("title: "+title);
1933
1934
1935							qtFeedPlayerTrack.html(title);
1936							qtFeedPlayerAuthor.html(author);
1937						 } else {
1938						 	// Add disconnect here
1939						 	$.SHOUTcast().stopStats();
1940						 	qtFeedPlayerTrack.html("Información no disponible");
1941							qtFeedPlayerAuthor.html("No disponible sin conexión");
1942						 }
1943					}
1944				}).startStats();
1945
1946
1947
1948			}
1949		}
1950		var qtShoutcastFeedData = $("#qtShoutcastFeedData"),
1951				qtradiofeedHost = qtShoutcastFeedData.attr("data-host"),
1952				qtradiofeedPort =  qtShoutcastFeedData.attr("data-port"),
1953				qrFeedStyle = qtShoutcastFeedData.attr("data-style"),
1954				theChannel = qtShoutcastFeedData.attr("data-channel"),
1955				qtFeedPlayerTrack = $('#qtFeedPlayerTrack'),
1956				qtFeedPlayerAuthor = $('#qtFeedPlayerAuthor');
1957
1958		$.SHOUTcast({
1959			host : qtradiofeedHost,
1960			port : qtradiofeedPort,
1961			interval: 5000,
1962			stream : theChannel,
1963			stats : function(){
1964				if(this.onAir()){
1965					// console.log("NOW ADDING SONG FEED: "+qrFeedStyle);
1966					// $.cookie('feedHost', qtradiofeedHost, { expires: 1, path: '/' }); // cookie removed
1967					// $.cookie('feedPort', qtradiofeedPort, { expires: 1, path: '/' });// cookie removed
1968					
1969					// console.log("SONG TITLE: "+this.get('songtitle'));
1970
1971					var result = this.get('songtitle');
1972					var feedsplit = result.split(" - "),
1973						author = feedsplit[0],
1974						title = feedsplit[1];
1975
1976					// console.log("author: "+author);
1977					// console.log("title: "+title);
1978
1979
1980					qtFeedPlayerTrack.html(title);
1981					qtFeedPlayerAuthor.html(author);
1982				 } else {
1983				 	// Add disconnect here
1984				 	$.SHOUTcast().stopStats();
1985				 	qtFeedPlayerTrack.html("Información no disponible");
1986					qtFeedPlayerAuthor.html("No disponible sin conexión");
1987				 }
1988			}
1989		}).startStats();
1990
1991		$.fn.qtShoutcastFeed();
1992})(jQuery);
1993

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.