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