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.